aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/lowagie/text/pdf/codec/PngImage.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/lowagie/text/pdf/codec/PngImage.java')
-rw-r--r--src/main/java/com/lowagie/text/pdf/codec/PngImage.java987
1 files changed, 987 insertions, 0 deletions
diff --git a/src/main/java/com/lowagie/text/pdf/codec/PngImage.java b/src/main/java/com/lowagie/text/pdf/codec/PngImage.java
new file mode 100644
index 0000000..b82e17b
--- /dev/null
+++ b/src/main/java/com/lowagie/text/pdf/codec/PngImage.java
@@ -0,0 +1,987 @@
+/*
+ * Copyright 2003 by Paulo Soares.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version 1.1
+ * (the "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the License.
+ *
+ * The Original Code is 'iText, a free JAVA-PDF library'.
+ *
+ * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
+ * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
+ * All Rights Reserved.
+ * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
+ * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
+ *
+ * Contributor(s): all the names of the contributors are added in the source code
+ * where applicable.
+ *
+ * Alternatively, the contents of this file may be used under the terms of the
+ * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
+ * provisions of LGPL are applicable instead of those above. If you wish to
+ * allow use of your version of this file only under the terms of the LGPL
+ * License and not to allow others to use your version of this file under
+ * the MPL, indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by the LGPL.
+ * If you do not delete the provisions above, a recipient may use your version
+ * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the MPL as stated above or under the terms of the GNU
+ * Library General Public License as published by the Free Software Foundation;
+ * either version 2 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
+ * details.
+ *
+ * If you didn't download this code from the following link, you should check if
+ * you aren't using an obsolete version:
+ * http://www.lowagie.com/iText/
+ *
+ *
+ * The original JAI codecs have the following license
+ *
+ * Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduct the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
+ * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
+ * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
+ * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
+ * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
+ * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
+ * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
+ * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
+ * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that Software is not designed,licensed or intended for use in
+ * the design, construction, operation or maintenance of any nuclear facility.
+ */
+
+package com.lowagie.text.pdf.codec;
+
+import java.awt.color.ICC_Profile;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import com.lowagie.text.ExceptionConverter;
+import com.lowagie.text.Image;
+import com.lowagie.text.ImgRaw;
+import com.lowagie.text.pdf.ByteBuffer;
+import com.lowagie.text.pdf.PdfArray;
+import com.lowagie.text.pdf.PdfDictionary;
+import com.lowagie.text.pdf.PdfLiteral;
+import com.lowagie.text.pdf.PdfName;
+import com.lowagie.text.pdf.PdfNumber;
+import com.lowagie.text.pdf.PdfObject;
+import com.lowagie.text.pdf.PdfReader;
+import com.lowagie.text.pdf.PdfString;
+
+/** Reads a PNG image. All types of PNG can be read.
+ * <p>
+ * It is based in part in the JAI codec.
+ *
+ * @author Paulo Soares (psoares@consiste.pt)
+ */
+public class PngImage {
+/** Some PNG specific values. */
+ public static final int[] PNGID = {137, 80, 78, 71, 13, 10, 26, 10};
+
+/** A PNG marker. */
+ public static final String IHDR = "IHDR";
+
+/** A PNG marker. */
+ public static final String PLTE = "PLTE";
+
+/** A PNG marker. */
+ public static final String IDAT = "IDAT";
+
+/** A PNG marker. */
+ public static final String IEND = "IEND";
+
+/** A PNG marker. */
+ public static final String tRNS = "tRNS";
+
+/** A PNG marker. */
+ public static final String pHYs = "pHYs";
+
+/** A PNG marker. */
+ public static final String gAMA = "gAMA";
+
+/** A PNG marker. */
+ public static final String cHRM = "cHRM";
+
+/** A PNG marker. */
+ public static final String sRGB = "sRGB";
+
+/** A PNG marker. */
+ public static final String iCCP = "iCCP";
+
+ private static final int TRANSFERSIZE = 4096;
+ private static final int PNG_FILTER_NONE = 0;
+ private static final int PNG_FILTER_SUB = 1;
+ private static final int PNG_FILTER_UP = 2;
+ private static final int PNG_FILTER_AVERAGE = 3;
+ private static final int PNG_FILTER_PAETH = 4;
+ private static final PdfName intents[] = {PdfName.PERCEPTUAL,
+ PdfName.RELATIVECALORIMETRIC,PdfName.SATURATION,PdfName.ABSOLUTECALORIMETRIC};
+
+ InputStream is;
+ DataInputStream dataStream;
+ int width;
+ int height;
+ int bitDepth;
+ int colorType;
+ int compressionMethod;
+ int filterMethod;
+ int interlaceMethod;
+ PdfDictionary additional = new PdfDictionary();
+ byte image[];
+ byte smask[];
+ byte trans[];
+ NewByteArrayOutputStream idat = new NewByteArrayOutputStream();
+ int dpiX;
+ int dpiY;
+ float XYRatio;
+ boolean genBWMask;
+ boolean palShades;
+ int transRedGray = -1;
+ int transGreen = -1;
+ int transBlue = -1;
+ int inputBands;
+ int bytesPerPixel; // number of bytes per input pixel
+ byte colorTable[];
+ float gamma = 1f;
+ boolean hasCHRM = false;
+ float xW, yW, xR, yR, xG, yG, xB, yB;
+ PdfName intent;
+ ICC_Profile icc_profile;
+
+
+
+ /** Creates a new instance of PngImage */
+ PngImage(InputStream is) {
+ this.is = is;
+ }
+
+ /** Reads a PNG from an url.
+ * @param url the url
+ * @throws IOException on error
+ * @return the image
+ */
+ public static Image getImage(URL url) throws IOException {
+ InputStream is = null;
+ try {
+ is = url.openStream();
+ Image img = getImage(is);
+ img.setUrl(url);
+ return img;
+ }
+ finally {
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ /** Reads a PNG from a stream.
+ * @param is the stream
+ * @throws IOException on error
+ * @return the image
+ */
+ public static Image getImage(InputStream is) throws IOException {
+ PngImage png = new PngImage(is);
+ return png.getImage();
+ }
+
+ /** Reads a PNG from a file.
+ * @param file the file
+ * @throws IOException on error
+ * @return the image
+ */
+ public static Image getImage(String file) throws IOException {
+ return getImage(Image.toURL(file));
+ }
+
+ /** Reads a PNG from a byte array.
+ * @param data the byte array
+ * @throws IOException on error
+ * @return the image
+ */
+ public static Image getImage(byte data[]) throws IOException {
+ InputStream is = null;
+ try {
+ is = new ByteArrayInputStream(data);
+ Image img = getImage(is);
+ img.setOriginalData(data);
+ return img;
+ }
+ finally {
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ boolean checkMarker(String s) {
+ if (s.length() != 4)
+ return false;
+ for (int k = 0; k < 4; ++k) {
+ char c = s.charAt(k);
+ if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z'))
+ return false;
+ }
+ return true;
+ }
+
+ void readPng() throws IOException {
+ for (int i = 0; i < PNGID.length; i++) {
+ if (PNGID[i] != is.read()) {
+ throw new IOException("File is not a valid PNG.");
+ }
+ }
+ byte buffer[] = new byte[TRANSFERSIZE];
+ while (true) {
+ int len = getInt(is);
+ String marker = getString(is);
+ if (len < 0 || !checkMarker(marker))
+ throw new IOException("Corrupted PNG file.");
+ if (IDAT.equals(marker)) {
+ int size;
+ while (len != 0) {
+ size = is.read(buffer, 0, Math.min(len, TRANSFERSIZE));
+ if (size < 0)
+ return;
+ idat.write(buffer, 0, size);
+ len -= size;
+ }
+ }
+ else if (tRNS.equals(marker)) {
+ switch (colorType) {
+ case 0:
+ if (len >= 2) {
+ len -= 2;
+ int gray = getWord(is);
+ if (bitDepth == 16)
+ transRedGray = gray;
+ else
+ additional.put(PdfName.MASK, new PdfLiteral("["+gray+" "+gray+"]"));
+ }
+ break;
+ case 2:
+ if (len >= 6) {
+ len -= 6;
+ int red = getWord(is);
+ int green = getWord(is);
+ int blue = getWord(is);
+ if (bitDepth == 16) {
+ transRedGray = red;
+ transGreen = green;
+ transBlue = blue;
+ }
+ else
+ additional.put(PdfName.MASK, new PdfLiteral("["+red+" "+red+" "+green+" "+green+" "+blue+" "+blue+"]"));
+ }
+ break;
+ case 3:
+ if (len > 0) {
+ trans = new byte[len];
+ for (int k = 0; k < len; ++k)
+ trans[k] = (byte)is.read();
+ len = 0;
+ }
+ break;
+ }
+ Image.skip(is, len);
+ }
+ else if (IHDR.equals(marker)) {
+ width = getInt(is);
+ height = getInt(is);
+
+ bitDepth = is.read();
+ colorType = is.read();
+ compressionMethod = is.read();
+ filterMethod = is.read();
+ interlaceMethod = is.read();
+ }
+ else if (PLTE.equals(marker)) {
+ if (colorType == 3) {
+ PdfArray colorspace = new PdfArray();
+ colorspace.add(PdfName.INDEXED);
+ colorspace.add(getColorspace());
+ colorspace.add(new PdfNumber(len / 3 - 1));
+ ByteBuffer colortable = new ByteBuffer();
+ while ((len--) > 0) {
+ colortable.append_i(is.read());
+ }
+ colorspace.add(new PdfString(colorTable = colortable.toByteArray()));
+ additional.put(PdfName.COLORSPACE, colorspace);
+ }
+ else {
+ Image.skip(is, len);
+ }
+ }
+ else if (pHYs.equals(marker)) {
+ int dx = getInt(is);
+ int dy = getInt(is);
+ int unit = is.read();
+ if (unit == 1) {
+ dpiX = (int)((float)dx * 0.0254f);
+ dpiY = (int)((float)dy * 0.0254f);
+ }
+ else {
+ if (dy != 0)
+ XYRatio = (float)dx / (float)dy;
+ }
+ }
+ else if (cHRM.equals(marker)) {
+ xW = (float)getInt(is) / 100000f;
+ yW = (float)getInt(is) / 100000f;
+ xR = (float)getInt(is) / 100000f;
+ yR = (float)getInt(is) / 100000f;
+ xG = (float)getInt(is) / 100000f;
+ yG = (float)getInt(is) / 100000f;
+ xB = (float)getInt(is) / 100000f;
+ yB = (float)getInt(is) / 100000f;
+ 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);
+ }
+ else if (sRGB.equals(marker)) {
+ int ri = is.read();
+ intent = intents[ri];
+ gamma = 2.2f;
+ xW = 0.3127f;
+ yW = 0.329f;
+ xR = 0.64f;
+ yR = 0.33f;
+ xG = 0.3f;
+ yG = 0.6f;
+ xB = 0.15f;
+ yB = 0.06f;
+ hasCHRM = true;
+ }
+ else if (gAMA.equals(marker)) {
+ int gm = getInt(is);
+ if (gm != 0) {
+ gamma = 100000f / (float)gm;
+ if (!hasCHRM) {
+ xW = 0.3127f;
+ yW = 0.329f;
+ xR = 0.64f;
+ yR = 0.33f;
+ xG = 0.3f;
+ yG = 0.6f;
+ xB = 0.15f;
+ yB = 0.06f;
+ hasCHRM = true;
+ }
+ }
+ }
+ else if (iCCP.equals(marker)) {
+ do {
+ --len;
+ } while (is.read() != 0);
+ is.read();
+ --len;
+ byte icccom[] = new byte[len];
+ int p = 0;
+ while (len > 0) {
+ int r = is.read(icccom, p, len);
+ if (r < 0)
+ throw new IOException("Premature end of file.");
+ p += r;
+ len -= r;
+ }
+ byte iccp[] = PdfReader.FlateDecode(icccom, true);
+ icccom = null;
+ try {
+ icc_profile = ICC_Profile.getInstance(iccp);
+ }
+ catch (Exception e) {
+ icc_profile = null;
+ }
+ }
+ else if (IEND.equals(marker)) {
+ break;
+ }
+ else {
+ Image.skip(is, len);
+ }
+ Image.skip(is, 4);
+ }
+ }
+
+ PdfObject getColorspace() {
+ if (icc_profile != null) {
+ if ((colorType & 2) == 0)
+ return PdfName.DEVICEGRAY;
+ else
+ return PdfName.DEVICERGB;
+ }
+ if (gamma == 1f && !hasCHRM) {
+ if ((colorType & 2) == 0)
+ return PdfName.DEVICEGRAY;
+ else
+ return PdfName.DEVICERGB;
+ }
+ else {
+ PdfArray array = new PdfArray();
+ PdfDictionary dic = new PdfDictionary();
+ if ((colorType & 2) == 0) {
+ if (gamma == 1f)
+ return PdfName.DEVICEGRAY;
+ array.add(PdfName.CALGRAY);
+ dic.put(PdfName.GAMMA, new PdfNumber(gamma));
+ dic.put(PdfName.WHITEPOINT, new PdfLiteral("[1 1 1]"));
+ array.add(dic);
+ }
+ else {
+ PdfObject wp = new PdfLiteral("[1 1 1]");
+ array.add(PdfName.CALRGB);
+ if (gamma != 1f) {
+ PdfArray gm = new PdfArray();
+ PdfNumber n = new PdfNumber(gamma);
+ gm.add(n);
+ gm.add(n);
+ gm.add(n);
+ dic.put(PdfName.GAMMA, gm);
+ }
+ if (hasCHRM) {
+ float z = yW*((xG-xB)*yR-(xR-xB)*yG+(xR-xG)*yB);
+ float YA = yR*((xG-xB)*yW-(xW-xB)*yG+(xW-xG)*yB)/z;
+ float XA = YA*xR/yR;
+ float ZA = YA*((1-xR)/yR-1);
+ float YB = -yG*((xR-xB)*yW-(xW-xB)*yR+(xW-xR)*yB)/z;
+ float XB = YB*xG/yG;
+ float ZB = YB*((1-xG)/yG-1);
+ float YC = yB*((xR-xG)*yW-(xW-xG)*yW+(xW-xR)*yG)/z;
+ float XC = YC*xB/yB;
+ float ZC = YC*((1-xB)/yB-1);
+ float XW = XA+XB+XC;
+ float YW = 1;//YA+YB+YC;
+ float ZW = ZA+ZB+ZC;
+ PdfArray wpa = new PdfArray();
+ wpa.add(new PdfNumber(XW));
+ wpa.add(new PdfNumber(YW));
+ wpa.add(new PdfNumber(ZW));
+ wp = wpa;
+ PdfArray matrix = new PdfArray();
+ matrix.add(new PdfNumber(XA));
+ matrix.add(new PdfNumber(YA));
+ matrix.add(new PdfNumber(ZA));
+ matrix.add(new PdfNumber(XB));
+ matrix.add(new PdfNumber(YB));
+ matrix.add(new PdfNumber(ZB));
+ matrix.add(new PdfNumber(XC));
+ matrix.add(new PdfNumber(YC));
+ matrix.add(new PdfNumber(ZC));
+ dic.put(PdfName.MATRIX, matrix);
+ }
+ dic.put(PdfName.WHITEPOINT, wp);
+ array.add(dic);
+ }
+ return array;
+ }
+ }
+
+ Image getImage() throws IOException {
+ readPng();
+ try {
+ int pal0 = 0;
+ int palIdx = 0;
+ palShades = false;
+ if (trans != null) {
+ for (int k = 0; k < trans.length; ++k) {
+ int n = trans[k] & 0xff;
+ if (n == 0) {
+ ++pal0;
+ palIdx = k;
+ }
+ if (n != 0 && n != 255) {
+ palShades = true;
+ break;
+ }
+ }
+ }
+ if ((colorType & 4) != 0)
+ palShades = true;
+ genBWMask = (!palShades && (pal0 > 1 || transRedGray >= 0));
+ if (!palShades && !genBWMask && pal0 == 1) {
+ additional.put(PdfName.MASK, new PdfLiteral("["+palIdx+" "+palIdx+"]"));
+ }
+ boolean needDecode = (interlaceMethod == 1) || (bitDepth == 16) || ((colorType & 4) != 0) || palShades || genBWMask;
+ switch (colorType) {
+ case 0:
+ inputBands = 1;
+ break;
+ case 2:
+ inputBands = 3;
+ break;
+ case 3:
+ inputBands = 1;
+ break;
+ case 4:
+ inputBands = 2;
+ break;
+ case 6:
+ inputBands = 4;
+ break;
+ }
+ if (needDecode)
+ decodeIdat();
+ int components = inputBands;
+ if ((colorType & 4) != 0)
+ --components;
+ int bpc = bitDepth;
+ if (bpc == 16)
+ bpc = 8;
+ Image img;
+ if (image != null)
+ img = Image.getInstance(width, height, components, bpc, image);
+ else {
+ img = new ImgRaw(width, height, components, bpc, idat.toByteArray());
+ img.setDeflated(true);
+ PdfDictionary decodeparms = new PdfDictionary();
+ decodeparms.put(PdfName.BITSPERCOMPONENT, new PdfNumber(bitDepth));
+ decodeparms.put(PdfName.PREDICTOR, new PdfNumber(15));
+ decodeparms.put(PdfName.COLUMNS, new PdfNumber(width));
+ decodeparms.put(PdfName.COLORS, new PdfNumber((colorType == 3 || (colorType & 2) == 0) ? 1 : 3));
+ additional.put(PdfName.DECODEPARMS, decodeparms);
+ }
+ if (additional.get(PdfName.COLORSPACE) == null)
+ additional.put(PdfName.COLORSPACE, getColorspace());
+ if (intent != null)
+ additional.put(PdfName.INTENT, intent);
+ if (additional.size() > 0)
+ img.setAdditional(additional);
+ if (icc_profile != null)
+ img.tagICC(icc_profile);
+ if (palShades) {
+ Image im2 = Image.getInstance(width, height, 1, 8, smask);
+ im2.makeMask();
+ img.setImageMask(im2);
+ }
+ if (genBWMask) {
+ Image im2 = Image.getInstance(width, height, 1, 1, smask);
+ im2.makeMask();
+ img.setImageMask(im2);
+ }
+ img.setDpi(dpiX, dpiY);
+ img.setXYRatio(XYRatio);
+ img.setOriginalType(Image.ORIGINAL_PNG);
+ return img;
+ }
+ catch (Exception e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+
+ void decodeIdat() {
+ int nbitDepth = bitDepth;
+ if (nbitDepth == 16)
+ nbitDepth = 8;
+ int size = -1;
+ bytesPerPixel = (bitDepth == 16) ? 2 : 1;
+ switch (colorType) {
+ case 0:
+ size = (nbitDepth * width + 7) / 8 * height;
+ break;
+ case 2:
+ size = width * 3 * height;
+ bytesPerPixel *= 3;
+ break;
+ case 3:
+ if (interlaceMethod == 1)
+ size = (nbitDepth * width + 7) / 8 * height;
+ bytesPerPixel = 1;
+ break;
+ case 4:
+ size = width * height;
+ bytesPerPixel *= 2;
+ break;
+ case 6:
+ size = width * 3 * height;
+ bytesPerPixel *= 4;
+ break;
+ }
+ if (size >= 0)
+ image = new byte[size];
+ if (palShades)
+ smask = new byte[width * height];
+ else if (genBWMask)
+ smask = new byte[(width + 7) / 8 * height];
+ ByteArrayInputStream bai = new ByteArrayInputStream(idat.getBuf(), 0, idat.size());
+ InputStream infStream = new InflaterInputStream(bai, new Inflater());
+ dataStream = new DataInputStream(infStream);
+
+ if (interlaceMethod != 1) {
+ decodePass(0, 0, 1, 1, width, height);
+ }
+ else {
+ decodePass(0, 0, 8, 8, (width + 7)/8, (height + 7)/8);
+ decodePass(4, 0, 8, 8, (width + 3)/8, (height + 7)/8);
+ decodePass(0, 4, 4, 8, (width + 3)/4, (height + 3)/8);
+ decodePass(2, 0, 4, 4, (width + 1)/4, (height + 3)/4);
+ decodePass(0, 2, 2, 4, (width + 1)/2, (height + 1)/4);
+ decodePass(1, 0, 2, 2, width/2, (height + 1)/2);
+ decodePass(0, 1, 1, 2, width, height/2);
+ }
+
+ }
+
+ void decodePass( int xOffset, int yOffset,
+ int xStep, int yStep,
+ int passWidth, int passHeight) {
+ if ((passWidth == 0) || (passHeight == 0)) {
+ return;
+ }
+
+ int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8;
+ byte[] curr = new byte[bytesPerRow];
+ byte[] prior = new byte[bytesPerRow];
+
+ // Decode the (sub)image row-by-row
+ int srcY, dstY;
+ for (srcY = 0, dstY = yOffset;
+ srcY < passHeight;
+ srcY++, dstY += yStep) {
+ // Read the filter type byte and a row of data
+ int filter = 0;
+ try {
+ filter = dataStream.read();
+ dataStream.readFully(curr, 0, bytesPerRow);
+ } catch (Exception e) {
+ // empty on purpose
+ }
+
+ switch (filter) {
+ case PNG_FILTER_NONE:
+ break;
+ case PNG_FILTER_SUB:
+ decodeSubFilter(curr, bytesPerRow, bytesPerPixel);
+ break;
+ case PNG_FILTER_UP:
+ decodeUpFilter(curr, prior, bytesPerRow);
+ break;
+ case PNG_FILTER_AVERAGE:
+ decodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel);
+ break;
+ case PNG_FILTER_PAETH:
+ decodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel);
+ break;
+ default:
+ // Error -- uknown filter type
+ throw new RuntimeException("PNG filter unknown.");
+ }
+
+ processPixels(curr, xOffset, xStep, dstY, passWidth);
+
+ // Swap curr and prior
+ byte[] tmp = prior;
+ prior = curr;
+ curr = tmp;
+ }
+ }
+
+ void processPixels(byte curr[], int xOffset, int step, int y, int width) {
+ int srcX, dstX;
+
+ int out[] = getPixel(curr);
+ int sizes = 0;
+ switch (colorType) {
+ case 0:
+ case 3:
+ case 4:
+ sizes = 1;
+ break;
+ case 2:
+ case 6:
+ sizes = 3;
+ break;
+ }
+ if (image != null) {
+ dstX = xOffset;
+ int yStride = (sizes*this.width*(bitDepth == 16 ? 8 : bitDepth)+ 7)/8;
+ for (srcX = 0; srcX < width; srcX++) {
+ setPixel(image, out, inputBands * srcX, sizes, dstX, y, bitDepth, yStride);
+ dstX += step;
+ }
+ }
+ if (palShades) {
+ if ((colorType & 4) != 0) {
+ if (bitDepth == 16) {
+ for (int k = 0; k < width; ++k)
+ out[k * inputBands + sizes] >>>= 8;
+ }
+ int yStride = this.width;
+ dstX = xOffset;
+ for (srcX = 0; srcX < width; srcX++) {
+ setPixel(smask, out, inputBands * srcX + sizes, 1, dstX, y, 8, yStride);
+ dstX += step;
+ }
+ }
+ else { //colorType 3
+ int yStride = this.width;
+ int v[] = new int[1];
+ dstX = xOffset;
+ for (srcX = 0; srcX < width; srcX++) {
+ int idx = out[srcX];
+ if (idx < trans.length)
+ v[0] = trans[idx];
+ setPixel(smask, v, 0, 1, dstX, y, 8, yStride);
+ dstX += step;
+ }
+ }
+ }
+ else if (genBWMask) {
+ switch (colorType) {
+ case 3: {
+ int yStride = (this.width + 7) / 8;
+ int v[] = new int[1];
+ dstX = xOffset;
+ for (srcX = 0; srcX < width; srcX++) {
+ int idx = out[srcX];
+ if (idx < trans.length)
+ v[0] = (trans[idx] == 0 ? 1 : 0);
+ setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
+ dstX += step;
+ }
+ break;
+ }
+ case 0: {
+ int yStride = (this.width + 7) / 8;
+ int v[] = new int[1];
+ dstX = xOffset;
+ for (srcX = 0; srcX < width; srcX++) {
+ int g = out[srcX];
+ v[0] = (g == transRedGray ? 1 : 0);
+ setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
+ dstX += step;
+ }
+ break;
+ }
+ case 2: {
+ int yStride = (this.width + 7) / 8;
+ int v[] = new int[1];
+ dstX = xOffset;
+ for (srcX = 0; srcX < width; srcX++) {
+ int markRed = inputBands * srcX;
+ v[0] = (out[markRed] == transRedGray && out[markRed + 1] == transGreen
+ && out[markRed + 2] == transBlue ? 1 : 0);
+ setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
+ dstX += step;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ static int getPixel(byte image[], int x, int y, int bitDepth, int bytesPerRow) {
+ if (bitDepth == 8) {
+ int pos = bytesPerRow * y + x;
+ return image[pos] & 0xff;
+ }
+ else {
+ int pos = bytesPerRow * y + x / (8 / bitDepth);
+ int v = image[pos] >> (8 - bitDepth * (x % (8 / bitDepth))- bitDepth);
+ return v & ((1 << bitDepth) - 1);
+ }
+ }
+
+ static void setPixel(byte image[], int data[], int offset, int size, int x, int y, int bitDepth, int bytesPerRow) {
+ if (bitDepth == 8) {
+ int pos = bytesPerRow * y + size * x;
+ for (int k = 0; k < size; ++k)
+ image[pos + k] = (byte)data[k + offset];
+ }
+ else if (bitDepth == 16) {
+ int pos = bytesPerRow * y + size * x;
+ for (int k = 0; k < size; ++k)
+ image[pos + k] = (byte)(data[k + offset] >>> 8);
+ }
+ else {
+ int pos = bytesPerRow * y + x / (8 / bitDepth);
+ int v = data[offset] << (8 - bitDepth * (x % (8 / bitDepth))- bitDepth);
+ image[pos] |= v;
+ }
+ }
+
+ int[] getPixel(byte curr[]) {
+ switch (bitDepth) {
+ case 8: {
+ int out[] = new int[curr.length];
+ for (int k = 0; k < out.length; ++k)
+ out[k] = curr[k] & 0xff;
+ return out;
+ }
+ case 16: {
+ int out[] = new int[curr.length / 2];
+ for (int k = 0; k < out.length; ++k)
+ out[k] = ((curr[k * 2] & 0xff) << 8) + (curr[k * 2 + 1] & 0xff);
+ return out;
+ }
+ default: {
+ int out[] = new int[curr.length * 8 / bitDepth];
+ int idx = 0;
+ int passes = 8 / bitDepth;
+ int mask = (1 << bitDepth) - 1;
+ for (int k = 0; k < curr.length; ++k) {
+ for (int j = passes - 1; j >= 0; --j) {
+ out[idx++] = (curr[k] >>> (bitDepth * j)) & mask;
+ }
+ }
+ return out;
+ }
+ }
+ }
+
+ private static void decodeSubFilter(byte[] curr, int count, int bpp) {
+ for (int i = bpp; i < count; i++) {
+ int val;
+
+ val = curr[i] & 0xff;
+ val += curr[i - bpp] & 0xff;
+
+ curr[i] = (byte)val;
+ }
+ }
+
+ private static void decodeUpFilter(byte[] curr, byte[] prev,
+ int count) {
+ for (int i = 0; i < count; i++) {
+ int raw = curr[i] & 0xff;
+ int prior = prev[i] & 0xff;
+
+ curr[i] = (byte)(raw + prior);
+ }
+ }
+
+ private static void decodeAverageFilter(byte[] curr, byte[] prev,
+ int count, int bpp) {
+ int raw, priorPixel, priorRow;
+
+ for (int i = 0; i < bpp; i++) {
+ raw = curr[i] & 0xff;
+ priorRow = prev[i] & 0xff;
+
+ curr[i] = (byte)(raw + priorRow/2);
+ }
+
+ for (int i = bpp; i < count; i++) {
+ raw = curr[i] & 0xff;
+ priorPixel = curr[i - bpp] & 0xff;
+ priorRow = prev[i] & 0xff;
+
+ curr[i] = (byte)(raw + (priorPixel + priorRow)/2);
+ }
+ }
+
+ private static int paethPredictor(int a, int b, int c) {
+ int p = a + b - c;
+ int pa = Math.abs(p - a);
+ int pb = Math.abs(p - b);
+ int pc = Math.abs(p - c);
+
+ if ((pa <= pb) && (pa <= pc)) {
+ return a;
+ } else if (pb <= pc) {
+ return b;
+ } else {
+ return c;
+ }
+ }
+
+ private static void decodePaethFilter(byte[] curr, byte[] prev,
+ int count, int bpp) {
+ int raw, priorPixel, priorRow, priorRowPixel;
+
+ for (int i = 0; i < bpp; i++) {
+ raw = curr[i] & 0xff;
+ priorRow = prev[i] & 0xff;
+
+ curr[i] = (byte)(raw + priorRow);
+ }
+
+ for (int i = bpp; i < count; i++) {
+ raw = curr[i] & 0xff;
+ priorPixel = curr[i - bpp] & 0xff;
+ priorRow = prev[i] & 0xff;
+ priorRowPixel = prev[i - bpp] & 0xff;
+
+ curr[i] = (byte)(raw + paethPredictor(priorPixel,
+ priorRow,
+ priorRowPixel));
+ }
+ }
+
+ static class NewByteArrayOutputStream extends ByteArrayOutputStream {
+ public byte[] getBuf() {
+ return buf;
+ }
+ }
+
+/**
+ * Gets an <CODE>int</CODE> from an <CODE>InputStream</CODE>.
+ *
+ * @param is an <CODE>InputStream</CODE>
+ * @return the value of an <CODE>int</CODE>
+ */
+
+ public static final int getInt(InputStream is) throws IOException {
+ return (is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read();
+ }
+
+/**
+ * Gets a <CODE>word</CODE> from an <CODE>InputStream</CODE>.
+ *
+ * @param is an <CODE>InputStream</CODE>
+ * @return the value of an <CODE>int</CODE>
+ */
+
+ public static final int getWord(InputStream is) throws IOException {
+ return (is.read() << 8) + is.read();
+ }
+
+/**
+ * Gets a <CODE>String</CODE> from an <CODE>InputStream</CODE>.
+ *
+ * @param is an <CODE>InputStream</CODE>
+ * @return the value of an <CODE>int</CODE>
+ */
+
+ public static final String getString(InputStream is) throws IOException {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < 4; i++) {
+ buf.append((char)is.read());
+ }
+ return buf.toString();
+ }
+
+}