001/*
002 * $Id: PngImage.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044
045/*
046 * This code is based on a series of source files originally released
047 * by SUN in the context of the JAI project. The original code was released 
048 * under the BSD license in a specific wording. In a mail dating from
049 * January 23, 2008, Brian Burkhalter (@sun.com) gave us permission
050 * to use the code under the following version of the BSD license:
051 *
052 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
053 * 
054 * Redistribution and use in source and binary forms, with or without
055 * modification, are permitted provided that the following conditions
056 * are met: 
057 * 
058 * - Redistribution of source code must retain the above copyright 
059 *   notice, this  list of conditions and the following disclaimer.
060 * 
061 * - Redistribution in binary form must reproduce the above copyright
062 *   notice, this list of conditions and the following disclaimer in 
063 *   the documentation and/or other materials provided with the
064 *   distribution.
065 * 
066 * Neither the name of Sun Microsystems, Inc. or the names of 
067 * contributors may be used to endorse or promote products derived 
068 * from this software without specific prior written permission.
069 * 
070 * This software is provided "AS IS," without a warranty of any 
071 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
072 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
073 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
074 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
075 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
076 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
077 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
078 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
079 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
080 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
081 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
082 * POSSIBILITY OF SUCH DAMAGES. 
083 * 
084 * You acknowledge that this software is not designed or intended for 
085 * use in the design, construction, operation or maintenance of any 
086 * nuclear facility.
087 */
088
089package com.itextpdf.text.pdf.codec;
090
091import com.itextpdf.text.pdf.ICC_Profile;
092import java.io.ByteArrayInputStream;
093import java.io.ByteArrayOutputStream;
094import java.io.DataInputStream;
095import java.io.IOException;
096import java.io.InputStream;
097import java.net.URL;
098import java.util.zip.Inflater;
099import java.util.zip.InflaterInputStream;
100import com.itextpdf.text.error_messages.MessageLocalization;
101
102import com.itextpdf.text.ExceptionConverter;
103import com.itextpdf.text.Image;
104import com.itextpdf.text.ImgRaw;
105import com.itextpdf.text.Utilities;
106import com.itextpdf.text.pdf.ByteBuffer;
107import com.itextpdf.text.pdf.PdfArray;
108import com.itextpdf.text.pdf.PdfDictionary;
109import com.itextpdf.text.pdf.PdfLiteral;
110import com.itextpdf.text.pdf.PdfName;
111import com.itextpdf.text.pdf.PdfNumber;
112import com.itextpdf.text.pdf.PdfObject;
113import com.itextpdf.text.pdf.PdfReader;
114import com.itextpdf.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
121 */
122public 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(MessageLocalization.getComposedMessage("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(MessageLocalization.getComposedMessage("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(MessageLocalization.getComposedMessage("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(MessageLocalization.getComposedMessage("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}