1 /*
2  * Copyright 2003-2008 by Paulo Soares.
3  *
4  * The contents of this file are subject to the Mozilla Public License Version 1.1
5  * (the "License"); you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
7  *
8  * Software distributed under the License is distributed on an "AS IS" basis,
9  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10  * for the specific language governing rights and limitations under the License.
11  *
12  * The Original Code is 'iText, a free JAVA-PDF library'.
13  *
14  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
15  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
16  * All Rights Reserved.
17  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
18  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
19  *
20  * Contributor(s): all the names of the contributors are added in the source code
21  * where applicable.
22  *
23  * Alternatively, the contents of this file may be used under the terms of the
24  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
25  * provisions of LGPL are applicable instead of those above.  If you wish to
26  * allow use of your version of this file only under the terms of the LGPL
27  * License and not to allow others to use your version of this file under
28  * the MPL, indicate your decision by deleting the provisions above and
29  * replace them with the notice and other provisions required by the LGPL.
30  * If you do not delete the provisions above, a recipient may use your version
31  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
32  *
33  * This library is free software; you can redistribute it and/or modify it
34  * under the terms of the MPL as stated above or under the terms of the GNU
35  * Library General Public License as published by the Free Software Foundation;
36  * either version 2 of the License, or any later version.
37  *
38  * This library is distributed in the hope that it will be useful, but WITHOUT
39  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
40  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
41  * details.
42  *
43  * If you didn't download this code from the following link, you should check if
44  * you aren't using an obsolete version:
45  * http://www.lowagie.com/iText/
46  *
47  * This code is based on a series of source files originally released
48  * by SUN in the context of the JAI project. The original code was released 
49  * under the BSD license in a specific wording. In a mail dating from
50  * January 23, 2008, Brian Burkhalter (@sun.com) gave us permission
51  * to use the code under the following version of the BSD license:
52  *
53  * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
54  * 
55  * Redistribution and use in source and binary forms, with or without
56  * modification, are permitted provided that the following conditions
57  * are met: 
58  * 
59  * - Redistribution of source code must retain the above copyright 
60  *   notice, this  list of conditions and the following disclaimer.
61  * 
62  * - Redistribution in binary form must reproduce the above copyright
63  *   notice, this list of conditions and the following disclaimer in 
64  *   the documentation and/or other materials provided with the
65  *   distribution.
66  * 
67  * Neither the name of Sun Microsystems, Inc. or the names of 
68  * contributors may be used to endorse or promote products derived 
69  * from this software without specific prior written permission.
70  * 
71  * This software is provided "AS IS," without a warranty of any 
72  * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
73  * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
74  * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
75  * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
76  * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
77  * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
78  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
79  * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
80  * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
81  * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
82  * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
83  * POSSIBILITY OF SUCH DAMAGES. 
84  * 
85  * You acknowledge that this software is not designed or intended for 
86  * use in the design, construction, operation or maintenance of any 
87  * nuclear facility.
88  */

89
90 package com.lowagie.text.pdf.codec;
91
92 import java.awt.color.ICC_Profile;
93 import java.io.ByteArrayInputStream;
94 import java.io.ByteArrayOutputStream;
95 import java.io.DataInputStream;
96 import java.io.IOException;
97 import java.io.InputStream;
98 import java.net.URL;
99 import java.util.zip.Inflater;
100 import java.util.zip.InflaterInputStream;
101
102 import com.lowagie.text.ExceptionConverter;
103 import com.lowagie.text.Image;
104 import com.lowagie.text.ImgRaw;
105 import com.lowagie.text.Utilities;
106 import com.lowagie.text.pdf.ByteBuffer;
107 import com.lowagie.text.pdf.PdfArray;
108 import com.lowagie.text.pdf.PdfDictionary;
109 import com.lowagie.text.pdf.PdfLiteral;
110 import com.lowagie.text.pdf.PdfName;
111 import com.lowagie.text.pdf.PdfNumber;
112 import com.lowagie.text.pdf.PdfObject;
113 import com.lowagie.text.pdf.PdfReader;
114 import com.lowagie.text.pdf.PdfString;
115
116 /** Reads a PNG image. All types of PNG can be read.
117  * <p>
118  * It is based in part in the JAI codec.
119  *
120  * @author  Paulo Soares (psoares@consiste.pt)
121  */

