KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > imageio > plugins > png > PNGImageWriter


1 /*
2  * @(#)PNGImageWriter.java 1.35 07/09/10
3  *
4  * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package com.sun.imageio.plugins.png;
9
10 import java.awt.Rectangle JavaDoc;
11 import java.awt.image.ColorModel JavaDoc;
12 import java.awt.image.IndexColorModel JavaDoc;
13 import java.awt.image.Raster JavaDoc;
14 import java.awt.image.WritableRaster JavaDoc;
15 import java.awt.image.RenderedImage JavaDoc;
16 import java.awt.image.SampleModel JavaDoc;
17 import java.io.ByteArrayOutputStream JavaDoc;
18 import java.io.DataOutput JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.OutputStream JavaDoc;
21 import java.util.Iterator JavaDoc;
22 import java.util.Locale JavaDoc;
23 import java.util.zip.Deflater JavaDoc;
24 import java.util.zip.DeflaterOutputStream JavaDoc;
25 import javax.imageio.IIOException JavaDoc;
26 import javax.imageio.IIOImage JavaDoc;
27 import javax.imageio.ImageTypeSpecifier JavaDoc;
28 import javax.imageio.ImageWriteParam JavaDoc;
29 import javax.imageio.ImageWriter JavaDoc;
30 import javax.imageio.metadata.IIOMetadata JavaDoc;
31 import javax.imageio.metadata.IIOMetadata JavaDoc;
32 import javax.imageio.spi.ImageWriterSpi JavaDoc;
33 import javax.imageio.stream.ImageOutputStream JavaDoc;
34 import javax.imageio.stream.ImageOutputStreamImpl JavaDoc;
35
36 class CRC {
37
38     private static int[] crcTable = new int[256];
39     private int crc = 0xffffffff;
40
41     static {
42         // Initialize CRC table
43
for (int n = 0; n < 256; n++) {
44             int c = n;
45             for (int k = 0; k < 8; k++) {
46                 if ((c & 1) == 1) {
47                     c = 0xedb88320 ^ (c >>> 1);
48                 } else {
49                     c >>>= 1;
50                 }
51
52                 crcTable[n] = c;
53             }
54         }
55     }
56
57     public CRC() {}
58
59     public void reset() {
60         crc = 0xffffffff;
61     }
62
63     public void update(byte[] data, int off, int len) {
64         for (int n = 0; n < len; n++) {
65             crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8);
66         }
67     }
68
69     public void update(int data) {
70         crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
71     }
72
73     public int getValue() {
74         return crc ^ 0xffffffff;
75     }
76 }
77
78
79 class ChunkStream extends ImageOutputStreamImpl JavaDoc {
80     
81     private ImageOutputStream JavaDoc stream;
82     private long startPos;
83     private CRC crc = new CRC();
84
85     public ChunkStream(int type, ImageOutputStream JavaDoc stream) throws IOException JavaDoc {
86         this.stream = stream;
87         this.startPos = stream.getStreamPosition();
88         
89         stream.writeInt(-1); // length, will backpatch
90
writeInt(type);
91     }
92
93     public int read() throws IOException JavaDoc {
94         throw new RuntimeException JavaDoc("Method not available");
95     }
96
97     public int read(byte[] b, int off, int len) throws IOException JavaDoc {
98         throw new RuntimeException JavaDoc("Method not available");
99     }
100
101     public void write(byte[] b, int off, int len) throws IOException JavaDoc {
102         crc.update(b, off, len);
103         stream.write(b, off, len);
104     }
105
106     public void write(int b) throws IOException JavaDoc {
107         crc.update(b);
108         stream.write(b);
109     }
110
111     public void finish() throws IOException JavaDoc {
112         // Write CRC
113
stream.writeInt(crc.getValue());
114
115         // Write length
116
long pos = stream.getStreamPosition();
117         stream.seek(startPos);
118         stream.writeInt((int)(pos - startPos) - 12);
119
120         // Return to end of chunk and flush to minimize buffering
121
stream.seek(pos);
122         stream.flushBefore(pos);
123     }
124 }
125
126 // Compress output and write as a series of 'IDAT' chunks of
127
// fixed length.
128
class IDATOutputStream extends ImageOutputStreamImpl JavaDoc {
129     
130     private static byte[] chunkType = {
131         (byte)'I', (byte)'D', (byte)'A', (byte)'T'
132     };
133
134     private ImageOutputStream JavaDoc stream;
135     private int chunkLength;
136     private long startPos;
137     private CRC crc = new CRC();
138
139     Deflater JavaDoc def = new Deflater JavaDoc(Deflater.BEST_COMPRESSION);
140     byte[] buf = new byte[512];
141
142     private int bytesRemaining;
143
144     public IDATOutputStream(ImageOutputStream JavaDoc stream, int chunkLength)
145         throws IOException JavaDoc {
146         this.stream = stream;
147         this.chunkLength = chunkLength;
148         startChunk();
149     }
150
151     private void startChunk() throws IOException JavaDoc {
152         crc.reset();
153         this.startPos = stream.getStreamPosition();
154         stream.writeInt(-1); // length, will backpatch
155

156         crc.update(chunkType, 0, 4);
157         stream.write(chunkType, 0, 4);
158
159         this.bytesRemaining = chunkLength;
160     }
161
162     private void finishChunk() throws IOException JavaDoc {
163         // Write CRC
164
stream.writeInt(crc.getValue());
165
166         // Write length
167
long pos = stream.getStreamPosition();
168         stream.seek(startPos);
169         stream.writeInt((int)(pos - startPos) - 12);
170
171         // Return to end of chunk and flush to minimize buffering
172
stream.seek(pos);
173         stream.flushBefore(pos);
174     }
175
176     public int read() throws IOException JavaDoc {
177         throw new RuntimeException JavaDoc("Method not available");
178     }
179
180     public int read(byte[] b, int off, int len) throws IOException JavaDoc {
181         throw new RuntimeException JavaDoc("Method not available");
182     }
183
184     public void write(byte[] b, int off, int len) throws IOException JavaDoc {
185         if (len == 0) {
186             return;
187         }
188
189     if (!def.finished()) {
190         def.setInput(b, off, len);
191         while (!def.needsInput()) {
192         deflate();
193         }
194     }
195     }
196
197     public void deflate() throws IOException JavaDoc {
198     int len = def.deflate(buf, 0, buf.length);
199         int off = 0;
200
201         while (len > 0) {
202             if (bytesRemaining == 0) {
203                 finishChunk();
204                 startChunk();
205             }
206
207             int nbytes = Math.min(len, bytesRemaining);
208             crc.update(buf, off, nbytes);
209             stream.write(buf, off, nbytes);
210
211             off += nbytes;
212             len -= nbytes;
213             bytesRemaining -= nbytes;
214         }
215     }
216
217     public void write(int b) throws IOException JavaDoc {
218         byte[] wbuf = new byte[1];
219         wbuf[0] = (byte)b;
220         write(wbuf, 0, 1);
221     }
222
223     public void finish() throws IOException JavaDoc {
224     if (!def.finished()) {
225         def.finish();
226         while (!def.finished()) {
227         deflate();
228         }
229     }
230         finishChunk();
231     }
232 }
233
234
235 class PNGImageWriteParam extends ImageWriteParam JavaDoc {
236     
237     public PNGImageWriteParam(Locale JavaDoc locale) {
238         super();
239         this.canWriteProgressive = true;
240         this.locale = locale;
241     }
242 }
243
244 /**
245  * @version 0.5
246  */

