Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more |
3 |
* contributor license agreements. See the NOTICE file distributed with |
4 |
* this work for additional information regarding copyright ownership. |
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0 |
6 |
* (the "License"); you may not use this file except in compliance with |
7 |
* the License. You may obtain a copy of the License at |
8 |
* |
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
10 |
* |
11 |
* Unless required by applicable law or agreed to in writing, software |
12 |
* distributed under the License is distributed on an "AS IS" BASIS, |
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
* See the License for the specific language governing permissions and |
15 |
* limitations under the License. |
16 |
*/ |
17 |
|
18 |
/* $Id$ */ |
19 |
|
20 |
// Original author: Matthias Reichenbacher |
21 |
|
22 |
package org.apache.fop.render.pdf; |
23 |
|
24 |
import java.awt.image.ColorModel; |
25 |
import java.awt.image.IndexColorModel; |
26 |
import java.io.DataInputStream; |
27 |
import java.io.IOException; |
28 |
import java.io.InputStream; |
29 |
import java.io.OutputStream; |
30 |
import java.util.zip.Deflater; |
31 |
import java.util.zip.DeflaterOutputStream; |
32 |
import java.util.zip.Inflater; |
33 |
import java.util.zip.InflaterInputStream; |
34 |
|
35 |
import org.apache.commons.io.IOUtils; |
36 |
import org.apache.commons.io.output.ByteArrayOutputStream; |
37 |
import org.apache.commons.logging.Log; |
38 |
import org.apache.commons.logging.LogFactory; |
39 |
|
40 |
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG; |
41 |
import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; |
42 |
|
43 |
import org.apache.fop.pdf.BitmapImage; |
44 |
import org.apache.fop.pdf.FlateFilter; |
45 |
import org.apache.fop.pdf.PDFColor; |
46 |
import org.apache.fop.pdf.PDFDeviceColorSpace; |
47 |
import org.apache.fop.pdf.PDFDictionary; |
48 |
import org.apache.fop.pdf.PDFDocument; |
49 |
import org.apache.fop.pdf.PDFFilter; |
50 |
import org.apache.fop.pdf.PDFFilterException; |
51 |
import org.apache.fop.pdf.PDFFilterList; |
52 |
import org.apache.fop.pdf.PDFICCStream; |
53 |
import org.apache.fop.pdf.PDFReference; |
54 |
|
55 |
public class ImageRawPNGAdapter extends AbstractImageAdapter { |
56 |
|
57 |
/** logging instance */ |
58 |
private static Log log = LogFactory.getLog(ImageRawPNGAdapter.class); |
59 |
|
60 |
private PDFICCStream pdfICCStream; |
61 |
private PDFFilter pdfFilter; |
62 |
private String maskRef; |
63 |
private PDFReference softMask; |
64 |
private int numberOfInterleavedComponents; |
65 |
|
66 |
/** |
67 |
* Creates a new PDFImage from an Image instance. |
68 |
* @param image the image |
69 |
* @param key XObject key |
70 |
*/ |
71 |
public ImageRawPNGAdapter(ImageRawPNG image, String key) { |
72 |
super(image, key); |
73 |
} |
74 |
|
75 |
/** {@inheritDoc} */ |
76 |
public void setup(PDFDocument doc) { |
77 |
super.setup(doc); |
78 |
ColorModel cm = ((ImageRawPNG) this.image).getColorModel(); |
79 |
if (cm instanceof IndexColorModel) { |
80 |
numberOfInterleavedComponents = 1; |
81 |
} else { |
82 |
// this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha) |
83 |
// numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents(); |
84 |
numberOfInterleavedComponents = cm.getNumComponents(); |
85 |
} |
86 |
|
87 |
// set up image compression for non-alpha channel |
88 |
FlateFilter flate; |
89 |
try { |
90 |
flate = new FlateFilter(); |
91 |
flate.setApplied(true); |
92 |
flate.setPredictor(FlateFilter.PREDICTION_PNG_OPT); |
93 |
if (numberOfInterleavedComponents < 3) { |
94 |
// means palette (1) or gray (1) or gray + alpha (2) |
95 |
flate.setColors(1); |
96 |
} else { |
97 |
// means rgb (3) or rgb + alpha (4) |
98 |
flate.setColors(3); |
99 |
} |
100 |
flate.setColumns(image.getSize().getWidthPx()); |
101 |
flate.setBitsPerComponent(this.getBitsPerComponent()); |
102 |
} catch (PDFFilterException e) { |
103 |
throw new RuntimeException("FlateFilter configuration error", e); |
104 |
} |
105 |
this.pdfFilter = flate; |
106 |
|
107 |
// Handle transparency channel if applicable; note that for palette images the transparency is |
108 |
// not TRANSLUCENT |
109 |
if (cm.hasAlpha() && cm.getTransparency() == ColorModel.TRANSLUCENT) { |
110 |
doc.getProfile().verifyTransparencyAllowed(image.getInfo().getOriginalURI()); |
111 |
// TODO: Implement code to combine image with background color if transparency is not allowed |
112 |
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha channel |
113 |
// and then deflate it back again |
114 |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
115 |
DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater()); |
116 |
InputStream in = ((ImageRawStream) image).createInputStream(); |
117 |
try { |
118 |
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater()); |
119 |
DataInputStream dataStream = new DataInputStream(infStream); |
120 |
// offset is the byte offset of the alpha component |
121 |
int offset = numberOfInterleavedComponents - 1; // 1 for GA, 3 for RGBA |
122 |
int numColumns = image.getSize().getWidthPx(); |
123 |
int bytesPerRow = numberOfInterleavedComponents * numColumns; |
124 |
int filter; |
125 |
// read line by line; the first byte holds the filter |
126 |
while ((filter = dataStream.read()) != -1) { |
127 |
byte[] bytes = new byte[bytesPerRow]; |
128 |
dataStream.readFully(bytes, 0, bytesPerRow); |
129 |
dos.write((byte) filter); |
130 |
for (int j = 0; j < numColumns; j++) { |
131 |
dos.write(bytes, offset, 1); |
132 |
offset += numberOfInterleavedComponents; |
133 |
} |
134 |
offset = numberOfInterleavedComponents - 1; |
135 |
} |
136 |
dos.close(); |
137 |
} catch (IOException e) { |
138 |
throw new RuntimeException("Error processing transparency channel:", e); |
139 |
} finally { |
140 |
IOUtils.closeQuietly(in); |
141 |
} |
142 |
// set up alpha channel compression |
143 |
FlateFilter transFlate; |
144 |
try { |
145 |
transFlate = new FlateFilter(); |
146 |
transFlate.setApplied(true); |
147 |
transFlate.setPredictor(FlateFilter.PREDICTION_PNG_OPT); |
148 |
transFlate.setColors(1); |
149 |
transFlate.setColumns(image.getSize().getWidthPx()); |
150 |
transFlate.setBitsPerComponent(this.getBitsPerComponent()); |
151 |
} catch (PDFFilterException e) { |
152 |
throw new RuntimeException("FlateFilter configuration error", e); |
153 |
} |
154 |
BitmapImage alphaMask = new BitmapImage("Mask:" + this.getKey(), image.getSize().getWidthPx(), |
155 |
image.getSize().getHeightPx(), baos.toByteArray(), null); |
156 |
alphaMask.setPDFFilter(transFlate); |
157 |
alphaMask.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY)); |
158 |
softMask = doc.addImage(null, alphaMask).makeReference(); |
159 |
} |
160 |
} |
161 |
|
162 |
/** {@inheritDoc} */ |
163 |
public PDFDeviceColorSpace getColorSpace() { |
164 |
// DeviceGray, DeviceRGB, or DeviceCMYK |
165 |
return toPDFColorSpace(image.getColorSpace()); |
166 |
} |
167 |
|
168 |
/** {@inheritDoc} */ |
169 |
public int getBitsPerComponent() { |
170 |
return ((ImageRawPNG) this.image).getBitDepth(); |
171 |
} |
172 |
|
173 |
/** {@inheritDoc} */ |
174 |
public boolean isTransparent() { |
175 |
return ((ImageRawPNG) this.image).isTransparent(); |
176 |
} |
177 |
|
178 |
/** {@inheritDoc} */ |
179 |
public PDFColor getTransparentColor() { |
180 |
return new PDFColor(((ImageRawPNG) this.image).getTransparentColor()); |
181 |
} |
182 |
|
183 |
/** {@inheritDoc} */ |
184 |
public String getMask() { |
185 |
return maskRef; |
186 |
} |
187 |
|
188 |
/** {@inheritDoc} */ |
189 |
public String getSoftMask() { |
190 |
return softMask.toString(); |
191 |
} |
192 |
|
193 |
/** {@inheritDoc} */ |
194 |
public PDFReference getSoftMaskReference() { |
195 |
return softMask; |
196 |
} |
197 |
|
198 |
/** {@inheritDoc} */ |
199 |
public PDFFilter getPDFFilter() { |
200 |
return pdfFilter; |
201 |
} |
202 |
|
203 |
/** {@inheritDoc} */ |
204 |
public void outputContents(OutputStream out) throws IOException { |
205 |
InputStream in = ((ImageRawStream) image).createInputStream(); |
206 |
|
207 |
try { |
208 |
if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) { |
209 |
// means we have Gray, RGB, or Palette |
210 |
IOUtils.copy(in, out); |
211 |
} else { |
212 |
// means we have Gray + alpha or RGB + alpha |
213 |
// TODO: since we have alpha here do this when the alpha channel is extracted |
214 |
int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB |
215 |
int numColumns = image.getSize().getWidthPx(); |
216 |
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater()); |
217 |
DataInputStream dataStream = new DataInputStream(infStream); |
218 |
int offset = 0; |
219 |
int bytesPerRow = numberOfInterleavedComponents * numColumns; |
220 |
int filter; |
221 |
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha |
222 |
// channel and then deflate the RGB channels back again |
223 |
DeflaterOutputStream dos = new DeflaterOutputStream(out, new Deflater()); |
224 |
while ((filter = dataStream.read()) != -1) { |
225 |
byte[] bytes = new byte[bytesPerRow]; |
226 |
dataStream.readFully(bytes, 0, bytesPerRow); |
227 |
dos.write((byte) filter); |
228 |
for (int j = 0; j < numColumns; j++) { |
229 |
dos.write(bytes, offset, numBytes); |
230 |
offset += numberOfInterleavedComponents; |
231 |
} |
232 |
offset = 0; |
233 |
} |
234 |
dos.close(); |
235 |
} |
236 |
} finally { |
237 |
IOUtils.closeQuietly(in); |
238 |
} |
239 |
} |
240 |
|
241 |
/** {@inheritDoc} */ |
242 |
public PDFICCStream getICCStream() { |
243 |
return pdfICCStream; |
244 |
} |
245 |
|
246 |
/** {@inheritDoc} */ |
247 |
public String getFilterHint() { |
248 |
return PDFFilterList.PRECOMPRESSED_FILTER; |
249 |
} |
250 |
|
251 |
public void populateXObjectDictionary(PDFDictionary dict) { |
252 |
ColorModel cm = ((ImageRawPNG) image).getColorModel(); |
253 |
if (cm instanceof IndexColorModel) { |
254 |
IndexColorModel icm = (IndexColorModel) cm; |
255 |
super.populateXObjectDictionaryForIndexColorModel(dict, icm); |
256 |
} |
257 |
} |
258 |
} |