122 public class PngImage {
123 /** Some PNG specific values. */
124     public static final int[] PNGID = {137, 80, 78, 71, 13, 10, 26, 10};
125     
126 /** A PNG marker. */
127     public static final String IHDR = "IHDR";
128     
129 /** A PNG marker. */
130     public static final String PLTE = "PLTE";
131     
132 /** A PNG marker. */
133     public static final String IDAT = "IDAT";
134     
135 /** A PNG marker. */
136     public static final String IEND = "IEND";
137     
138 /** A PNG marker. */
139     public static final String tRNS = "tRNS";
140     
141 /** A PNG marker. */
142     public static final String pHYs = "pHYs";
143     
144 /** A PNG marker. */
145     public static final String gAMA = "gAMA";
146     
147 /** A PNG marker. */
148     public static final String cHRM = "cHRM";
149     
150 /** A PNG marker. */
151     public static final String sRGB = "sRGB";
152     
153 /** A PNG marker. */
154     public static final String iCCP = "iCCP";
155     
156     private static final int TRANSFERSIZE = 4096;
157     private static final int PNG_FILTER_NONE = 0;
158     private static final int PNG_FILTER_SUB = 1;
159     private static final int PNG_FILTER_UP = 2;
160     private static final int PNG_FILTER_AVERAGE = 3;
161     private static final int PNG_FILTER_PAETH = 4;
162     private static final PdfName intents[] = {PdfName.PERCEPTUAL,
163         PdfName.RELATIVECOLORIMETRIC,PdfName.SATURATION,PdfName.ABSOLUTECOLORIMETRIC};
164     
165     InputStream is;
166     DataInputStream dataStream;
167     int width;
168     int height;
169     int bitDepth;
170     int colorType;
171     int compressionMethod;
172     int filterMethod;
173     int interlaceMethod;
174     PdfDictionary additional = new PdfDictionary();
175     byte image[];
176     byte smask[];
177     byte trans[];
178     NewByteArrayOutputStream idat = new NewByteArrayOutputStream();
179     int dpiX;
180     int dpiY;
181     float XYRatio;
182     boolean genBWMask;
183     boolean palShades;
184     int transRedGray = -1;
185     int transGreen = -1;
186     int transBlue = -1;
187     int inputBands;
188     int bytesPerPixel; // number of bytes per input pixel
189     byte colorTable[];
190     float gamma = 1f;
191     boolean hasCHRM = false;
192     float xW, yW, xR, yR, xG, yG, xB, yB;
193     PdfName intent;
194     ICC_Profile icc_profile;
195
196     
197     
198     /** Creates a new instance of PngImage */
199     PngImage(InputStream is) {
200         this.is = is;
201     }
202     
203     /** Reads a PNG from an url.
204      * @param url the url
205      * @throws IOException on error
206      * @return the image
207      */
    
208     public static Image getImage(URL url) throws IOException {
209         InputStream is = null;
210         try {
211             is = url.openStream();
212             Image img = getImage(is);
213             img.setUrl(url);
214             return img;
215         }
216         finally {
217             if (is != null) {
218                 is.close();
219             }
220         }
221     }
222     
223     /** Reads a PNG from a stream.
224      * @param is the stream
225      * @throws IOException on error
226      * @return the image
227      */
    
228     public static Image getImage(InputStream is) throws IOException {
229         PngImage png = new PngImage(is);
230         return png.getImage();
231     }
232     
233     /** Reads a PNG from a file.
234      * @param file the file
235      * @throws IOException on error
236      * @return the image
237      */
    
238     public static Image getImage(String file) throws IOException {
239         return getImage(Utilities.toURL(file));
240     }
241     
242     /** Reads a PNG from a byte array.
243      * @param data the byte array
244      * @throws IOException on error
245      * @return the image
246      */
    
247     public static Image getImage(byte data[]) throws IOException {
248         ByteArrayInputStream is = new ByteArrayInputStream(data);
249         Image img = getImage(is);
250         img.setOriginalData(data);
251         return img;
252     }
253     
254     boolean checkMarker(String s) {
255         if (s.length() != 4)
256             return false;
257         for (int k = 0; k < 4; ++k) {
258             char c = s.charAt(k);
259             if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z'))
260                 return false;
261         }
262         return true;
263     }
264     
265     void readPng() throws IOException {
266         for (int i = 0; i < PNGID.length; i++) {
267             if (PNGID[i] != is.read())    {
268                 throw new IOException("File is not a valid PNG.");
269             }
270         }
271         byte buffer[] = new byte[TRANSFERSIZE];
272         while (true) {
273             int len = getInt(is);
274             String marker = getString(is);
275             if (len < 0 || !checkMarker(marker))
276                 throw new IOException("Corrupted PNG file.");
277             if (IDAT.equals(marker)) {
278                 int size;
279                 while (len != 0) {
280                     size = is.read(buffer, 0, Math.min(len, TRANSFERSIZE));
281                     if (size < 0)
282                         return;
283                     idat.write(buffer, 0, size);
284                     len -= size;
285                 }
286             }
287             else if (tRNS.equals(marker)) {
288                 switch (colorType) {
289                     case 0:
290                         if (len >= 2) {
291                             len -= 2;
292                             int gray = getWord(is);
293                             if (bitDepth == 16)
294                                 transRedGray = gray;
295                             else
296                                 additional.put(PdfName.MASK, new PdfLiteral("["+gray+" "+gray+"]"));
297                         }
298                         break;
299                     case 2:
300                         if (len >= 6) {
301                             len -= 6;
302                             int red = getWord(is);
303                             int green = getWord(is);
304                             int blue = getWord(is);
305                             if (bitDepth == 16) {
306                                 transRedGray = red;
307                                 transGreen = green;
308                                 transBlue = blue;
309                             }
310                             else
311                                 additional.put(PdfName.MASK, new PdfLiteral("["+red+" "+red+" "+green+" "+green+" "+blue+" "+blue+"]"));
312                         }
313                         break;
314                     case 3:
315                         if (len > 0) {
316                             trans = new byte[len];
317                             for (int k = 0; k < len; ++k)
318                                 trans[k] = (byte)is.read();
319                             len = 0;
320                         }
321                         break;
322                 }
323                 Utilities.skip(is, len);
324             }
325             else if (IHDR.equals(marker)) {
326                 width = getInt(is);
327                 height = getInt(is);
328                 
329                 bitDepth = is.read();
330                 colorType = is.read();
331                 compressionMethod = is.read();
332                 filterMethod = is.read();
333                 interlaceMethod = is.read();
334             }
335             else if (PLTE.equals(marker)) {
336                 if (colorType == 3) {
337                     PdfArray colorspace = new PdfArray();
338                     colorspace.add(PdfName.INDEXED);
339                     colorspace.add(getColorspace());
340                     colorspace.add(new PdfNumber(len / 3 - 1));
341                     ByteBuffer colortable = new ByteBuffer();
342                     while ((len--) > 0) {
343                         colortable.append_i(is.read());
344                     }
345                     colorspace.add(new PdfString(colorTable = colortable.toByteArray()));
346                     additional.put(PdfName.COLORSPACE, colorspace);
347                 }
348                 else {
349                     Utilities.skip(is, len);
350                 }
351             }
352             else if (pHYs.equals(marker)) {
353                 int dx = getInt(is);
354                 int dy = getInt(is);
355                 int unit = is.read();
356                 if (unit == 1) {
357                     dpiX = (int)(dx * 0.0254f + 0.5f);
358                     dpiY = (int)(dy * 0.0254f + 0.5f);
359                 }
360                 else {
361                     if (dy != 0)
362                         XYRatio = (float)dx / (float)dy;
363                 }
364             }
365             else if (cHRM.equals(marker)) {
366                 xW = getInt(is) / 100000f;
367                 yW = getInt(is) / 100000f;
368                 xR = getInt(is) / 100000f;
369                 yR = getInt(is) / 100000f;
370                 xG = getInt(is) / 100000f;
371                 yG = getInt(is) / 100000f;
372                 xB = getInt(is) / 100000f;
373                 yB = getInt(is) / 100000f;
374                 hasCHRM = !(Math.abs(xW)<0.0001f||Math.abs(yW)<0.0001f||Math.abs(xR)<0.0001f||Math.abs(yR)<0.0001f||Math.abs(xG)<0.0001f||Math.abs(yG)<0.0001f||Math.abs(xB)<0.0001f||Math.abs(yB)<0.0001f);
375             }
376             else if (sRGB.equals(marker)) {
377                 int ri = is.read();
378                 intent = intents[ri];
379                 gamma = 2.2f;
380                 xW = 0.3127f;
381                 yW = 0.329f;
382                 xR = 0.64f;
383                 yR = 0.33f;
384                 xG = 0.3f;
385                 yG = 0.6f;
386                 xB = 0.15f;
387                 yB = 0.06f;
388                 hasCHRM = true;
389             }
390             else if (gAMA.equals(marker)) {
391                 int gm = getInt(is);
392                 if (gm != 0) {
393                     gamma = 100000f / gm;
394                     if (!hasCHRM) {
395                         xW = 0.3127f;
396                         yW = 0.329f;
397                         xR = 0.64f;
398                         yR = 0.33f;
399                         xG = 0.3f;
400                         yG = 0.6f;
401                         xB = 0.15f;
402                         yB = 0.06f;
403                         hasCHRM = true;
404                     }
405                 }
406             }
407             else if (iCCP.equals(marker)) {
408                 do {
409                     --len;
410                 } while (is.read() != 0);
411                 is.read();
412                 --len;
413                 byte icccom[] = new byte[len];
414                 int p = 0;
415                 while (len > 0) {
416                     int r = is.read(icccom, p, len);
417                     if (r < 0)
418                         throw new IOException("Premature end of file.");
419                     p += r;
420                     len -= r;
421                 }
422                 byte iccp[] = PdfReader.FlateDecode(icccom, true);
423                 icccom = null;
424                 try {
425                     icc_profile = ICC_Profile.getInstance(iccp);
426                 }
427                 catch (RuntimeException e) {
428                     icc_profile = null;
429                 }
430             }
431             else if (IEND.equals(marker)) {
432                 break;
433             }
434             else {
435                 Utilities.skip(is, len);
436             }
437             Utilities.skip(is, 4);
438         }
439     }
440     
441     PdfObject getColorspace() {
442         if (icc_profile != null) {
443             if ((colorType & 2) == 0)
444                 return PdfName.DEVICEGRAY;
445             else
446                 return PdfName.DEVICERGB;
447         }
448         if (gamma == 1f && !hasCHRM) {
449             if ((colorType & 2) == 0)
450                 return PdfName.DEVICEGRAY;
451             else
452                 return PdfName.DEVICERGB;
453         }
454         else {
455             PdfArray array = new PdfArray();
456             PdfDictionary dic = new PdfDictionary();
457             if ((colorType & 2) == 0) {
458                 if (gamma == 1f)
459                     return PdfName.DEVICEGRAY;
460                 array.add(PdfName.CALGRAY);
461                 dic.put(PdfName.GAMMA, new PdfNumber(gamma));
462                 dic.put(PdfName.WHITEPOINT, new PdfLiteral("[1 1 1]"));
463                 array.add(dic);
464             }
465             else {
466                 PdfObject wp = new PdfLiteral("[1 1 1]");
467                 array.add(PdfName.CALRGB);
468                 if (gamma != 1f) {
469                     PdfArray gm = new PdfArray();
470                     PdfNumber n = new PdfNumber(gamma);
471                     gm.add(n);
472                     gm.add(n);
473                     gm.add(n);
474                     dic.put(PdfName.GAMMA, gm);
475                 }
476                 if (hasCHRM) {
477                     float z = yW*((xG-xB)*yR-(xR-xB)*yG+(xR-xG)*yB);
478                     float YA = yR*((xG-xB)*yW-(xW-xB)*yG+(xW-xG)*yB)/z;
479                     float XA = YA*xR/yR;
480                     float ZA = YA*((1-xR)/yR-1);
481                     float YB = -yG*((xR-xB)*yW-(xW-xB)*yR+(xW-xR)*yB)/z;
482                     float XB = YB*xG/yG;
483                     float ZB = YB*((1-xG)/yG-1);
484                     float YC = yB*((xR-xG)*yW-(xW-xG)*yW+(xW-xR)*yG)/z;
485                     float XC = YC*xB/yB;
486                     float ZC = YC*((1-xB)/yB-1);
487                     float XW = XA+XB+XC;
488                     float YW = 1;//YA+YB+YC;
489                     float ZW = ZA+ZB+ZC;
490                     PdfArray wpa = new PdfArray();
491                     wpa.add(new PdfNumber(XW));
492                     wpa.add(new PdfNumber(YW));
493                     wpa.add(new PdfNumber(ZW));
494                     wp = wpa;
495                     PdfArray matrix = new PdfArray();
496                     matrix.add(new PdfNumber(XA));
497                     matrix.add(new PdfNumber(YA));
498                     matrix.add(new PdfNumber(ZA));
499                     matrix.add(new PdfNumber(XB));
500                     matrix.add(new PdfNumber(YB));
501                     matrix.add(new PdfNumber(ZB));
502                     matrix.add(new PdfNumber(XC));
503                     matrix.add(new PdfNumber(YC));
504                     matrix.add(new PdfNumber(ZC));
505                     dic.put(PdfName.MATRIX, matrix);
506                 }
507                 dic.put(PdfName.WHITEPOINT, wp);
508                 array.add(dic);
509             }
510             return array;
511         }
512     }
513     
514     Image getImage() throws IOException {
515         readPng();
516         try {
517             int pal0 = 0;
518             int palIdx = 0;
519             palShades = false;
520             if (trans != null) {
521                 for (int k = 0; k < trans.length; ++k) {
522                     int n = trans[k] & 0xff;
523                     if (n == 0) {
524                         ++pal0;
525                         palIdx = k;
526                     }
527                     if (n != 0 && n != 255) {
528                         palShades = true;
529                         break;
530                     }
531                 }
532             }
533             if ((colorType & 4) != 0)
534                 palShades = true;
535             genBWMask = (!palShades && (pal0 > 1 || transRedGray >= 0));
536             if (!palShades && !genBWMask && pal0 == 1) {
537                 additional.put(PdfName.MASK, new PdfLiteral("["+palIdx+" "+palIdx+"]"));
538             }
539             boolean needDecode = (interlaceMethod == 1) || (bitDepth == 16) || ((colorType & 4) != 0) || palShades || genBWMask;
540             switch (colorType) {
541                 case 0:
542                     inputBands = 1;
543                     break;
544                 case 2:
545                     inputBands = 3;
546                     break;
547                 case 3:
548                     inputBands = 1;
549                     break;
550                 case 4:
551                     inputBands = 2;
552                     break;
553                 case 6:
554                     inputBands = 4;
555                     break;
556             }
557             if (needDecode)
558                 decodeIdat();
559             int components = inputBands;
560             if ((colorType & 4) != 0)
561                 --components;
562             int bpc = bitDepth;
563             if (bpc == 16)
564                 bpc = 8;
565             Image img;
566             if (image != null) {
567                 if (colorType == 3)
568                     img = new ImgRaw(width, height, components, bpc, image);
569                 else
570                     img = Image.getInstance(width, height, components, bpc, image);
571             }
572             else {
573                 img = new ImgRaw(width, height, components, bpc, idat.toByteArray());
574                 img.setDeflated(true);
575                 PdfDictionary decodeparms = new PdfDictionary();
576                 decodeparms.put(PdfName.BITSPERCOMPONENT, new PdfNumber(bitDepth));
577                 decodeparms.put(PdfName.PREDICTOR, new PdfNumber(15));
578                 decodeparms.put(PdfName.COLUMNS, new PdfNumber(width));
579                 decodeparms.put(PdfName.COLORS, new PdfNumber((colorType == 3 || (colorType & 2) == 0) ? 1 : 3));
580                 additional.put(PdfName.DECODEPARMS, decodeparms);
581             }
582             if (additional.get(PdfName.COLORSPACE) == null)
583                 additional.put(PdfName.COLORSPACE, getColorspace());
584             if (intent != null)
585                 additional.put(PdfName.INTENT, intent);
586             if (additional.size() > 0)
587                 img.setAdditional(additional);
588             if (icc_profile != null)
589                 img.tagICC(icc_profile);
590             if (palShades) {
591                 Image im2 = Image.getInstance(width, height, 1, 8, smask);
592                 im2.makeMask();
593                 img.setImageMask(im2);
594             }
595             if (genBWMask) {
596                 Image im2 = Image.getInstance(width, height, 1, 1, smask);
597                 im2.makeMask();
598                 img.setImageMask(im2);
599             }
600             img.setDpi(dpiX, dpiY);
601             img.setXYRatio(XYRatio);
602             img.setOriginalType(Image.ORIGINAL_PNG);
603             return img;
604         }
605         catch (Exception e) {
606             throw new ExceptionConverter(e);
607         }
608     }
609     
610     void decodeIdat() {
611         int nbitDepth = bitDepth;
612         if (nbitDepth == 16)
613             nbitDepth = 8;
614         int size = -1;
615         bytesPerPixel = (bitDepth == 16) ? 2 : 1;
616         switch (colorType) {
617             case 0:
618                 size = (nbitDepth * width + 7) / 8 * height;
619                 break;
620             case 2:
621                 size = width * 3 * height;
622                 bytesPerPixel *= 3;
623                 break;
624             case 3:
625                 if (interlaceMethod == 1)
626                     size = (nbitDepth * width + 7) / 8 * height;
627                 bytesPerPixel = 1;
628                 break;
629             case 4:
630                 size = width * height;
631                 bytesPerPixel *= 2;
632                 break;
633             case 6:
634                 size = width * 3 * height;
635                 bytesPerPixel *= 4;
636                 break;
637         }
638         if (size >= 0)
639             image = new byte[size];
640         if (palShades)
641             smask = new byte[width * height];
642         else if (genBWMask)
643             smask = new byte[(width + 7) / 8 * height];
644         ByteArrayInputStream bai = new ByteArrayInputStream(idat.getBuf(), 0, idat.size());
645         InputStream infStream = new InflaterInputStream(bai, new Inflater());
646         dataStream = new DataInputStream(infStream);
647         
648         if (interlaceMethod != 1) {
649             decodePass(0, 0, 1, 1, width, height);
650         }
651         else {
652             decodePass(0, 0, 8, 8, (width + 7)/8, (height + 7)/8);
653             decodePass(4, 0, 8, 8, (width + 3)/8, (height + 7)/8);
654             decodePass(0, 4, 4, 8, (width + 3)/4, (height + 3)/8);
655             decodePass(2, 0, 4, 4, (width + 1)/4, (height + 3)/4);
656             decodePass(0, 2, 2, 4, (width + 1)/2, (height + 1)/4);
657             decodePass(1, 0, 2, 2, width/2, (height + 1)/2);
658             decodePass(0, 1, 1, 2, width, height/2);
659         }
660         
661     }
662     
663     void decodePass( int xOffset, int yOffset,
664     int xStep, int yStep,
665     int passWidth, int passHeight) {
666         if ((passWidth == 0) || (passHeight == 0)) {
667             return;
668         }
669         
670         int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8;
671         byte[] curr = new byte[bytesPerRow];
672         byte[] prior = new byte[bytesPerRow];
673         
674         // Decode the (sub)image row-by-row
675         int srcY, dstY;
676         for (srcY = 0, dstY = yOffset;
677         srcY < passHeight;
678         srcY++, dstY += yStep) {
679             // Read the filter type byte and a row of data
680             int filter = 0;
681             try {
682                 filter = dataStream.read();
683                 dataStream.readFully(curr, 0, bytesPerRow);
684             } catch (Exception e) {
685                 // empty on purpose
686             }
687             
688             switch (filter) {
689                 case PNG_FILTER_NONE:
690                     break;
691                 case PNG_FILTER_SUB:
692                     decodeSubFilter(curr, bytesPerRow, bytesPerPixel);
693                     break;
694                 case PNG_FILTER_UP:
695                     decodeUpFilter(curr, prior, bytesPerRow);
696                     break;
697                 case PNG_FILTER_AVERAGE:
698                     decodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel);
699                     break;
700                 case PNG_FILTER_PAETH:
701                     decodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel);
702                     break;
703                 default:
704                     // Error -- uknown filter type
705                     throw new RuntimeException("PNG filter unknown.");
706             }
707             
708             processPixels(curr, xOffset, xStep, dstY, passWidth);
709             
710             // Swap curr and prior
711             byte[] tmp = prior;
712             prior = curr;
713             curr = tmp;
714         }
715     }
716     
717     void processPixels(byte curr[], int xOffset, int step, int y, int width) {
718         int srcX, dstX;
719
720         int out[] = getPixel(curr);
721         int sizes = 0;
722         switch (colorType) {
723             case 0:
724             case 3:
725             case 4:
726                 sizes = 1;
727                 break;
728             case 2:
729             case 6:
730                 sizes = 3;
731                 break;
732         }
733         if (image != null) {
734             dstX = xOffset;
735             int yStride = (sizes*this.width*(bitDepth == 16 ? 8 : bitDepth)+ 7)/8;
736             for (srcX = 0; srcX < width; srcX++) {
737                 setPixel(image, out, inputBands * srcX, sizes, dstX, y, bitDepth, yStride);
738                 dstX += step;
739             }
740         }
741         if (palShades) {
742             if ((colorType & 4) != 0) {
743                 if (bitDepth == 16) {
744                     for (int k = 0; k < width; ++k)
745                         out[k * inputBands + sizes] >>>= 8;
746                 }
747                 int yStride = this.width;
748                 dstX = xOffset;
749                 for (srcX = 0; srcX < width; srcX++) {
750                     setPixel(smask, out, inputBands * srcX + sizes, 1, dstX, y, 8, yStride);
751                     dstX += step;
752                 }
753             }
754             else { //colorType 3
755                 int yStride = this.width;
756                 int v[] = new int[1];
757                 dstX = xOffset;
758                 for (srcX = 0; srcX < width; srcX++) {
759                     int idx = out[srcX];
760                     if (idx < trans.length)
761                         v[0] = trans[idx];
762                     else
763                         v[0] = 255; // Patrick Valsecchi
764                     setPixel(smask, v, 0, 1, dstX, y, 8, yStride);
765                     dstX += step;
766                 }
767             }
768         }
769         else if (genBWMask) {
770             switch (colorType) {
771                 case 3: {
772                     int yStride = (this.width + 7) / 8;
773                     int v[] = new int[1];
774                     dstX = xOffset;
775                     for (srcX = 0; srcX < width; srcX++) {
776                         int idx = out[srcX];
777                         v[0] = ((idx < trans.length && trans[idx] == 0) ? 1 : 0);
778                         setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
779                         dstX += step;
780                     }
781                     break;
782                 }
783                 case 0: {
784                     int yStride = (this.width + 7) / 8;
785                     int v[] = new int[1];
786                     dstX = xOffset;
787                     for (srcX = 0; srcX < width; srcX++) {
788                         int g = out[srcX];
789                         v[0] = (g == transRedGray ? 1 : 0);
790                         setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
791                         dstX += step;
792                     }
793                     break;
794                 }
795                 case 2: {
796                     int yStride = (this.width + 7) / 8;
797                     int v[] = new int[1];
798                     dstX = xOffset;
799                     for (srcX = 0; srcX < width; srcX++) {
800                         int markRed = inputBands * srcX;
801                         v[0] = (out[markRed] == transRedGray && out[markRed + 1] == transGreen 
802                             && out[markRed + 2] == transBlue ? 1 : 0);
803                         setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
804                         dstX += step;
805                     }
806                     break;
807                 }
808             }
809         }
810     }
811     
812     static int getPixel(byte image[], int x, int y, int bitDepth, int bytesPerRow) {
813         if (bitDepth == 8) {
814             int pos = bytesPerRow * y + x;
815             return image[pos] & 0xff;
816         }
817         else {
818             int pos = bytesPerRow * y + x / (8 / bitDepth);
819             int v = image[pos] >> (8 - bitDepth * (x % (8 / bitDepth))- bitDepth);
820             return v & ((1 << bitDepth) - 1);
821         }
822     }
823     
824     static void setPixel(byte image[], int data[], int offset, int size, int x, int y, int bitDepth, int bytesPerRow) {
825         if (bitDepth == 8) {
826             int pos = bytesPerRow * y + size * x;
827             for (int k = 0; k < size; ++k)
828                 image[pos + k] = (byte)data[k + offset];
829         }
830         else if (bitDepth == 16) {
831             int pos = bytesPerRow * y + size * x;
832             for (int k = 0; k < size; ++k)
833                 image[pos + k] = (byte)(data[k + offset] >>> 8);
834         }
835         else {
836             int pos = bytesPerRow * y + x / (8 / bitDepth);
837             int v = data[offset] << (8 - bitDepth * (x % (8 / bitDepth))- bitDepth);
838             image[pos] |= v;
839         }
840     }
841     
842     int[] getPixel(byte curr[]) {
843         switch (bitDepth) {
844             case 8: {
845                 int out[] = new int[curr.length];
846                 for (int k = 0; k < out.length; ++k)
847                     out[k] = curr[k] & 0xff;
848                 return out;
849             }
850             case 16: {
851                 int out[] = new int[curr.length / 2];
852                 for (int k = 0; k < out.length; ++k)
853                     out[k] = ((curr[k * 2] & 0xff) << 8) + (curr[k * 2 + 1] & 0xff);
854                 return out;
855             }
856             default: {
857                 int out[] = new int[curr.length * 8 / bitDepth];
858                 int idx = 0;
859                 int passes = 8 / bitDepth;
860                 int mask = (1 << bitDepth) - 1;
861                 for (int k = 0; k < curr.length; ++k) {
862                     for (int j = passes - 1; j >= 0; --j) {
863                         out[idx++] = (curr[k] >>> (bitDepth * j)) & mask; 
864                     }
865                 }
866                 return out;
867             }
868         }
869     }
870     
871     private static void decodeSubFilter(byte[] curr, int count, int bpp) {
872         for (int i = bpp; i < count; i++) {
873             int val;
874             
875             val = curr[i] & 0xff;
876             val += curr[i - bpp] & 0xff;
877             
878             curr[i] = (byte)val;
879         }
880     }
881     
882     private static void decodeUpFilter(byte[] curr, byte[] prev,
883     int count) {
884         for (int i = 0; i < count; i++) {
885             int raw = curr[i] & 0xff;
886             int prior = prev[i] & 0xff;
887             
888             curr[i] = (byte)(raw + prior);
889         }
890     }
891     
892     private static void decodeAverageFilter(byte[] curr, byte[] prev,
893     int count, int bpp) {
894         int raw, priorPixel, priorRow;
895         
896         for (int i = 0; i < bpp; i++) {
897             raw = curr[i] & 0xff;
898             priorRow = prev[i] & 0xff;
899             
900             curr[i] = (byte)(raw + priorRow/2);
901         }
902         
903         for (int i = bpp; i < count; i++) {
904             raw = curr[i] & 0xff;
905             priorPixel = curr[i - bpp] & 0xff;
906             priorRow = prev[i] & 0xff;
907             
908             curr[i] = (byte)(raw + (priorPixel + priorRow)/2);
909         }
910     }
911     
912     private static int paethPredictor(int a, int b, int c) {
913         int p = a + b - c;
914         int pa = Math.abs(p - a);
915         int pb = Math.abs(p - b);
916         int pc = Math.abs(p - c);
917         
918         if ((pa <= pb) && (pa <= pc)) {
919             return a;
920         } else if (pb <= pc) {
921             return b;
922         } else {
923             return c;
924         }
925     }
926     
927     private static void decodePaethFilter(byte[] curr, byte[] prev,
928     int count, int bpp) {
929         int raw, priorPixel, priorRow, priorRowPixel;
930         
931         for (int i = 0; i < bpp; i++) {
932             raw = curr[i] & 0xff;
933             priorRow = prev[i] & 0xff;
934             
935             curr[i] = (byte)(raw + priorRow);
936         }
937         
938         for (int i = bpp; i < count; i++) {
939             raw = curr[i] & 0xff;
940             priorPixel = curr[i - bpp] & 0xff;
941             priorRow = prev[i] & 0xff;
942             priorRowPixel = prev[i - bpp] & 0xff;
943             
944             curr[i] = (byte)(raw + paethPredictor(priorPixel,
945             priorRow,
946             priorRowPixel));
947         }
948     }
949     
950     static class NewByteArrayOutputStream extends ByteArrayOutputStream {
951         public byte[] getBuf() {
952             return buf;
953         }
954     }
955
956 /**
957  * Gets an <CODE>int</CODE> from an <CODE>InputStream</CODE>.
958  *
959  * @param        is      an <CODE>InputStream</CODE>
960  * @return        the value of an <CODE>int</CODE>
961  */

962     
963     public static final int getInt(InputStream is) throws IOException {
964         return (is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read();
965     }
966     
967 /**
968  * Gets a <CODE>word</CODE> from an <CODE>InputStream</CODE>.
969  *
970  * @param        is      an <CODE>InputStream</CODE>
971  * @return        the value of an <CODE>int</CODE>
972  */

973     
974     public static final int getWord(InputStream is) throws IOException {
975         return (is.read() << 8) + is.read();
976     }
977     
978 /**
979  * Gets a <CODE>String</CODE> from an <CODE>InputStream</CODE>.
980  *
981  * @param        is      an <CODE>InputStream</CODE>
982  * @return        the value of an <CODE>int</CODE>
983  */

984     
985     public static final String getString(InputStream is) throws IOException {
986         StringBuffer buf = new StringBuffer();
987         for (int i = 0; i < 4; i++) {
988             buf.append((char)is.read());
989         }
990         return buf.toString();
991     }
992
993 }
994