247 public class PNGImageWriter extends ImageWriter JavaDoc {
248
249     ImageOutputStream JavaDoc stream = null;
250
251     PNGMetadata metadata = null;
252
253     // Factors from the ImageWriteParam
254
int sourceXOffset = 0;
255     int sourceYOffset = 0;
256     int sourceWidth = 0;
257     int sourceHeight = 0;
258     int[] sourceBands = null;
259     int periodX = 1;
260     int periodY = 1;
261
262     int numBands;
263     int bpp;
264
265     RowFilter rowFilter = new RowFilter();
266     byte[] prevRow = null;
267     byte[] currRow = null;
268     byte[][] filteredRows = null;
269
270     // Per-band scaling tables
271
//
272
// After the first call to initializeScaleTables, either scale and scale0
273
// will be valid, or scaleh and scalel will be valid, but not both.
274
//
275
// The tables will be designed for use with a set of input but depths
276
// given by sampleSize, and an output bit depth given by scalingBitDepth.
277
//
278
int[] sampleSize = null; // Sample size per band, in bits
279
int scalingBitDepth = -1; // Output bit depth of the scaling tables
280

281     // Tables for 1, 2, 4, or 8 bit output
282
byte[][] scale = null; // 8 bit table
283
byte[] scale0 = null; // equivalent to scale[0]
284

285     // Tables for 16 bit output
286
byte[][] scaleh = null; // High bytes of output
287
byte[][] scalel = null; // Low bytes of output
288

289     int totalPixels; // Total number of pixels to be written by write_IDAT
290
int pixelsDone; // Running count of pixels written by write_IDAT
291

292     public PNGImageWriter(ImageWriterSpi JavaDoc originatingProvider) {
293         super(originatingProvider);
294     }
295  
296     public void setOutput(Object JavaDoc output) {
297         super.setOutput(output);
298         if (output != null) {
299             if (!(output instanceof ImageOutputStream JavaDoc)) {
300                 throw new IllegalArgumentException JavaDoc("output not an ImageOutputStream!");
301             }
302             this.stream = (ImageOutputStream JavaDoc)output;
303         } else {
304             this.stream = null;
305         }
306     }
307
308     private static int[] allowedProgressivePasses = { 1, 7 };
309
310     public ImageWriteParam JavaDoc getDefaultWriteParam() {
311         return new PNGImageWriteParam(getLocale());
312     }
313
314     public IIOMetadata JavaDoc getDefaultStreamMetadata(ImageWriteParam JavaDoc param) {
315         return null;
316     }
317
318     public IIOMetadata JavaDoc getDefaultImageMetadata(ImageTypeSpecifier JavaDoc imageType,
319                                                ImageWriteParam JavaDoc param) {
320         PNGMetadata m = new PNGMetadata();
321         m.initialize(imageType, imageType.getSampleModel().getNumBands());
322         return m;
323     }
324
325     public IIOMetadata JavaDoc convertStreamMetadata(IIOMetadata JavaDoc inData,
326                                              ImageWriteParam JavaDoc param) {
327         return null;
328     }
329
330     public IIOMetadata JavaDoc convertImageMetadata(IIOMetadata JavaDoc inData,
331                                             ImageTypeSpecifier JavaDoc imageType,
332                                             ImageWriteParam JavaDoc param) {
333         // TODO - deal with imageType
334
if (inData instanceof PNGMetadata) {
335             return (PNGMetadata)((PNGMetadata)inData).clone();
336         } else {
337             return new PNGMetadata(inData);
338         }
339     }
340
341     private void write_magic() throws IOException JavaDoc {
342         // Write signature
343
byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
344         stream.write(magic);
345     }
346
347     private void write_IHDR() throws IOException JavaDoc {
348         // Write IHDR chunk
349
ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);
350         cs.writeInt(metadata.IHDR_width);
351         cs.writeInt(metadata.IHDR_height);
352         cs.writeByte(metadata.IHDR_bitDepth);
353         cs.writeByte(metadata.IHDR_colorType);
354         if (metadata.IHDR_compressionMethod != 0) {
355             throw new IIOException JavaDoc(
356 "Only compression method 0 is defined in PNG 1.1");
357         }
358         cs.writeByte(metadata.IHDR_compressionMethod);
359         if (metadata.IHDR_filterMethod != 0) {
360             throw new IIOException JavaDoc(
361 "Only filter method 0 is defined in PNG 1.1");
362         }
363         cs.writeByte(metadata.IHDR_filterMethod);
364         if (metadata.IHDR_interlaceMethod < 0 ||
365             metadata.IHDR_interlaceMethod > 1) {
366             throw new IIOException JavaDoc(
367 "Only interlace methods 0 (node) and 1 (adam7) are defined in PNG 1.1");
368         }
369         cs.writeByte(metadata.IHDR_interlaceMethod);
370         cs.finish();
371     }
372
373     private void write_cHRM() throws IOException JavaDoc {
374         if (metadata.cHRM_present) {
375             ChunkStream cs = new ChunkStream(PNGImageReader.cHRM_TYPE, stream);
376             cs.writeInt(metadata.cHRM_whitePointX);
377             cs.writeInt(metadata.cHRM_whitePointY);
378             cs.writeInt(metadata.cHRM_redX);
379             cs.writeInt(metadata.cHRM_redY);
380             cs.writeInt(metadata.cHRM_greenX);
381             cs.writeInt(metadata.cHRM_greenY);
382             cs.writeInt(metadata.cHRM_blueX);
383             cs.writeInt(metadata.cHRM_blueY);
384             cs.finish();
385         }
386     }
387
388     private void write_gAMA() throws IOException JavaDoc {
389         if (metadata.gAMA_present) {
390             ChunkStream cs = new ChunkStream(PNGImageReader.gAMA_TYPE, stream);
391             cs.writeInt(metadata.gAMA_gamma);
392             cs.finish();
393         }
394     }
395
396     private void write_iCCP() throws IOException JavaDoc {
397         if (metadata.iCCP_present) {
398             ChunkStream cs = new ChunkStream(PNGImageReader.iCCP_TYPE, stream);
399             cs.writeBytes(metadata.iCCP_profileName);
400             cs.writeByte(0); // null terminator
401

402             cs.writeByte(metadata.iCCP_compressionMethod);
403             cs.write(metadata.iCCP_compressedProfile);
404             cs.finish();
405         }
406     }
407
408     private void write_sBIT() throws IOException JavaDoc {
409         if (metadata.sBIT_present) {
410             ChunkStream cs = new ChunkStream(PNGImageReader.sBIT_TYPE, stream);
411             int colorType = metadata.IHDR_colorType;
412             if (metadata.sBIT_colorType != colorType) {
413                 processWarningOccurred(0,
414 "sBIT metadata has wrong color type.\n" +
415 "The chunk will not be written.");
416                 return;
417             }
418
419             if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
420                 colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
421                 cs.writeByte(metadata.sBIT_grayBits);
422             } else if (colorType == PNGImageReader.PNG_COLOR_RGB ||
423                        colorType == PNGImageReader.PNG_COLOR_PALETTE ||
424                        colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
425                 cs.writeByte(metadata.sBIT_redBits);
426                 cs.writeByte(metadata.sBIT_greenBits);
427                 cs.writeByte(metadata.sBIT_blueBits);
428             }
429
430             if (colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
431                 colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
432                 cs.writeByte(metadata.sBIT_alphaBits);
433             }
434             cs.finish();
435         }
436     }
437
438     private void write_sRGB() throws IOException JavaDoc {
439         if (metadata.sRGB_present) {
440             ChunkStream cs = new ChunkStream(PNGImageReader.sRGB_TYPE, stream);
441             cs.writeByte(metadata.sRGB_renderingIntent);
442             cs.finish();
443         }
444     }
445
446     private void write_PLTE() throws IOException JavaDoc {
447         if (metadata.PLTE_present) {
448             if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY ||
449               metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
450                 // PLTE cannot occur in a gray image
451

452                 processWarningOccurred(0,
453 "A PLTE chunk may not appear in a gray or gray alpha image.\n" +
454 "The chunk will not be written");
455                 return;
456             }
457
458             ChunkStream cs = new ChunkStream(PNGImageReader.PLTE_TYPE, stream);
459
460             int numEntries = metadata.PLTE_red.length;
461             byte[] palette = new byte[numEntries*3];
462             int index = 0;
463             for (int i = 0; i < numEntries; i++) {
464                 palette[index++] = metadata.PLTE_red[i];
465                 palette[index++] = metadata.PLTE_green[i];
466                 palette[index++] = metadata.PLTE_blue[i];
467             }
468
469             cs.write(palette);
470             cs.finish();
471         }
472     }
473
474     private void write_hIST() throws IOException JavaDoc, IIOException JavaDoc {
475         if (metadata.hIST_present) {
476             ChunkStream cs = new ChunkStream(PNGImageReader.hIST_TYPE, stream);
477
478             if (!metadata.PLTE_present) {
479                 throw new IIOException JavaDoc("hIST chunk without PLTE chunk!");
480             }
481             
482             cs.writeChars(metadata.hIST_histogram,
483                           0, metadata.hIST_histogram.length);
484             cs.finish();
485         }
486     }
487
488     private void write_tRNS() throws IOException JavaDoc, IIOException JavaDoc {
489         if (metadata.tRNS_present) {
490             ChunkStream cs = new ChunkStream(PNGImageReader.tRNS_TYPE, stream);
491             int colorType = metadata.IHDR_colorType;
492             int chunkType = metadata.tRNS_colorType;
493
494             // Special case: image is RGB and chunk is Gray
495
// Promote chunk contents to RGB
496
int chunkRed = metadata.tRNS_red;
497             int chunkGreen = metadata.tRNS_green;
498             int chunkBlue = metadata.tRNS_blue;
499             if (colorType == PNGImageReader.PNG_COLOR_RGB &&
500                 chunkType == PNGImageReader.PNG_COLOR_GRAY) {
501                 chunkType = colorType;
502                 chunkRed = chunkGreen = chunkBlue =
503                     metadata.tRNS_gray;
504             }
505
506             if (chunkType != colorType) {
507                 processWarningOccurred(0,
508 "tRNS metadata has incompatible color type.\n" +
509 "The chunk will not be written.");
510                 return;
511             }
512
513             if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
514                 if (!metadata.PLTE_present) {
515                     throw new IIOException JavaDoc("tRNS chunk without PLTE chunk!");
516                 }
517                 cs.write(metadata.tRNS_alpha);
518             } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) {
519                 cs.writeShort(metadata.tRNS_gray);
520             } else if (colorType == PNGImageReader.PNG_COLOR_RGB) {
521                 cs.writeShort(chunkRed);
522                 cs.writeShort(chunkGreen);
523                 cs.writeShort(chunkBlue);
524             } else {
525                 throw new IIOException JavaDoc("tRNS chunk for color type 4 or 6!");
526             }
527             cs.finish();
528         }
529     }
530
531     private void write_bKGD() throws IOException JavaDoc {
532         if (metadata.bKGD_present) {
533             ChunkStream cs = new ChunkStream(PNGImageReader.bKGD_TYPE, stream);
534             int colorType = metadata.IHDR_colorType & 0x3;
535             int chunkType = metadata.bKGD_colorType;
536
537             // Special case: image is RGB(A) and chunk is Gray
538
// Promote chunk contents to RGB
539
int chunkRed = metadata.bKGD_red;
540             int chunkGreen = metadata.bKGD_red;
541             int chunkBlue = metadata.bKGD_red;
542             if (colorType == PNGImageReader.PNG_COLOR_RGB &&
543                 chunkType == PNGImageReader.PNG_COLOR_GRAY) {
544                 // Make a gray bKGD chunk look like RGB
545
chunkType = colorType;
546                 chunkRed = chunkGreen = chunkBlue =
547                     metadata.bKGD_gray;
548             }
549
550             // Ignore status of alpha in colorType
551
if (chunkType != colorType) {
552                 processWarningOccurred(0,
553 "bKGD metadata has incompatible color type.\n" +
554 "The chunk will not be written.");
555                 return;
556             }
557
558             if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
559                 cs.writeByte(metadata.bKGD_index);
560             } else if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
561                        colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
562                 cs.writeShort(metadata.bKGD_gray);
563             } else { // colorType == PNGImageReader.PNG_COLOR_RGB ||
564
// colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
565
cs.writeShort(chunkRed);
566                 cs.writeShort(chunkGreen);
567                 cs.writeShort(chunkBlue);
568             }
569             cs.finish();
570         }
571     }
572
573     private void write_pHYs() throws IOException JavaDoc {
574         if (metadata.pHYs_present) {
575             ChunkStream cs = new ChunkStream(PNGImageReader.pHYs_TYPE, stream);
576             cs.writeInt(metadata.pHYs_pixelsPerUnitXAxis);
577             cs.writeInt(metadata.pHYs_pixelsPerUnitYAxis);
578             cs.writeByte(metadata.pHYs_unitSpecifier);
579             cs.finish();
580         }
581     }
582
583     private void write_sPLT() throws IOException JavaDoc {
584         if (metadata.sPLT_present) {
585             ChunkStream cs = new ChunkStream(PNGImageReader.sPLT_TYPE, stream);
586
587             cs.writeBytes(metadata.sPLT_paletteName);
588             cs.writeByte(0); // null terminator
589

590             cs.writeByte(metadata.sPLT_sampleDepth);
591             int numEntries = metadata.sPLT_red.length;
592
593             if (metadata.sPLT_sampleDepth == 8) {
594                 for (int i = 0; i < numEntries; i++) {
595                     cs.writeByte(metadata.sPLT_red[i]);
596                     cs.writeByte(metadata.sPLT_green[i]);
597                     cs.writeByte(metadata.sPLT_blue[i]);
598                     cs.writeByte(metadata.sPLT_alpha[i]);
599                     cs.writeShort(metadata.sPLT_frequency[i]);
600                 }
601             } else { // sampleDepth == 16
602
for (int i = 0; i < numEntries; i++) {
603                     cs.writeShort(metadata.sPLT_red[i]);
604                     cs.writeShort(metadata.sPLT_green[i]);
605                     cs.writeShort(metadata.sPLT_blue[i]);
606                     cs.writeShort(metadata.sPLT_alpha[i]);
607                     cs.writeShort(metadata.sPLT_frequency[i]);
608                 }
609             }
610             cs.finish();
611         }
612     }
613
614     private void write_tIME() throws IOException JavaDoc {
615         if (metadata.tIME_present) {
616             ChunkStream cs = new ChunkStream(PNGImageReader.tIME_TYPE, stream);
617             cs.writeShort(metadata.tIME_year);
618             cs.writeByte(metadata.tIME_month);
619             cs.writeByte(metadata.tIME_day);
620             cs.writeByte(metadata.tIME_hour);
621             cs.writeByte(metadata.tIME_minute);
622             cs.writeByte(metadata.tIME_second);
623             cs.finish();
624         }
625     }
626
627     private void write_tEXt() throws IOException JavaDoc {
628         Iterator JavaDoc keywordIter = metadata.tEXt_keyword.iterator();
629         Iterator JavaDoc textIter = metadata.tEXt_text.iterator();
630
631         while (keywordIter.hasNext()) {
632             ChunkStream cs = new ChunkStream(PNGImageReader.tEXt_TYPE, stream);
633             String JavaDoc keyword = (String JavaDoc)keywordIter.next();
634             cs.writeBytes(keyword);
635             cs.writeByte(0);
636
637             String JavaDoc text = (String JavaDoc)textIter.next();
638             cs.writeBytes(text);
639             cs.finish();
640         }
641     }
642
643     private byte[] deflate(String JavaDoc s) throws IOException JavaDoc {
644         ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
645         DeflaterOutputStream JavaDoc dos = new DeflaterOutputStream JavaDoc(baos);
646
647         int len = s.length();
648         for (int i = 0; i < len; i++) {
649             dos.write((int)s.charAt(i));
650         }
651         dos.close();
652         
653         return baos.toByteArray();
654     }
655
656     private void write_iTXt() throws IOException JavaDoc {
657         Iterator JavaDoc keywordIter = metadata.iTXt_keyword.iterator();
658         Iterator JavaDoc flagIter = metadata.iTXt_compressionFlag.iterator();
659         Iterator JavaDoc methodIter = metadata.iTXt_compressionMethod.iterator();
660         Iterator JavaDoc languageIter = metadata.iTXt_languageTag.iterator();
661         Iterator JavaDoc translatedKeywordIter =
662             metadata.iTXt_translatedKeyword.iterator();
663         Iterator JavaDoc textIter = metadata.iTXt_text.iterator();
664
665         while (keywordIter.hasNext()) {
666             ChunkStream cs = new ChunkStream(PNGImageReader.iTXt_TYPE, stream);
667             String JavaDoc keyword = (String JavaDoc)keywordIter.next();
668             cs.writeBytes(keyword);
669             cs.writeByte(0);
670
671             int flag = ((Integer JavaDoc)flagIter.next()).intValue();
672             cs.writeByte(flag);
673             int method = ((Integer JavaDoc)methodIter.next()).intValue();
674             cs.writeByte(method);
675
676             String JavaDoc languageTag = (String JavaDoc)languageIter.next();
677             cs.writeBytes(languageTag);
678             cs.writeByte(0);
679
680             String JavaDoc translatedKeyword = (String JavaDoc)translatedKeywordIter.next();
681             cs.writeBytes(translatedKeyword);
682             cs.writeByte(0);
683
684             String JavaDoc text = (String JavaDoc)textIter.next();
685             if (flag == 1) {
686                 cs.write(deflate(text));
687             } else {
688                 cs.writeUTF(text);
689             }
690             cs.finish();
691         }
692     }
693
694     private void write_zTXt() throws IOException JavaDoc {
695         Iterator JavaDoc keywordIter = metadata.zTXt_keyword.iterator();
696         Iterator JavaDoc methodIter = metadata.zTXt_compressionMethod.iterator();
697         Iterator JavaDoc textIter = metadata.zTXt_text.iterator();
698
699         while (keywordIter.hasNext()) {
700             ChunkStream cs = new ChunkStream(PNGImageReader.zTXt_TYPE, stream);
701             String JavaDoc keyword = (String JavaDoc)keywordIter.next();
702             cs.writeBytes(keyword);
703             cs.writeByte(0);
704
705             int compressionMethod = ((Integer JavaDoc)methodIter.next()).intValue();
706             cs.writeByte(compressionMethod);
707
708             String JavaDoc text = (String JavaDoc)textIter.next();
709             cs.write(deflate(text));
710             cs.finish();
711         }
712     }
713
714     private void writeUnknownChunks() throws IOException JavaDoc {
715         Iterator JavaDoc typeIter = metadata.unknownChunkType.iterator();
716         Iterator JavaDoc dataIter = metadata.unknownChunkData.iterator();
717
718         while (typeIter.hasNext() && dataIter.hasNext()) {
719             String JavaDoc type = (String JavaDoc)typeIter.next();
720             ChunkStream cs = new ChunkStream(PNGImageReader.chunkType(type),
721                                              stream);
722             byte[] data = (byte[])dataIter.next();
723             cs.write(data);
724             cs.finish();
725         }
726     }
727
728     private void encodePass(ImageOutputStream JavaDoc os,
729                             RenderedImage JavaDoc image,
730                             int xOffset, int yOffset,
731                             int xSkip, int ySkip) throws IOException JavaDoc {
732         int minX = sourceXOffset;
733         int minY = sourceYOffset;
734         int width = sourceWidth;
735         int height = sourceHeight;
736         
737         // Adjust offsets and skips based on source subsampling factors
738
xOffset *= periodX;
739         xSkip *= periodX;
740         yOffset *= periodY;
741         ySkip *= periodY;
742
743         // Early exit if no data for this pass
744
int hpixels = (width - xOffset + xSkip - 1)/xSkip;
745         int vpixels = (height - yOffset + ySkip - 1)/ySkip;
746         if (hpixels == 0 || vpixels == 0) {
747             return;
748         }
749
750         // Convert X offset and skip from pixels to samples
751
xOffset *= numBands;
752         xSkip *= numBands;
753
754         // Create row buffers
755
int samplesPerByte = 8/metadata.IHDR_bitDepth;
756         int numSamples = width*numBands;
757         int[] samples = new int[numSamples];
758
759         int bytesPerRow = hpixels*numBands;
760         if (metadata.IHDR_bitDepth < 8) {
761             bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte;
762         } else if (metadata.IHDR_bitDepth == 16) {
763             bytesPerRow *= 2;
764         }
765
766         IndexColorModel JavaDoc icm_gray_alpha = null;
767         if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA &&
768             image.getColorModel() instanceof IndexColorModel JavaDoc)
769         {
770             // reserve space for alpha samples
771
bytesPerRow *= 2;
772             
773             // will be used to calculate alpha value for the pixel
774
icm_gray_alpha = (IndexColorModel JavaDoc)image.getColorModel();
775         }
776
777         currRow = new byte[bytesPerRow + bpp];
778         prevRow = new byte[bytesPerRow + bpp];
779         filteredRows = new byte[5][bytesPerRow + bpp];
780
781     int bitDepth = metadata.IHDR_bitDepth;
782         for (int row = minY + yOffset; row < minY + height; row += ySkip) {
783             Rectangle JavaDoc rect = new Rectangle JavaDoc(minX, row, width, 1);
784             Raster JavaDoc ras = image.getData(rect);
785             if (sourceBands != null) {
786                 ras = ras.createChild(minX, row, width, 1, minX, row,
787                                       sourceBands);
788             }
789
790             ras.getPixels(minX, row, width, 1, samples);
791
792             if (image.getColorModel().isAlphaPremultiplied()) {
793                 WritableRaster JavaDoc wr = ras.createCompatibleWritableRaster();
794                 wr.setPixels(wr.getMinX(), wr.getMinY(),
795                              wr.getWidth(), wr.getHeight(),
796                              samples);
797
798                 image.getColorModel().coerceData(wr, false);
799                 wr.getPixels(wr.getMinX(), wr.getMinY(),
800                              wr.getWidth(), wr.getHeight(),
801                              samples);
802             }
803
804             // Reorder palette data if necessary
805
int[] paletteOrder = metadata.PLTE_order;
806             if (paletteOrder != null) {
807                 for (int i = 0; i < numSamples; i++) {
808                     samples[i] = paletteOrder[samples[i]];
809                 }
810             }
811
812             int count = bpp; // leave first 'bpp' bytes zero
813
int pos = 0;
814             int tmp = 0;
815
816             switch (bitDepth) {
817             case 1: case 2: case 4:
818                 // Image can only have a single band
819

820                 int mask = samplesPerByte - 1;
821                 for (int s = xOffset; s < numSamples; s += xSkip) {
822             byte val = scale0[samples[s]];
823                     tmp = (tmp << bitDepth) | val;
824
825                     if ((pos++ & mask) == mask) {
826                         currRow[count++] = (byte)tmp;
827                         tmp = 0;
828                         pos = 0;
829                     }
830                 }
831
832                 // Left shift the last byte
833
if ((pos & mask) != 0) {
834                     tmp <<= ((8/bitDepth) - pos)*bitDepth;
835                     currRow[count++] = (byte)tmp;
836                 }
837                 break;
838
839             case 8:
840         if (numBands == 1) {
841             for (int s = xOffset; s < numSamples; s += xSkip) {
842             currRow[count++] = scale0[samples[s]];
843             if (icm_gray_alpha != null) {
844                             currRow[count++] =
845                                 scale0[icm_gray_alpha.getAlpha(0xff & samples[s])];
846                         }
847             }
848         } else {
849             for (int s = xOffset; s < numSamples; s += xSkip) {
850             for (int b = 0; b < numBands; b++) {
851                 currRow[count++] = scale[b][samples[s + b]];
852             }
853             }
854         }
855                 break;
856
857             case 16:
858                 for (int s = xOffset; s < numSamples; s += xSkip) {
859                     for (int b = 0; b < numBands; b++) {
860                         currRow[count++] = scaleh[b][samples[s + b]];
861                         currRow[count++] = scalel[b][samples[s + b]];
862                     }
863                 }
864                 break;
865             }
866
867             // Perform filtering
868
int filterType = rowFilter.filterRow(metadata.IHDR_colorType,
869                                                  currRow, prevRow,
870                                                  filteredRows,
871                                                  bytesPerRow, bpp);
872             
873             os.write(filterType);
874             os.write(filteredRows[filterType], bpp, bytesPerRow);
875             
876             // Swap current and previous rows
877
byte[] swap = currRow;
878             currRow = prevRow;
879             prevRow = swap;
880
881             pixelsDone += hpixels;
882             processImageProgress(100.0F*pixelsDone/totalPixels);
883
884             // If write has been aborted, just return;
885
// processWriteAborted will be called later
886
if (abortRequested()) {
887                 return;
888             }
889         }
890     }
891
892     // Use sourceXOffset, etc.
893
private void write_IDAT(RenderedImage JavaDoc image) throws IOException JavaDoc {
894         IDATOutputStream ios = new IDATOutputStream(stream, 32768);
895
896         if (metadata.IHDR_interlaceMethod == 1) {
897             for (int i = 0; i < 7; i++) {
898                 encodePass(ios, image,
899                            PNGImageReader.adam7XOffset[i],
900                            PNGImageReader.adam7YOffset[i],
901                            PNGImageReader.adam7XSubsampling[i],
902                            PNGImageReader.adam7YSubsampling[i]);
903                 if (abortRequested()) {
904                     break;
905                 }
906             }
907         } else {
908             encodePass(ios, image, 0, 0, 1, 1);
909         }
910
911         ios.finish();
912     }
913
914     private void writeIEND() throws IOException JavaDoc {
915         ChunkStream cs = new ChunkStream(PNGImageReader.IEND_TYPE, stream);
916         cs.finish();
917     }
918
919     // Check two int arrays for value equality, always returns false
920
// if either array is null
921
private boolean equals(int[] s0, int[] s1) {
922     if (s0 == null || s1 == null) {
923         return false;
924     }
925     if (s0.length != s1.length) {
926         return false;
927     }
928     for (int i = 0; i < s0.length; i++) {
929         if (s0[i] != s1[i]) {
930         return false;
931         }
932     }
933     return true;
934     }
935
936     // Initialize the scale/scale0 or scaleh/scalel arrays to
937
// hold the results of scaling an input value to the desired
938
// output bit depth
939
private void initializeScaleTables(int[] sampleSize) {
940     int bitDepth = metadata.IHDR_bitDepth;
941
942     // If the existing tables are still valid, just return
943
if (bitDepth == scalingBitDepth &&
944         equals(sampleSize, this.sampleSize)) {
945         return;
946     }
947     
948     // Compute new tables
949
this.sampleSize = sampleSize;
950     this.scalingBitDepth = bitDepth;
951     int maxOutSample = (1 << bitDepth) - 1;
952     if (bitDepth <= 8) {
953         scale = new byte[numBands][];
954         for (int b = 0; b < numBands; b++) {
955         int maxInSample = (1 << sampleSize[b]) - 1;
956         int halfMaxInSample = maxInSample/2;
957         scale[b] = new byte[maxInSample + 1];
958         for (int s = 0; s <= maxInSample; s++) {
959             scale[b][s] =
960             (byte)((s*maxOutSample + halfMaxInSample)/maxInSample);
961         }
962         }
963         scale0 = scale[0];
964         scaleh = scalel = null;
965     } else { // bitDepth == 16
966
// Divide scaling table into high and low bytes
967
scaleh = new byte[numBands][];
968         scalel = new byte[numBands][];
969
970         for (int b = 0; b < numBands; b++) {
971         int maxInSample = (1 << sampleSize[b]) - 1;
972         int halfMaxInSample = maxInSample/2;
973         scaleh[b] = new byte[maxInSample + 1];
974         scalel[b] = new byte[maxInSample + 1];
975         for (int s = 0; s <= maxInSample; s++) {
976             int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
977             scaleh[b][s] = (byte)(val >> 8);
978             scalel[b][s] = (byte)(val & 0xff);
979         }
980         }
981         scale = null;
982         scale0 = null;
983     }
984     }
985
986     public void write(IIOMetadata JavaDoc streamMetadata,
987                       IIOImage JavaDoc image,
988                       ImageWriteParam JavaDoc param) throws IIOException JavaDoc {
989         if (stream == null) {
990             throw new IllegalStateException JavaDoc("output == null!");
991         }
992         if (image == null) {
993             throw new IllegalArgumentException JavaDoc("image == null!");
994         }
995         if (image.hasRaster()) {
996             throw new UnsupportedOperationException JavaDoc("image has a Raster!");
997         }
998
999         RenderedImage JavaDoc im = image.getRenderedImage();
1000        SampleModel JavaDoc sampleModel = im.getSampleModel();
1001        this.numBands = sampleModel.getNumBands();
1002
1003        // Set source region and subsampling to default values
1004
this.sourceXOffset = im.getMinX();
1005        this.sourceYOffset = im.getMinY();
1006        this.sourceWidth = im.getWidth();
1007        this.sourceHeight = im.getHeight();
1008        this.sourceBands = null;
1009        this.periodX = 1;
1010        this.periodY = 1;
1011
1012        if (param != null) {
1013            // Get source region and subsampling factors
1014
Rectangle JavaDoc sourceRegion = param.getSourceRegion();
1015            if (sourceRegion != null) {
1016                Rectangle JavaDoc imageBounds = new Rectangle JavaDoc(im.getMinX(),
1017                                                      im.getMinY(),
1018                                                      im.getWidth(),
1019                                                      im.getHeight());
1020                // Clip to actual image bounds
1021
sourceRegion = sourceRegion.intersection(imageBounds);
1022                sourceXOffset = sourceRegion.x;
1023                sourceYOffset = sourceRegion.y;
1024                sourceWidth = sourceRegion.width;
1025                sourceHeight = sourceRegion.height;
1026            }
1027
1028            // Adjust for subsampling offsets
1029
int gridX = param.getSubsamplingXOffset();
1030            int gridY = param.getSubsamplingYOffset();
1031            sourceXOffset += gridX;
1032            sourceYOffset += gridY;
1033            sourceWidth -= gridX;
1034            sourceHeight -= gridY;
1035
1036            // Get subsampling factors
1037
periodX = param.getSourceXSubsampling();
1038            periodY = param.getSourceYSubsampling();
1039
1040            int[] sBands = param.getSourceBands();
1041            if (sBands != null) {
1042                sourceBands = sBands;
1043                numBands = sourceBands.length;
1044            }
1045        }
1046
1047        // Compute output dimensions
1048
int destWidth = (sourceWidth + periodX - 1)/periodX;
1049        int destHeight = (sourceHeight + periodY - 1)/periodY;
1050        if (destWidth <= 0 || destHeight <= 0) {
1051            throw new IllegalArgumentException JavaDoc("Empty source region!");
1052        }
1053
1054        // Compute total number of pixels for progress notification
1055
this.totalPixels = destWidth*destHeight;
1056        this.pixelsDone = 0;
1057
1058        // Create metadata
1059
IIOMetadata JavaDoc imd = image.getMetadata();
1060        if (imd != null) {
1061            metadata = (PNGMetadata)convertImageMetadata(imd,
1062                               ImageTypeSpecifier.createFromRenderedImage(im),
1063                                                         null);
1064        } else {
1065            metadata = new PNGMetadata();
1066        }
1067
1068        if (param != null) {
1069            // Use Adam7 interlacing if set in write param
1070
switch (param.getProgressiveMode()) {
1071            case ImageWriteParam.MODE_DEFAULT:
1072                metadata.IHDR_interlaceMethod = 1;
1073                break;
1074            case ImageWriteParam.MODE_DISABLED:
1075                metadata.IHDR_interlaceMethod = 0;
1076                break;
1077                // MODE_COPY_FROM_METADATA should alreay be taken care of
1078
// MODE_EXPLICIT is not allowed
1079
}
1080        }
1081
1082        // Initialize bitDepth and colorType
1083
metadata.initialize(new ImageTypeSpecifier JavaDoc(im), numBands);
1084
1085        // Overwrite IHDR width and height values with values from image
1086
metadata.IHDR_width = destWidth;
1087        metadata.IHDR_height = destHeight;
1088
1089        this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
1090
1091    // Initialize scaling tables for this image
1092
initializeScaleTables(sampleModel.getSampleSize());
1093
1094        clearAbortRequest();
1095
1096        processImageStarted(0);
1097
1098        try {
1099            write_magic();
1100            write_IHDR();
1101
1102            write_cHRM();
1103            write_gAMA();
1104            write_iCCP();
1105            write_sBIT();
1106            write_sRGB();
1107        
1108            write_PLTE();
1109            
1110            write_hIST();
1111            write_tRNS();
1112            write_bKGD();
1113            
1114            write_pHYs();
1115            write_sPLT();
1116            write_tIME();
1117            write_tEXt();
1118            write_iTXt();
1119            write_zTXt();
1120            
1121            writeUnknownChunks();
1122            
1123            write_IDAT(im);
1124
1125            if (abortRequested()) {
1126                processWriteAborted();
1127            } else {
1128                // Finish up and inform the listeners we are done
1129
writeIEND();
1130                processImageComplete();
1131            }
1132        } catch (IOException JavaDoc e) {
1133            throw new IIOException JavaDoc("I/O error writing PNG file!", e);
1134        }
1135    }
1136}
1137
Popular Tags