9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.imageio.plugins.png;
27
28 import java.awt.Rectangle;
29 import java.awt.image.ColorModel;
30 import java.awt.image.IndexColorModel;
31 import java.awt.image.Raster;
32 import java.awt.image.WritableRaster;
33 import java.awt.image.RenderedImage;
34 import java.awt.image.SampleModel;
35 import java.io.ByteArrayOutputStream;
36 import java.io.DataOutput;
37 import java.io.IOException;
38 import java.io.OutputStream;
39 import java.util.Iterator;
40 import java.util.Locale;
41 import java.util.zip.Deflater;
42 import java.util.zip.DeflaterOutputStream;
43 import javax.imageio.IIOException;
44 import javax.imageio.IIOImage;
45 import javax.imageio.ImageTypeSpecifier;
46 import javax.imageio.ImageWriteParam;
47 import javax.imageio.ImageWriter;
48 import javax.imageio.metadata.IIOMetadata;
49 import javax.imageio.metadata.IIOMetadata;
50 import javax.imageio.spi.ImageWriterSpi;
51 import javax.imageio.stream.ImageOutputStream;
52 import javax.imageio.stream.ImageOutputStreamImpl;
53
54 class CRC {
55
56 private static int[] crcTable = new int[256];
57 private int crc = 0xffffffff;
58
59 static {
60 // Initialize CRC table
61 for (int n = 0; n < 256; n++) {
62 int c = n;
63 for (int k = 0; k < 8; k++) {
64 if ((c & 1) == 1) {
65 c = 0xedb88320 ^ (c >>> 1);
66 } else {
67 c >>>= 1;
68 }
69
70 crcTable[n] = c;
71 }
72 }
73 }
74
75 public CRC() {}
76
77 public void reset() {
78 crc = 0xffffffff;
79 }
80
81 public void update(byte[] data, int off, int len) {
82 for (int n = 0; n < len; n++) {
83 crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8);
84 }
85 }
86
87 public void update(int data) {
88 crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
89 }
90
91 public int getValue() {
92 return crc ^ 0xffffffff;
93 }
94 }
95
96
97 final class ChunkStream extends ImageOutputStreamImpl {
98
99 private ImageOutputStream stream;
100 private long startPos;
101 private CRC crc = new CRC();
102
103 public ChunkStream(int type, ImageOutputStream stream) throws IOException {
104 this.stream = stream;
105 this.startPos = stream.getStreamPosition();
106
107 stream.writeInt(-1); // length, will backpatch
108 writeInt(type);
109 }
110
111 public int read() throws IOException {
112 throw new RuntimeException("Method not available");
113 }
114
115 public int read(byte[] b, int off, int len) throws IOException {
116 throw new RuntimeException("Method not available");
117 }
118
119 public void write(byte[] b, int off, int len) throws IOException {
120 crc.update(b, off, len);
121 stream.write(b, off, len);
122 }
123
124 public void write(int b) throws IOException {
125 crc.update(b);
126 stream.write(b);
127 }
128
129 public void finish() throws IOException {
130 // Write CRC
131 stream.writeInt(crc.getValue());
132
133 // Write length
134 long pos = stream.getStreamPosition();
135 stream.seek(startPos);
136 stream.writeInt((int)(pos - startPos) - 12);
137
138 // Return to end of chunk and flush to minimize buffering
139 stream.seek(pos);
140 stream.flushBefore(pos);
141 }
142
143 protected void finalize() throws Throwable {
144 // Empty finalizer (for improved performance; no need to call
145 // super.finalize() in this case)
146 }
147 }
148
149 // Compress output and write as a series of 'IDAT' chunks of
150 // fixed length.
151 final class IDATOutputStream extends ImageOutputStreamImpl {
152
153 private static byte[] chunkType = {
154 (byte)'I', (byte)'D', (byte)'A', (byte)'T'
155 };
156
157 private ImageOutputStream stream;
158 private int chunkLength;
159 private long startPos;
160 private CRC crc = new CRC();
161
162 Deflater def = new Deflater(Deflater.BEST_COMPRESSION);
163 byte[] buf = new byte[512];
164
165 private int bytesRemaining;
166
167 public IDATOutputStream(ImageOutputStream stream, int chunkLength)
168 throws IOException {
169 this.stream = stream;
170 this.chunkLength = chunkLength;
171 startChunk();
172 }
173
174 private void startChunk() throws IOException {
175 crc.reset();
176 this.startPos = stream.getStreamPosition();
177 stream.writeInt(-1); // length, will backpatch
178
179 crc.update(chunkType, 0, 4);
180 stream.write(chunkType, 0, 4);
181
182 this.bytesRemaining = chunkLength;
183 }
184
185 private void finishChunk() throws IOException {
186 // Write CRC
187 stream.writeInt(crc.getValue());
188
189 // Write length
190 long pos = stream.getStreamPosition();
191 stream.seek(startPos);
192 stream.writeInt((int)(pos - startPos) - 12);
193
194 // Return to end of chunk and flush to minimize buffering
195 stream.seek(pos);
196 try {
197 stream.flushBefore(pos);
198 } catch (IOException e) {
199 /*
200 * If flushBefore() fails we try to access startPos in finally
201 * block of write_IDAT(). We should update startPos to avoid
202 * IndexOutOfBoundException while seek() is happening.
203 */
204 this.startPos = stream.getStreamPosition();
205 throw e;
206 }
207 }
208
209 public int read() throws IOException {
210 throw new RuntimeException("Method not available");
211 }
212
213 public int read(byte[] b, int off, int len) throws IOException {
214 throw new RuntimeException("Method not available");
215 }
216
217 public void write(byte[] b, int off, int len) throws IOException {
218 if (len == 0) {
219 return;
220 }
221
222 if (!def.finished()) {
223 def.setInput(b, off, len);
224 while (!def.needsInput()) {
225 deflate();
226 }
227 }
228 }
229
230 public void deflate() throws IOException {
231 int len = def.deflate(buf, 0, buf.length);
232 int off = 0;
233
234 while (len > 0) {
235 if (bytesRemaining == 0) {
236 finishChunk();
237 startChunk();
238 }
239
240 int nbytes = Math.min(len, bytesRemaining);
241 crc.update(buf, off, nbytes);
242 stream.write(buf, off, nbytes);
243
244 off += nbytes;
245 len -= nbytes;
246 bytesRemaining -= nbytes;
247 }
248 }
249
250 public void write(int b) throws IOException {
251 byte[] wbuf = new byte[1];
252 wbuf[0] = (byte)b;
253 write(wbuf, 0, 1);
254 }
255
256 public void finish() throws IOException {
257 try {
258 if (!def.finished()) {
259 def.finish();
260 while (!def.finished()) {
261 deflate();
262 }
263 }
264 finishChunk();
265 } finally {
266 def.end();
267 }
268 }
269
270 protected void finalize() throws Throwable {
271 // Empty finalizer (for improved performance; no need to call
272 // super.finalize() in this case)
273 }
274 }
275
276
277 class PNGImageWriteParam extends ImageWriteParam {
278
279 public PNGImageWriteParam(Locale locale) {
280 super();
281 this.canWriteProgressive = true;
282 this.locale = locale;
283 }
284 }
285
286 /**
287 */
288 public class PNGImageWriter extends ImageWriter {
289
290 ImageOutputStream stream = null;
291
292 PNGMetadata metadata = null;
293
294 // Factors from the ImageWriteParam
295 int sourceXOffset = 0;
296 int sourceYOffset = 0;
297 int sourceWidth = 0;
298 int sourceHeight = 0;
299 int[] sourceBands = null;
300 int periodX = 1;
301 int periodY = 1;
302
303 int numBands;
304 int bpp;
305
306 RowFilter rowFilter = new RowFilter();
307 byte[] prevRow = null;
308 byte[] currRow = null;
309 byte[][] filteredRows = null;
310
311 // Per-band scaling tables
317 // given by sampleSize, and an output bit depth given by scalingBitDepth.
318 //
319 int[] sampleSize = null; // Sample size per band, in bits
320 int scalingBitDepth = -1; // Output bit depth of the scaling tables
321
322 // Tables for 1, 2, 4, or 8 bit output
323 byte[][] scale = null; // 8 bit table
324 byte[] scale0 = null; // equivalent to scale[0]
325
326 // Tables for 16 bit output
327 byte[][] scaleh = null; // High bytes of output
328 byte[][] scalel = null; // Low bytes of output
329
330 int totalPixels; // Total number of pixels to be written by write_IDAT
331 int pixelsDone; // Running count of pixels written by write_IDAT
332
333 public PNGImageWriter(ImageWriterSpi originatingProvider) {
334 super(originatingProvider);
335 }
336
337 public void setOutput(Object output) {
338 super.setOutput(output);
339 if (output != null) {
340 if (!(output instanceof ImageOutputStream)) {
341 throw new IllegalArgumentException("output not an ImageOutputStream!");
342 }
343 this.stream = (ImageOutputStream)output;
344 } else {
345 this.stream = null;
346 }
347 }
348
349 private static int[] allowedProgressivePasses = { 1, 7 };
350
351 public ImageWriteParam getDefaultWriteParam() {
352 return new PNGImageWriteParam(getLocale());
353 }
354
355 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
356 return null;
357 }
358
359 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
360 ImageWriteParam param) {
361 PNGMetadata m = new PNGMetadata();
362 m.initialize(imageType, imageType.getSampleModel().getNumBands());
363 return m;
364 }
365
366 public IIOMetadata convertStreamMetadata(IIOMetadata inData,
367 ImageWriteParam param) {
368 return null;
369 }
370
371 public IIOMetadata convertImageMetadata(IIOMetadata inData,
372 ImageTypeSpecifier imageType,
373 ImageWriteParam param) {
374 // TODO - deal with imageType
375 if (inData instanceof PNGMetadata) {
376 return (PNGMetadata)((PNGMetadata)inData).clone();
377 } else {
378 return new PNGMetadata(inData);
379 }
380 }
381
382 private void write_magic() throws IOException {
383 // Write signature
384 byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
385 stream.write(magic);
386 }
387
388 private void write_IHDR() throws IOException {
389 // Write IHDR chunk
390 ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);
918 os.write(filteredRows[filterType], bpp, bytesPerRow);
919
920 // Swap current and previous rows
921 byte[] swap = currRow;
922 currRow = prevRow;
923 prevRow = swap;
924
925 pixelsDone += hpixels;
926 processImageProgress(100.0F*pixelsDone/totalPixels);
927
928 // If write has been aborted, just return;
929 // processWriteAborted will be called later
930 if (abortRequested()) {
931 return;
932 }
933 }
934 }
935
936 // Use sourceXOffset, etc.
937 private void write_IDAT(RenderedImage image) throws IOException {
938 IDATOutputStream ios = new IDATOutputStream(stream, 32768);
939 try {
940 if (metadata.IHDR_interlaceMethod == 1) {
941 for (int i = 0; i < 7; i++) {
942 encodePass(ios, image,
943 PNGImageReader.adam7XOffset[i],
944 PNGImageReader.adam7YOffset[i],
945 PNGImageReader.adam7XSubsampling[i],
946 PNGImageReader.adam7YSubsampling[i]);
947 if (abortRequested()) {
948 break;
949 }
950 }
951 } else {
952 encodePass(ios, image, 0, 0, 1, 1);
953 }
954 } finally {
955 ios.finish();
956 }
957 }
958
1011 // Divide scaling table into high and low bytes
1012 scaleh = new byte[numBands][];
1013 scalel = new byte[numBands][];
1014
1015 for (int b = 0; b < numBands; b++) {
1016 int maxInSample = (1 << sampleSize[b]) - 1;
1017 int halfMaxInSample = maxInSample/2;
1018 scaleh[b] = new byte[maxInSample + 1];
1019 scalel[b] = new byte[maxInSample + 1];
1020 for (int s = 0; s <= maxInSample; s++) {
1021 int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
1022 scaleh[b][s] = (byte)(val >> 8);
1023 scalel[b][s] = (byte)(val & 0xff);
1024 }
1025 }
1026 scale = null;
1027 scale0 = null;
1028 }
1029 }
1030
1031 public void write(IIOMetadata streamMetadata,
1032 IIOImage image,
1033 ImageWriteParam param) throws IIOException {
1034 if (stream == null) {
1035 throw new IllegalStateException("output == null!");
1036 }
1037 if (image == null) {
1038 throw new IllegalArgumentException("image == null!");
1039 }
1040 if (image.hasRaster()) {
1041 throw new UnsupportedOperationException("image has a Raster!");
1042 }
1043
1044 RenderedImage im = image.getRenderedImage();
1045 SampleModel sampleModel = im.getSampleModel();
1046 this.numBands = sampleModel.getNumBands();
1047
1048 // Set source region and subsampling to default values
1049 this.sourceXOffset = im.getMinX();
1050 this.sourceYOffset = im.getMinY();
1093 int destWidth = (sourceWidth + periodX - 1)/periodX;
1094 int destHeight = (sourceHeight + periodY - 1)/periodY;
1095 if (destWidth <= 0 || destHeight <= 0) {
1096 throw new IllegalArgumentException("Empty source region!");
1097 }
1098
1099 // Compute total number of pixels for progress notification
1100 this.totalPixels = destWidth*destHeight;
1101 this.pixelsDone = 0;
1102
1103 // Create metadata
1104 IIOMetadata imd = image.getMetadata();
1105 if (imd != null) {
1106 metadata = (PNGMetadata)convertImageMetadata(imd,
1107 ImageTypeSpecifier.createFromRenderedImage(im),
1108 null);
1109 } else {
1110 metadata = new PNGMetadata();
1111 }
1112
1113 if (param != null) {
1114 // Use Adam7 interlacing if set in write param
1115 switch (param.getProgressiveMode()) {
1116 case ImageWriteParam.MODE_DEFAULT:
1117 metadata.IHDR_interlaceMethod = 1;
1118 break;
1119 case ImageWriteParam.MODE_DISABLED:
1120 metadata.IHDR_interlaceMethod = 0;
1121 break;
1122 // MODE_COPY_FROM_METADATA should alreay be taken care of
1123 // MODE_EXPLICIT is not allowed
1124 }
1125 }
1126
1127 // Initialize bitDepth and colorType
1128 metadata.initialize(new ImageTypeSpecifier(im), numBands);
1129
1130 // Overwrite IHDR width and height values with values from image
1131 metadata.IHDR_width = destWidth;
1132 metadata.IHDR_height = destHeight;
1133
1134 this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
1135
1136 // Initialize scaling tables for this image
1137 initializeScaleTables(sampleModel.getSampleSize());
1138
1139 clearAbortRequest();
1140
1141 processImageStarted(0);
1142
1143 try {
|
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.imageio.plugins.png;
27
28 import java.awt.Rectangle;
29 import java.awt.image.IndexColorModel;
30 import java.awt.image.Raster;
31 import java.awt.image.WritableRaster;
32 import java.awt.image.RenderedImage;
33 import java.awt.image.SampleModel;
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.util.Iterator;
37 import java.util.Locale;
38 import java.util.zip.Deflater;
39 import java.util.zip.DeflaterOutputStream;
40 import javax.imageio.IIOException;
41 import javax.imageio.IIOImage;
42 import javax.imageio.ImageTypeSpecifier;
43 import javax.imageio.ImageWriteParam;
44 import javax.imageio.ImageWriter;
45 import javax.imageio.metadata.IIOMetadata;
46 import javax.imageio.spi.ImageWriterSpi;
47 import javax.imageio.stream.ImageOutputStream;
48 import javax.imageio.stream.ImageOutputStreamImpl;
49
50 final class CRC {
51
52 private static final int[] crcTable = new int[256];
53 private int crc = 0xffffffff;
54
55 static {
56 // Initialize CRC table
57 for (int n = 0; n < 256; n++) {
58 int c = n;
59 for (int k = 0; k < 8; k++) {
60 if ((c & 1) == 1) {
61 c = 0xedb88320 ^ (c >>> 1);
62 } else {
63 c >>>= 1;
64 }
65
66 crcTable[n] = c;
67 }
68 }
69 }
70
71 CRC() {}
72
73 void reset() {
74 crc = 0xffffffff;
75 }
76
77 void update(byte[] data, int off, int len) {
78 int c = crc;
79 for (int n = 0; n < len; n++) {
80 c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
81 }
82 crc = c;
83 }
84
85 void update(int data) {
86 crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
87 }
88
89 int getValue() {
90 return crc ^ 0xffffffff;
91 }
92 }
93
94
95 final class ChunkStream extends ImageOutputStreamImpl {
96
97 private final ImageOutputStream stream;
98 private final long startPos;
99 private final CRC crc = new CRC();
100
101 ChunkStream(int type, ImageOutputStream stream) throws IOException {
102 this.stream = stream;
103 this.startPos = stream.getStreamPosition();
104
105 stream.writeInt(-1); // length, will backpatch
106 writeInt(type);
107 }
108
109 @Override
110 public int read() throws IOException {
111 throw new RuntimeException("Method not available");
112 }
113
114 @Override
115 public int read(byte[] b, int off, int len) throws IOException {
116 throw new RuntimeException("Method not available");
117 }
118
119 @Override
120 public void write(byte[] b, int off, int len) throws IOException {
121 crc.update(b, off, len);
122 stream.write(b, off, len);
123 }
124
125 @Override
126 public void write(int b) throws IOException {
127 crc.update(b);
128 stream.write(b);
129 }
130
131 void finish() throws IOException {
132 // Write CRC
133 stream.writeInt(crc.getValue());
134
135 // Write length
136 long pos = stream.getStreamPosition();
137 stream.seek(startPos);
138 stream.writeInt((int)(pos - startPos) - 12);
139
140 // Return to end of chunk and flush to minimize buffering
141 stream.seek(pos);
142 stream.flushBefore(pos);
143 }
144
145 @Override
146 protected void finalize() throws Throwable {
147 // Empty finalizer (for improved performance; no need to call
148 // super.finalize() in this case)
149 }
150 }
151
152 // Compress output and write as a series of 'IDAT' chunks of
153 // fixed length.
154 final class IDATOutputStream extends ImageOutputStreamImpl {
155
156 private static final byte[] chunkType = {
157 (byte)'I', (byte)'D', (byte)'A', (byte)'T'
158 };
159
160 private final ImageOutputStream stream;
161 private final int chunkLength;
162 private long startPos;
163 private final CRC crc = new CRC();
164
165 private final Deflater def;
166 private final byte[] buf = new byte[512];
167 // reused 1 byte[] array:
168 private final byte[] wbuf1 = new byte[1];
169
170 private int bytesRemaining;
171
172 IDATOutputStream(ImageOutputStream stream, int chunkLength,
173 int deflaterLevel) throws IOException
174 {
175 this.stream = stream;
176 this.chunkLength = chunkLength;
177 this.def = new Deflater(deflaterLevel);
178
179 startChunk();
180 }
181
182 private void startChunk() throws IOException {
183 crc.reset();
184 this.startPos = stream.getStreamPosition();
185 stream.writeInt(-1); // length, will backpatch
186
187 crc.update(chunkType, 0, 4);
188 stream.write(chunkType, 0, 4);
189
190 this.bytesRemaining = chunkLength;
191 }
192
193 private void finishChunk() throws IOException {
194 // Write CRC
195 stream.writeInt(crc.getValue());
196
197 // Write length
198 long pos = stream.getStreamPosition();
199 stream.seek(startPos);
200 stream.writeInt((int)(pos - startPos) - 12);
201
202 // Return to end of chunk and flush to minimize buffering
203 stream.seek(pos);
204 try {
205 stream.flushBefore(pos);
206 } catch (IOException e) {
207 /*
208 * If flushBefore() fails we try to access startPos in finally
209 * block of write_IDAT(). We should update startPos to avoid
210 * IndexOutOfBoundException while seek() is happening.
211 */
212 this.startPos = stream.getStreamPosition();
213 throw e;
214 }
215 }
216
217 @Override
218 public int read() throws IOException {
219 throw new RuntimeException("Method not available");
220 }
221
222 @Override
223 public int read(byte[] b, int off, int len) throws IOException {
224 throw new RuntimeException("Method not available");
225 }
226
227 @Override
228 public void write(byte[] b, int off, int len) throws IOException {
229 if (len == 0) {
230 return;
231 }
232
233 if (!def.finished()) {
234 def.setInput(b, off, len);
235 while (!def.needsInput()) {
236 deflate();
237 }
238 }
239 }
240
241 void deflate() throws IOException {
242 int len = def.deflate(buf, 0, buf.length);
243 int off = 0;
244
245 while (len > 0) {
246 if (bytesRemaining == 0) {
247 finishChunk();
248 startChunk();
249 }
250
251 int nbytes = Math.min(len, bytesRemaining);
252 crc.update(buf, off, nbytes);
253 stream.write(buf, off, nbytes);
254
255 off += nbytes;
256 len -= nbytes;
257 bytesRemaining -= nbytes;
258 }
259 }
260
261 @Override
262 public void write(int b) throws IOException {
263 wbuf1[0] = (byte)b;
264 write(wbuf1, 0, 1);
265 }
266
267 void finish() throws IOException {
268 try {
269 if (!def.finished()) {
270 def.finish();
271 while (!def.finished()) {
272 deflate();
273 }
274 }
275 finishChunk();
276 } finally {
277 def.end();
278 }
279 }
280
281 @Override
282 protected void finalize() throws Throwable {
283 // Empty finalizer (for improved performance; no need to call
284 // super.finalize() in this case)
285 }
286 }
287
288
289 final class PNGImageWriteParam extends ImageWriteParam {
290
291 /** Default quality level = 0.5 ie medium compression */
292 private static final float DEFAULT_QUALITY = 0.5f;
293
294 private static final String[] compressionNames = {"Deflate"};
295 private static final float[] qualityVals = { 0.00F, 0.30F, 0.75F, 1.00F };
296 private static final String[] qualityDescs = {
297 "High compression", // 0.00 -> 0.30
298 "Medium compression", // 0.30 -> 0.75
299 "Low compression" // 0.75 -> 1.00
300 };
301
302 PNGImageWriteParam(Locale locale) {
303 super();
304 this.canWriteProgressive = true;
305 this.locale = locale;
306 this.canWriteCompressed = true;
307 this.compressionTypes = compressionNames;
308 this.compressionType = compressionTypes[0];
309 this.compressionMode = MODE_DEFAULT;
310 this.compressionQuality = DEFAULT_QUALITY;
311 }
312
313 /**
314 * Removes any previous compression quality setting.
315 *
316 * <p> The default implementation resets the compression quality
317 * to <code>0.5F</code>.
318 *
319 * @exception IllegalStateException if the compression mode is not
320 * <code>MODE_EXPLICIT</code>.
321 */
322 @Override
323 public void unsetCompression() {
324 super.unsetCompression();
325 this.compressionType = compressionTypes[0];
326 this.compressionQuality = DEFAULT_QUALITY;
327 }
328
329 /**
330 * Returns <code>true</code> since the PNG plug-in only supports
331 * lossless compression.
332 *
333 * @return <code>true</code>.
334 */
335 @Override
336 public boolean isCompressionLossless() {
337 return true;
338 }
339
340 @Override
341 public String[] getCompressionQualityDescriptions() {
342 super.getCompressionQualityDescriptions();
343 return qualityDescs.clone();
344 }
345
346 @Override
347 public float[] getCompressionQualityValues() {
348 super.getCompressionQualityValues();
349 return qualityVals.clone();
350 }
351 }
352
353 /**
354 */
355 public final class PNGImageWriter extends ImageWriter {
356
357 /** Default compression level = 4 ie medium compression */
358 private static final int DEFAULT_COMPRESSION_LEVEL = 4;
359
360 ImageOutputStream stream = null;
361
362 // compression level
363 private int deflaterLevel;
364
365 PNGMetadata metadata = null;
366
367 // Factors from the ImageWriteParam
368 int sourceXOffset = 0;
369 int sourceYOffset = 0;
370 int sourceWidth = 0;
371 int sourceHeight = 0;
372 int[] sourceBands = null;
373 int periodX = 1;
374 int periodY = 1;
375
376 int numBands;
377 int bpp;
378
379 RowFilter rowFilter = new RowFilter();
380 byte[] prevRow = null;
381 byte[] currRow = null;
382 byte[][] filteredRows = null;
383
384 // Per-band scaling tables
390 // given by sampleSize, and an output bit depth given by scalingBitDepth.
391 //
392 int[] sampleSize = null; // Sample size per band, in bits
393 int scalingBitDepth = -1; // Output bit depth of the scaling tables
394
395 // Tables for 1, 2, 4, or 8 bit output
396 byte[][] scale = null; // 8 bit table
397 byte[] scale0 = null; // equivalent to scale[0]
398
399 // Tables for 16 bit output
400 byte[][] scaleh = null; // High bytes of output
401 byte[][] scalel = null; // Low bytes of output
402
403 int totalPixels; // Total number of pixels to be written by write_IDAT
404 int pixelsDone; // Running count of pixels written by write_IDAT
405
406 public PNGImageWriter(ImageWriterSpi originatingProvider) {
407 super(originatingProvider);
408 }
409
410 @Override
411 public void setOutput(Object output) {
412 super.setOutput(output);
413 if (output != null) {
414 if (!(output instanceof ImageOutputStream)) {
415 throw new IllegalArgumentException("output not an ImageOutputStream!");
416 }
417 this.stream = (ImageOutputStream)output;
418 } else {
419 this.stream = null;
420 }
421 }
422
423 @Override
424 public ImageWriteParam getDefaultWriteParam() {
425 return new PNGImageWriteParam(getLocale());
426 }
427
428 @Override
429 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
430 return null;
431 }
432
433 @Override
434 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
435 ImageWriteParam param) {
436 PNGMetadata m = new PNGMetadata();
437 m.initialize(imageType, imageType.getSampleModel().getNumBands());
438 return m;
439 }
440
441 @Override
442 public IIOMetadata convertStreamMetadata(IIOMetadata inData,
443 ImageWriteParam param) {
444 return null;
445 }
446
447 @Override
448 public IIOMetadata convertImageMetadata(IIOMetadata inData,
449 ImageTypeSpecifier imageType,
450 ImageWriteParam param) {
451 // TODO - deal with imageType
452 if (inData instanceof PNGMetadata) {
453 return (PNGMetadata)((PNGMetadata)inData).clone();
454 } else {
455 return new PNGMetadata(inData);
456 }
457 }
458
459 private void write_magic() throws IOException {
460 // Write signature
461 byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
462 stream.write(magic);
463 }
464
465 private void write_IHDR() throws IOException {
466 // Write IHDR chunk
467 ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);
995 os.write(filteredRows[filterType], bpp, bytesPerRow);
996
997 // Swap current and previous rows
998 byte[] swap = currRow;
999 currRow = prevRow;
1000 prevRow = swap;
1001
1002 pixelsDone += hpixels;
1003 processImageProgress(100.0F*pixelsDone/totalPixels);
1004
1005 // If write has been aborted, just return;
1006 // processWriteAborted will be called later
1007 if (abortRequested()) {
1008 return;
1009 }
1010 }
1011 }
1012
1013 // Use sourceXOffset, etc.
1014 private void write_IDAT(RenderedImage image) throws IOException {
1015 IDATOutputStream ios = new IDATOutputStream(stream, 32768,
1016 deflaterLevel);
1017 try {
1018 if (metadata.IHDR_interlaceMethod == 1) {
1019 for (int i = 0; i < 7; i++) {
1020 encodePass(ios, image,
1021 PNGImageReader.adam7XOffset[i],
1022 PNGImageReader.adam7YOffset[i],
1023 PNGImageReader.adam7XSubsampling[i],
1024 PNGImageReader.adam7YSubsampling[i]);
1025 if (abortRequested()) {
1026 break;
1027 }
1028 }
1029 } else {
1030 encodePass(ios, image, 0, 0, 1, 1);
1031 }
1032 } finally {
1033 ios.finish();
1034 }
1035 }
1036
1089 // Divide scaling table into high and low bytes
1090 scaleh = new byte[numBands][];
1091 scalel = new byte[numBands][];
1092
1093 for (int b = 0; b < numBands; b++) {
1094 int maxInSample = (1 << sampleSize[b]) - 1;
1095 int halfMaxInSample = maxInSample/2;
1096 scaleh[b] = new byte[maxInSample + 1];
1097 scalel[b] = new byte[maxInSample + 1];
1098 for (int s = 0; s <= maxInSample; s++) {
1099 int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
1100 scaleh[b][s] = (byte)(val >> 8);
1101 scalel[b][s] = (byte)(val & 0xff);
1102 }
1103 }
1104 scale = null;
1105 scale0 = null;
1106 }
1107 }
1108
1109 @Override
1110 public void write(IIOMetadata streamMetadata,
1111 IIOImage image,
1112 ImageWriteParam param) throws IIOException {
1113 if (stream == null) {
1114 throw new IllegalStateException("output == null!");
1115 }
1116 if (image == null) {
1117 throw new IllegalArgumentException("image == null!");
1118 }
1119 if (image.hasRaster()) {
1120 throw new UnsupportedOperationException("image has a Raster!");
1121 }
1122
1123 RenderedImage im = image.getRenderedImage();
1124 SampleModel sampleModel = im.getSampleModel();
1125 this.numBands = sampleModel.getNumBands();
1126
1127 // Set source region and subsampling to default values
1128 this.sourceXOffset = im.getMinX();
1129 this.sourceYOffset = im.getMinY();
1172 int destWidth = (sourceWidth + periodX - 1)/periodX;
1173 int destHeight = (sourceHeight + periodY - 1)/periodY;
1174 if (destWidth <= 0 || destHeight <= 0) {
1175 throw new IllegalArgumentException("Empty source region!");
1176 }
1177
1178 // Compute total number of pixels for progress notification
1179 this.totalPixels = destWidth*destHeight;
1180 this.pixelsDone = 0;
1181
1182 // Create metadata
1183 IIOMetadata imd = image.getMetadata();
1184 if (imd != null) {
1185 metadata = (PNGMetadata)convertImageMetadata(imd,
1186 ImageTypeSpecifier.createFromRenderedImage(im),
1187 null);
1188 } else {
1189 metadata = new PNGMetadata();
1190 }
1191
1192 // reset compression level to default:
1193 deflaterLevel = DEFAULT_COMPRESSION_LEVEL;
1194
1195 if (param != null) {
1196 switch(param.getCompressionMode()) {
1197 case ImageWriteParam.MODE_DISABLED:
1198 deflaterLevel = Deflater.NO_COMPRESSION;
1199 break;
1200 case ImageWriteParam.MODE_EXPLICIT:
1201 float quality = param.getCompressionQuality();
1202 if (quality >= 0f && quality <= 1f) {
1203 deflaterLevel = 9 - Math.round(9f * quality);
1204 }
1205 break;
1206 default:
1207 }
1208
1209 // Use Adam7 interlacing if set in write param
1210 switch (param.getProgressiveMode()) {
1211 case ImageWriteParam.MODE_DEFAULT:
1212 metadata.IHDR_interlaceMethod = 1;
1213 break;
1214 case ImageWriteParam.MODE_DISABLED:
1215 metadata.IHDR_interlaceMethod = 0;
1216 break;
1217 // MODE_COPY_FROM_METADATA should already be taken care of
1218 // MODE_EXPLICIT is not allowed
1219 default:
1220 }
1221 }
1222
1223 // Initialize bitDepth and colorType
1224 metadata.initialize(new ImageTypeSpecifier(im), numBands);
1225
1226 // Overwrite IHDR width and height values with values from image
1227 metadata.IHDR_width = destWidth;
1228 metadata.IHDR_height = destHeight;
1229
1230 this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
1231
1232 // Initialize scaling tables for this image
1233 initializeScaleTables(sampleModel.getSampleSize());
1234
1235 clearAbortRequest();
1236
1237 processImageStarted(0);
1238
1239 try {
|