KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > batik > ext > awt > image > codec > PNGImageEncoder


1 /*
2
3    Copyright 2001,2003 The Apache Software Foundation
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6    you may not use this file except in compliance with the License.
7    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 package org.apache.batik.ext.awt.image.codec;
19
20 import java.awt.Rectangle JavaDoc;
21 import java.awt.image.ColorModel JavaDoc;
22 import java.awt.image.IndexColorModel JavaDoc;
23 import java.awt.image.Raster JavaDoc;
24 import java.awt.image.RenderedImage JavaDoc;
25 import java.awt.image.SampleModel JavaDoc;
26 import java.io.ByteArrayOutputStream JavaDoc;
27 import java.io.DataOutput JavaDoc;
28 import java.io.DataOutputStream JavaDoc;
29 import java.io.FilterOutputStream JavaDoc;
30 import java.io.IOException JavaDoc;
31 import java.io.OutputStream JavaDoc;
32 import java.util.Calendar JavaDoc;
33 import java.util.Date JavaDoc;
34 import java.util.GregorianCalendar JavaDoc;
35 import java.util.TimeZone JavaDoc;
36 import java.util.zip.Deflater JavaDoc;
37 import java.util.zip.DeflaterOutputStream JavaDoc;
38
39 class CRC {
40
41     private static int[] crcTable = new int[256];
42
43     static {
44         // Initialize CRC table
45
for (int n = 0; n < 256; n++) {
46             int c = n;
47             for (int k = 0; k < 8; k++) {
48                 if ((c & 1) == 1) {
49                     c = 0xedb88320 ^ (c >>> 1);
50                 } else {
51                     c >>>= 1;
52                 }
53
54                 crcTable[n] = c;
55             }
56         }
57     }
58
59     public static int updateCRC(int crc, byte[] data, int off, int len) {
60         int c = crc;
61
62         for (int n = 0; n < len; n++) {
63              c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
64         }
65
66         return c;
67     }
68 }
69
70
71 class ChunkStream extends OutputStream JavaDoc implements DataOutput JavaDoc {
72
73     private String JavaDoc type;
74     private ByteArrayOutputStream JavaDoc baos;
75     private DataOutputStream JavaDoc dos;
76
77     public ChunkStream(String JavaDoc type) throws IOException JavaDoc {
78         this.type = type;
79
80         this.baos = new ByteArrayOutputStream JavaDoc();
81         this.dos = new DataOutputStream JavaDoc(baos);
82     }
83
84     public void write(byte[] b) throws IOException JavaDoc {
85         dos.write(b);
86     }
87
88     public void write(byte[] b, int off, int len) throws IOException JavaDoc {
89         dos.write(b, off, len);
90     }
91
92     public void write(int b) throws IOException JavaDoc {
93         dos.write(b);
94     }
95
96     public void writeBoolean(boolean v) throws IOException JavaDoc {
97         dos.writeBoolean(v);
98     }
99
100     public void writeByte(int v) throws IOException JavaDoc {
101         dos.writeByte(v);
102     }
103
104     public void writeBytes(String JavaDoc s) throws IOException JavaDoc {
105         dos.writeBytes(s);
106     }
107
108     public void writeChar(int v) throws IOException JavaDoc {
109         dos.writeChar(v);
110     }
111
112     public void writeChars(String JavaDoc s) throws IOException JavaDoc {
113         dos.writeChars(s);
114     }
115
116     public void writeDouble(double v) throws IOException JavaDoc {
117         dos.writeDouble(v);
118     }
119
120     public void writeFloat(float v) throws IOException JavaDoc {
121         dos.writeFloat(v);
122     }
123
124     public void writeInt(int v) throws IOException JavaDoc {
125         dos.writeInt(v);
126     }
127
128     public void writeLong(long v) throws IOException JavaDoc {
129         dos.writeLong(v);
130     }
131
132     public void writeShort(int v) throws IOException JavaDoc {
133         dos.writeShort(v);
134     }
135
136     public void writeUTF(String JavaDoc str) throws IOException JavaDoc {
137         dos.writeUTF(str);
138     }
139
140     public void writeToStream(DataOutputStream JavaDoc output) throws IOException JavaDoc {
141         byte[] typeSignature = new byte[4];
142         typeSignature[0] = (byte)type.charAt(0);
143         typeSignature[1] = (byte)type.charAt(1);
144         typeSignature[2] = (byte)type.charAt(2);
145         typeSignature[3] = (byte)type.charAt(3);
146
147         dos.flush();
148         baos.flush();
149
150         byte[] data = baos.toByteArray();
151         int len = data.length;
152
153         output.writeInt(len);
154         output.write(typeSignature);
155         output.write(data, 0, len);
156
157         int crc = 0xffffffff;
158         crc = CRC.updateCRC(crc, typeSignature, 0, 4);
159         crc = CRC.updateCRC(crc, data, 0, len);
160         output.writeInt(crc ^ 0xffffffff);
161     }
162 }
163
164
165 class IDATOutputStream extends FilterOutputStream JavaDoc {
166
167     private static final byte[] typeSignature =
168       {(byte)'I', (byte)'D', (byte)'A', (byte)'T'};
169
170     private int bytesWritten = 0;
171     private int segmentLength;
172     byte[] buffer;
173
174     public IDATOutputStream(OutputStream JavaDoc output,
175                             int segmentLength) {
176         super(output);
177         this.segmentLength = segmentLength;
178         this.buffer = new byte[segmentLength];
179     }
180
181     public void close() throws IOException JavaDoc {
182         flush();
183     }
184
185     private void writeInt(int x) throws IOException JavaDoc {
186         out.write(x >> 24);
187         out.write((x >> 16) & 0xff);
188         out.write((x >> 8) & 0xff);
189         out.write(x & 0xff);
190     }
191
192     public void flush() throws IOException JavaDoc {
193         // Length
194
writeInt(bytesWritten);
195         // 'IDAT' signature
196
out.write(typeSignature);
197         // Data
198
out.write(buffer, 0, bytesWritten);
199
200         int crc = 0xffffffff;
201         crc = CRC.updateCRC(crc, typeSignature, 0, 4);
202         crc = CRC.updateCRC(crc, buffer, 0, bytesWritten);
203
204         // CRC
205
writeInt(crc ^ 0xffffffff);
206
207         // Reset buffer
208
bytesWritten = 0;
209     }
210
211     public void write(byte[] b) throws IOException JavaDoc {
212         this.write(b, 0, b.length);
213     }
214
215     public void write(byte[] b, int off, int len) throws IOException JavaDoc {
216         while (len > 0) {
217             int bytes = Math.min(segmentLength - bytesWritten, len);
218             System.arraycopy(b, off, buffer, bytesWritten, bytes);
219             off += bytes;
220             len -= bytes;
221             bytesWritten += bytes;
222
223             if (bytesWritten == segmentLength) {
224                 flush();
225             }
226         }
227     }
228
229     public void write(int b) throws IOException JavaDoc {
230         buffer[bytesWritten++] = (byte)b;
231         if (bytesWritten == segmentLength) {
232             flush();
233         }
234     }
235 }
236
237 /**
238  * An ImageEncoder for the PNG file format.
239  *
240  * @since EA4
241  */

242 public class PNGImageEncoder extends ImageEncoderImpl {
243
244     private static final int PNG_COLOR_GRAY = 0;
245     private static final int PNG_COLOR_RGB = 2;
246     private static final int PNG_COLOR_PALETTE = 3;
247     private static final int PNG_COLOR_GRAY_ALPHA = 4;
248     private static final int PNG_COLOR_RGB_ALPHA = 6;
249
250     private static final byte[] magic = {
251         (byte)137, (byte) 80, (byte) 78, (byte) 71,
252         (byte) 13, (byte) 10, (byte) 26, (byte) 10
253     };
254
255     private PNGEncodeParam param;
256
257     private RenderedImage JavaDoc image;
258     private int width;
259     private int height;
260     private int bitDepth;
261     private int bitShift;
262     private int numBands;
263     private int colorType;
264
265     private int bpp; // bytes per pixel, rounded up
266

267     private boolean skipAlpha = false;
268     private boolean compressGray = false;
269
270     private boolean interlace;
271
272     private byte[] redPalette = null;
273     private byte[] greenPalette = null;
274     private byte[] bluePalette = null;
275     private byte[] alphaPalette = null;
276
277     private DataOutputStream JavaDoc dataOutput;
278
279     public PNGImageEncoder(OutputStream JavaDoc output,
280                            PNGEncodeParam param) {
281         super(output, param);
282
283         if (param != null) {
284             this.param = param;
285         }
286         this.dataOutput = new DataOutputStream JavaDoc(output);
287     }
288
289     private void writeMagic() throws IOException JavaDoc {
290         dataOutput.write(magic);
291     }
292
293     private void writeIHDR() throws IOException JavaDoc {
294         ChunkStream cs = new ChunkStream("IHDR");
295         cs.writeInt(width);
296         cs.writeInt(height);
297         cs.writeByte((byte)bitDepth);
298         cs.writeByte((byte)colorType);
299         cs.writeByte((byte)0);
300         cs.writeByte((byte)0);
301         cs.writeByte(interlace ? (byte)1 : (byte)0);
302
303         cs.writeToStream(dataOutput);
304     }
305
306     private byte[] prevRow = null;
307     private byte[] currRow = null;
308
309     private byte[][] filteredRows = null;
310
311     private static int clamp(int val, int maxValue) {
312         return (val > maxValue) ? maxValue : val;
313     }
314
315     private void encodePass(OutputStream JavaDoc os, Raster JavaDoc ras,
316                             int xOffset, int yOffset,
317                             int xSkip, int ySkip)
318         throws IOException JavaDoc {
319         int minX = ras.getMinX();
320         int minY = ras.getMinY();
321         int width = ras.getWidth();
322         int height = ras.getHeight();
323
324         xOffset *= numBands;
325         xSkip *= numBands;
326
327         int samplesPerByte = 8/bitDepth;
328
329         int numSamples = width*numBands;
330         int[] samples = new int[numSamples];
331
332         int pixels = (numSamples - xOffset + xSkip - 1)/xSkip;
333         int bytesPerRow = pixels*numBands;
334         if (bitDepth < 8) {
335             bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte;
336         } else if (bitDepth == 16) {
337             bytesPerRow *= 2;
338         }
339
340         if (bytesPerRow == 0) {
341             return;
342         }
343
344         currRow = new byte[bytesPerRow + bpp];
345         prevRow = new byte[bytesPerRow + bpp];
346
347         filteredRows = new byte[5][bytesPerRow + bpp];
348
349         int maxValue = (1 << bitDepth) - 1;
350
351         for (int row = minY + yOffset; row < minY + height; row += ySkip) {
352             ras.getPixels(minX, row, width, 1, samples);
353
354             if (compressGray) {
355                 int shift = 8 - bitDepth;
356                 for (int i = 0; i < width; i++) {
357                     samples[i] >>= shift;
358                 }
359             }
360
361             int count = bpp; // leave first 'bpp' bytes zero
362
int pos = 0;
363             int tmp = 0;
364
365             switch (bitDepth) {
366             case 1: case 2: case 4:
367                 // Image can only have a single band
368

369                 int mask = samplesPerByte - 1;
370                 for (int s = xOffset; s < numSamples; s += xSkip) {
371                     int val = clamp(samples[s] >> bitShift, maxValue);
372                     tmp = (tmp << bitDepth) | val;
373
374                     if (pos++ == mask) {
375                         currRow[count++] = (byte)tmp;
376                         tmp = 0;
377                         pos = 0;
378                     }
379                 }
380
381                 // Left shift the last byte
382
if (pos != 0) {
383                     tmp <<= (samplesPerByte - pos)*bitDepth;
384                     currRow[count++] = (byte)tmp;
385                 }
386                 break;
387
388             case 8:
389                 for (int s = xOffset; s < numSamples; s += xSkip) {
390                     for (int b = 0; b < numBands; b++) {
391                         currRow[count++] =
392                             (byte)clamp(samples[s + b] >> bitShift, maxValue);
393                     }
394                 }
395                 break;
396
397             case 16:
398                 for (int s = xOffset; s < numSamples; s += xSkip) {
399                     for (int b = 0; b < numBands; b++) {
400                         int val = clamp(samples[s + b] >> bitShift, maxValue);
401                         currRow[count++] = (byte)(val >> 8);
402                         currRow[count++] = (byte)(val & 0xff);
403                     }
404                 }
405                 break;
406             }
407
408             // Perform filtering
409
int filterType = param.filterRow(currRow, prevRow,
410                                              filteredRows,
411                                              bytesPerRow, bpp);
412
413             os.write(filterType);
414             os.write(filteredRows[filterType], bpp, bytesPerRow);
415
416             // Swap current and previous rows
417
byte[] swap = currRow;
418             currRow = prevRow;
419             prevRow = swap;
420         }
421     }
422
423     private void writeIDAT() throws IOException JavaDoc {
424         IDATOutputStream ios = new IDATOutputStream(dataOutput, 8192);
425         DeflaterOutputStream JavaDoc dos =
426             new DeflaterOutputStream JavaDoc(ios, new Deflater JavaDoc(9));
427
428         // Future work - don't convert entire image to a Raster It
429
// might seem that you could just call image.getData() but
430
// 'BufferedImage.subImage' doesn't appear to set the Width
431
// and height properly of the Child Raster, so the Raster
432
// you get back here appears larger than it should.
433
// This solves that problem by bounding the raster to the
434
// image's bounds...
435
Raster JavaDoc ras = image.getData(new Rectangle JavaDoc(image.getMinX(),
436                                                  image.getMinY(),
437                                                  image.getWidth(),
438                                                  image.getHeight()));
439         // System.out.println("Image: [" +
440
// image.getMinY() + ", " +
441
// image.getMinX() + ", " +
442
// image.getWidth() + ", " +
443
// image.getHeight() + "]");
444
// System.out.println("Ras: [" +
445
// ras.getMinX() + ", " +
446
// ras.getMinY() + ", " +
447
// ras.getWidth() + ", " +
448
// ras.getHeight() + "]");
449

450         if (skipAlpha) {
451             int numBands = ras.getNumBands() - 1;
452             int[] bandList = new int[numBands];
453             for (int i = 0; i < numBands; i++) {
454                 bandList[i] = i;
455             }
456             ras = ras.createChild(0, 0,
457                                   ras.getWidth(), ras.getHeight(),
458                                   0, 0,
459                                   bandList);
460         }
461
462         if (interlace) {
463             // Interlacing pass 1
464
encodePass(dos, ras, 0, 0, 8, 8);
465             // Interlacing pass 2
466
encodePass(dos, ras, 4, 0, 8, 8);
467             // Interlacing pass 3
468
encodePass(dos, ras, 0, 4, 4, 8);
469             // Interlacing pass 4
470
encodePass(dos, ras, 2, 0, 4, 4);
471             // Interlacing pass 5
472
encodePass(dos, ras, 0, 2, 2, 4);
473             // Interlacing pass 6
474
encodePass(dos, ras, 1, 0, 2, 2);
475             // Interlacing pass 7
476
encodePass(dos, ras, 0, 1, 1, 2);
477         } else {
478             encodePass(dos, ras, 0, 0, 1, 1);
479         }
480
481         dos.finish();
482         ios.flush();
483     }
484
485     private void writeIEND() throws IOException JavaDoc {
486         ChunkStream cs = new ChunkStream("IEND");
487         cs.writeToStream(dataOutput);
488     }
489
490     private static final float[] srgbChroma = {
491         0.31270F, 0.329F, 0.64F, 0.33F, 0.3F, 0.6F, 0.15F, 0.06F
492     };
493
494     private void writeCHRM() throws IOException JavaDoc {
495         if (param.isChromaticitySet() || param.isSRGBIntentSet()) {
496             ChunkStream cs = new ChunkStream("cHRM");
497
498             float[] chroma;
499             if (!param.isSRGBIntentSet()) {
500                 chroma = param.getChromaticity();
501             } else {
502                 chroma = srgbChroma; // SRGB chromaticities
503
}
504
505             for (int i = 0; i < 8; i++) {
506                 cs.writeInt((int)(chroma[i]*100000));
507             }
508             cs.writeToStream(dataOutput);
509         }
510     }
511
512     private void writeGAMA() throws IOException JavaDoc {
513         if (param.isGammaSet() || param.isSRGBIntentSet()) {
514             ChunkStream cs = new ChunkStream("gAMA");
515
516             float gamma;
517             if (!param.isSRGBIntentSet()) {
518                 gamma = param.getGamma();
519             } else {
520                 gamma = 1.0F/2.2F; // SRGB gamma
521
}
522             // TD should include the .5 but causes regard to say
523
// everything is different.
524
cs.writeInt((int)(gamma*100000/*+0.5*/));
525             cs.writeToStream(dataOutput);
526         }
527     }
528
529     private void writeICCP() throws IOException JavaDoc {
530         if (param.isICCProfileDataSet()) {
531             ChunkStream cs = new ChunkStream("iCCP");
532             byte[] ICCProfileData = param.getICCProfileData();
533             cs.write(ICCProfileData);
534             cs.writeToStream(dataOutput);
535         }
536     }
537
538     private void writeSBIT() throws IOException JavaDoc {
539         if (param.isSignificantBitsSet()) {
540             ChunkStream cs = new ChunkStream("sBIT");
541             int[] significantBits = param.getSignificantBits();
542             int len = significantBits.length;
543             for (int i = 0; i < len; i++) {
544                 cs.writeByte(significantBits[i]);
545             }
546             cs.writeToStream(dataOutput);
547         }
548     }
549
550     private void writeSRGB() throws IOException JavaDoc {
551         if (param.isSRGBIntentSet()) {
552             ChunkStream cs = new ChunkStream("sRGB");
553
554             int intent = param.getSRGBIntent();
555             cs.write(intent);
556             cs.writeToStream(dataOutput);
557         }
558     }
559
560     private void writePLTE() throws IOException JavaDoc {
561         if (redPalette == null) {
562             return;
563         }
564
565         ChunkStream cs = new ChunkStream("PLTE");
566         for (int i = 0; i < redPalette.length; i++) {
567             cs.writeByte(redPalette[i]);
568             cs.writeByte(greenPalette[i]);
569             cs.writeByte(bluePalette[i]);
570         }
571
572         cs.writeToStream(dataOutput);
573     }
574
575     private void writeBKGD() throws IOException JavaDoc {
576         if (param.isBackgroundSet()) {
577             ChunkStream cs = new ChunkStream("bKGD");
578
579             switch (colorType) {
580             case PNG_COLOR_GRAY:
581             case PNG_COLOR_GRAY_ALPHA:
582                 int gray = ((PNGEncodeParam.Gray)param).getBackgroundGray();
583                 cs.writeShort(gray);
584                 break;
585
586             case PNG_COLOR_PALETTE:
587                 int index =
588                    ((PNGEncodeParam.Palette)param).getBackgroundPaletteIndex();
589                 cs.writeByte(index);
590                 break;
591
592             case PNG_COLOR_RGB:
593             case PNG_COLOR_RGB_ALPHA:
594                 int[] rgb = ((PNGEncodeParam.RGB)param).getBackgroundRGB();
595                 cs.writeShort(rgb[0]);
596                 cs.writeShort(rgb[1]);
597                 cs.writeShort(rgb[2]);
598                 break;
599             }
600
601             cs.writeToStream(dataOutput);
602         }
603     }
604
605     private void writeHIST() throws IOException JavaDoc {
606         if (param.isPaletteHistogramSet()) {
607             ChunkStream cs = new ChunkStream("hIST");
608
609             int[] hist = param.getPaletteHistogram();
610             for (int i = 0; i < hist.length; i++) {
611                 cs.writeShort(hist[i]);
612             }
613
614             cs.writeToStream(dataOutput);
615         }
616     }
617
618     private void writeTRNS() throws IOException JavaDoc {
619         if (param.isTransparencySet() &&
620             (colorType != PNG_COLOR_GRAY_ALPHA) &&
621             (colorType != PNG_COLOR_RGB_ALPHA)) {
622             ChunkStream cs = new ChunkStream("tRNS");
623
624             if (param instanceof PNGEncodeParam.Palette) {
625                 byte[] t =
626                     ((PNGEncodeParam.Palette)param).getPaletteTransparency();
627                 for (int i = 0; i < t.length; i++) {
628                     cs.writeByte(t[i]);
629                 }
630             } else if (param instanceof PNGEncodeParam.Gray) {
631                 int t = ((PNGEncodeParam.Gray)param).getTransparentGray();
632                 cs.writeShort(t);
633             } else if (param instanceof PNGEncodeParam.RGB) {
634                 int[] t = ((PNGEncodeParam.RGB)param).getTransparentRGB();
635                 cs.writeShort(t[0]);
636                 cs.writeShort(t[1]);
637                 cs.writeShort(t[2]);
638             }
639
640             cs.writeToStream(dataOutput);
641         } else if (colorType == PNG_COLOR_PALETTE) {
642             int lastEntry = Math.min(255, alphaPalette.length - 1);
643             int nonOpaque;
644             for (nonOpaque = lastEntry; nonOpaque >= 0; nonOpaque--) {
645                 if (alphaPalette[nonOpaque] != (byte)255) {
646                     break;
647                 }
648             }
649
650             if (nonOpaque >= 0) {
651                 ChunkStream cs = new ChunkStream("tRNS");
652                 for (int i = 0; i <= nonOpaque; i++) {
653                     cs.writeByte(alphaPalette[i]);
654                 }
655                 cs.writeToStream(dataOutput);
656             }
657         }
658     }
659
660     private void writePHYS() throws IOException JavaDoc {
661         if (param.isPhysicalDimensionSet()) {
662             ChunkStream cs = new ChunkStream("pHYs");
663
664             int[] dims = param.getPhysicalDimension();
665             cs.writeInt(dims[0]);
666             cs.writeInt(dims[1]);
667             cs.writeByte((byte)dims[2]);
668
669             cs.writeToStream(dataOutput);
670         }
671     }
672
673     private void writeSPLT() throws IOException JavaDoc {
674         if (param.isSuggestedPaletteSet()) {
675             ChunkStream cs = new ChunkStream("sPLT");
676
677             System.out.println("sPLT not supported yet.");
678
679             cs.writeToStream(dataOutput);
680         }
681     }
682
683     private void writeTIME() throws IOException JavaDoc {
684         if (param.isModificationTimeSet()) {
685             ChunkStream cs = new ChunkStream("tIME");
686
687             Date JavaDoc date = param.getModificationTime();
688             TimeZone JavaDoc gmt = TimeZone.getTimeZone("GMT");
689
690             GregorianCalendar JavaDoc cal = new GregorianCalendar JavaDoc(gmt);
691             cal.setTime(date);
692
693             int year = cal.get(Calendar.YEAR);
694             int month = cal.get(Calendar.MONTH);
695             int day = cal.get(Calendar.DAY_OF_MONTH);
696             int hour = cal.get(Calendar.HOUR_OF_DAY);
697             int minute = cal.get(Calendar.MINUTE);
698             int second = cal.get(Calendar.SECOND);
699
700             cs.writeShort(year);
701             cs.writeByte(month + 1);
702             cs.writeByte(day);
703             cs.writeByte(hour);
704             cs.writeByte(minute);
705             cs.writeByte(second);
706
707             cs.writeToStream(dataOutput);
708         }
709     }
710
711     private void writeTEXT() throws IOException JavaDoc {
712         if (param.isTextSet()) {
713             String JavaDoc[] text = param.getText();
714
715             for (int i = 0; i < text.length/2; i++) {
716                 byte[] keyword = text[2*i].getBytes();
717                 byte[] value = text[2*i + 1].getBytes();
718
719                 ChunkStream cs = new ChunkStream("tEXt");
720
721                 cs.write(keyword, 0, Math.min(keyword.length, 79));
722                 cs.write(0);
723                 cs.write(value);
724
725                 cs.writeToStream(dataOutput);
726             }
727         }
728     }
729
730     private void writeZTXT() throws IOException JavaDoc {
731         if (param.isCompressedTextSet()) {
732             String JavaDoc[] text = param.getCompressedText();
733
734             for (int i = 0; i < text.length/2; i++) {
735                 byte[] keyword = text[2*i].getBytes();
736                 byte[] value = text[2*i + 1].getBytes();
737
738                 ChunkStream cs = new ChunkStream("zTXt");
739
740                 cs.write(keyword, 0, Math.min(keyword.length, 79));
741                 cs.write(0);
742                 cs.write(0);
743
744                 DeflaterOutputStream JavaDoc dos = new DeflaterOutputStream JavaDoc(cs);
745                 dos.write(value);
746                 dos.finish();
747
748                 cs.writeToStream(dataOutput);
749             }
750         }
751     }
752
753     private void writePrivateChunks() throws IOException JavaDoc {
754         int numChunks = param.getNumPrivateChunks();
755         for (int i = 0; i < numChunks; i++) {
756             String JavaDoc type = param.getPrivateChunkType(i);
757             byte[] data = param.getPrivateChunkData(i);
758
759             ChunkStream cs = new ChunkStream(type);
760             cs.write(data);
761             cs.writeToStream(dataOutput);
762         }
763     }
764
765     /**
766      * Analyzes a set of palettes and determines if it can be expressed
767      * as a standard set of gray values, with zero or one values being
768      * fully transparent and the rest being fully opaque. If it
769      * is possible to express the data thusly, the method returns
770      * a suitable instance of PNGEncodeParam.Gray; otherwise it
771      * returns null.
772      */

773     private PNGEncodeParam.Gray createGrayParam(byte[] redPalette,
774                                                 byte[] greenPalette,
775                                                 byte[] bluePalette,
776                                                 byte[] alphaPalette) {
777         PNGEncodeParam.Gray param = new PNGEncodeParam.Gray();
778         int numTransparent = 0;
779
780         int grayFactor = 255/((1 << bitDepth) - 1);
781         int entries = 1 << bitDepth;
782         for (int i = 0; i < entries; i++) {
783             byte red = redPalette[i];
784             if ((red != i*grayFactor) ||
785                 (red != greenPalette[i]) ||
786                 (red != bluePalette[i])) {
787                 return null;
788             }
789
790             // All alphas must be 255 except at most 1 can be 0
791
byte alpha = alphaPalette[i];
792             if (alpha == (byte)0) {
793                 param.setTransparentGray(i);
794
795                 ++numTransparent;
796                 if (numTransparent > 1) {
797                     return null;
798                 }
799             } else if (alpha != (byte)255) {
800                 return null;
801             }
802         }
803
804         return param;
805     }
806
807     /**
808      * This method encodes a <code>RenderedImage</code> into PNG.
809      * The stream into which the PNG is dumped is not closed at
810      * the end of the operation, this should be done if needed
811      * by the caller of this method.
812      */

813     public void encode(RenderedImage JavaDoc im) throws IOException JavaDoc {
814         this.image = im;
815         this.width = image.getWidth();
816         this.height = image.getHeight();
817
818         SampleModel JavaDoc sampleModel = image.getSampleModel();
819
820         int[] sampleSize = sampleModel.getSampleSize();
821
822         // Set bitDepth to a sentinel value
823
this.bitDepth = -1;
824         this.bitShift = 0;
825
826         // Allow user to override the bit depth of gray images
827
if (param instanceof PNGEncodeParam.Gray) {
828             PNGEncodeParam.Gray paramg = (PNGEncodeParam.Gray)param;
829             if (paramg.isBitDepthSet()) {
830                 this.bitDepth = paramg.getBitDepth();
831             }
832
833             if (paramg.isBitShiftSet()) {
834                 this.bitShift = paramg.getBitShift();
835             }
836         }
837
838         // Get bit depth from image if not set in param
839
if (this.bitDepth == -1) {
840             // Get bit depth from channel 0 of the image
841

842             this.bitDepth = sampleSize[0];
843             // Ensure all channels have the same bit depth
844
for (int i = 1; i < sampleSize.length; i++) {
845                 if (sampleSize[i] != bitDepth) {
846                     throw new RuntimeException JavaDoc();
847                 }
848             }
849
850             // Round bit depth up to a power of 2
851
if (bitDepth > 2 && bitDepth < 4) {
852                 bitDepth = 4;
853             } else if (bitDepth > 4 && bitDepth < 8) {
854                 bitDepth = 8;
855             } else if (bitDepth > 8 && bitDepth < 16) {
856                 bitDepth = 16;
857             } else if (bitDepth > 16) {
858                 throw new RuntimeException JavaDoc();
859             }
860         }
861
862         this.numBands = sampleModel.getNumBands();
863         this.bpp = numBands*((bitDepth == 16) ? 2 : 1);
864
865         ColorModel JavaDoc colorModel = image.getColorModel();
866         if (colorModel instanceof IndexColorModel JavaDoc) {
867             if (bitDepth < 1 || bitDepth > 8) {
868                 throw new RuntimeException JavaDoc();
869             }
870             if (sampleModel.getNumBands() != 1) {
871                 throw new RuntimeException JavaDoc();
872             }
873
874             IndexColorModel JavaDoc icm = (IndexColorModel JavaDoc)colorModel;
875             int size = icm.getMapSize();
876
877             redPalette = new byte[size];
878             greenPalette = new byte[size];
879             bluePalette = new byte[size];
880             alphaPalette = new byte[size];
881
882             icm.getReds(redPalette);
883             icm.getGreens(greenPalette);
884             icm.getBlues(bluePalette);
885             icm.getAlphas(alphaPalette);
886
887             this.bpp = 1;
888
889             if (param == null) {
890                 param = createGrayParam(redPalette,
891                                         greenPalette,
892                                         bluePalette,
893                                         alphaPalette);
894             }
895
896             // If param is still null, it can't be expressed as gray
897
if (param == null) {
898                 param = new PNGEncodeParam.Palette();
899             }
900
901             if (param instanceof PNGEncodeParam.Palette) {
902                 // If palette not set in param, create one from the ColorModel.
903
PNGEncodeParam.Palette parami = (PNGEncodeParam.Palette)param;
904                 if (parami.isPaletteSet()) {
905                     int[] palette = parami.getPalette();
906                     size = palette.length/3;
907
908                     int index = 0;
909                     for (int i = 0; i < size; i++) {
910                         redPalette[i] = (byte)palette[index++];
911                         greenPalette[i] = (byte)palette[index++];
912                         bluePalette[i] = (byte)palette[index++];
913                         alphaPalette[i] = (byte)255;
914                     }
915                 }
916                 this.colorType = PNG_COLOR_PALETTE;
917             } else if (param instanceof PNGEncodeParam.Gray) {
918                 redPalette = greenPalette = bluePalette = alphaPalette = null;
919                 this.colorType = PNG_COLOR_GRAY;
920             } else {
921                 throw new RuntimeException JavaDoc();
922             }
923         } else if (numBands == 1) {
924             if (param == null) {
925                 param = new PNGEncodeParam.Gray();
926             }
927             this.colorType = PNG_COLOR_GRAY;
928         } else if (numBands == 2) {
929             if (param == null) {
930                 param = new PNGEncodeParam.Gray();
931             }
932
933             if (param.isTransparencySet()) {
934                 skipAlpha = true;
935                 numBands = 1;
936                 if ((sampleSize[0] == 8) && (bitDepth < 8)) {
937                     compressGray = true;
938                 }
939                 bpp = (bitDepth == 16) ? 2 : 1;
940                 this.colorType = PNG_COLOR_GRAY;
941             } else {
942                 if (this.bitDepth < 8) {
943                     this.bitDepth = 8;
944                 }
945                 this.colorType = PNG_COLOR_GRAY_ALPHA;
946             }
947         } else if (numBands == 3) {
948             if (param == null) {
949                 param = new PNGEncodeParam.RGB();
950             }
951             this.colorType = PNG_COLOR_RGB;
952         } else if (numBands == 4) {
953             if (param == null) {
954                 param = new PNGEncodeParam.RGB();
955             }
956             if (param.isTransparencySet()) {
957                 skipAlpha = true;
958                 numBands = 3;
959                 bpp = (bitDepth == 16) ? 6 : 3;
960                 this.colorType = PNG_COLOR_RGB;
961             } else {
962                 this.colorType = PNG_COLOR_RGB_ALPHA;
963             }
964         }
965
966         interlace = param.getInterlacing();
967
968         writeMagic();
969
970         writeIHDR();
971
972         writeCHRM();
973         writeGAMA();
974         writeICCP();
975         writeSBIT();
976         writeSRGB();
977
978         writePLTE();
979
980         writeHIST();
981         writeTRNS();
982         writeBKGD();
983
984         writePHYS();
985         writeSPLT();
986         writeTIME();
987         writeTEXT();
988         writeZTXT();
989
990         writePrivateChunks();
991
992         writeIDAT();
993
994         writeIEND();
995
996         dataOutput.flush();
997     }
998 }
999
Popular Tags