001: /*
002: * Copyright 2003 by Paulo Soares.
003: *
004: * The contents of this file are subject to the Mozilla Public License Version 1.1
005: * (the "License"); you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
007: *
008: * Software distributed under the License is distributed on an "AS IS" basis,
009: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
010: * for the specific language governing rights and limitations under the License.
011: *
012: * The Original Code is 'iText, a free JAVA-PDF library'.
013: *
014: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
015: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
016: * All Rights Reserved.
017: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
018: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
019: *
020: * Contributor(s): all the names of the contributors are added in the source code
021: * where applicable.
022: *
023: * Alternatively, the contents of this file may be used under the terms of the
024: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
025: * provisions of LGPL are applicable instead of those above. If you wish to
026: * allow use of your version of this file only under the terms of the LGPL
027: * License and not to allow others to use your version of this file under
028: * the MPL, indicate your decision by deleting the provisions above and
029: * replace them with the notice and other provisions required by the LGPL.
030: * If you do not delete the provisions above, a recipient may use your version
031: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
032: *
033: * This library is free software; you can redistribute it and/or modify it
034: * under the terms of the MPL as stated above or under the terms of the GNU
035: * Library General Public License as published by the Free Software Foundation;
036: * either version 2 of the License, or any later version.
037: *
038: * This library is distributed in the hope that it will be useful, but WITHOUT
039: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
040: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
041: * details.
042: *
043: * If you didn't download this code from the following link, you should check if
044: * you aren't using an obsolete version:
045: * http://www.lowagie.com/iText/
046: *
047: *
048: * The original JAI codecs have the following license
049: *
050: * Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
051: *
052: * Redistribution and use in source and binary forms, with or without
053: * modification, are permitted provided that the following conditions are met:
054: *
055: * -Redistributions of source code must retain the above copyright notice, this
056: * list of conditions and the following disclaimer.
057: *
058: * -Redistribution in binary form must reproduct the above copyright notice,
059: * this list of conditions and the following disclaimer in the documentation
060: * and/or other materials provided with the distribution.
061: *
062: * Neither the name of Sun Microsystems, Inc. or the names of contributors may
063: * be used to endorse or promote products derived from this software without
064: * specific prior written permission.
065: *
066: * This software is provided "AS IS," without a warranty of any kind. ALL
067: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
068: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
069: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
070: * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
071: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
072: * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
073: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
074: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
075: * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
076: * POSSIBILITY OF SUCH DAMAGES.
077: *
078: * You acknowledge that Software is not designed,licensed or intended for use in
079: * the design, construction, operation or maintenance of any nuclear facility.
080: */
081:
082: package com.lowagie.text.pdf.codec;
083:
084: import java.awt.color.ICC_Profile;
085: import java.io.ByteArrayInputStream;
086: import java.io.ByteArrayOutputStream;
087: import java.io.DataInputStream;
088: import java.io.IOException;
089: import java.io.InputStream;
090: import java.net.URL;
091: import java.util.zip.Inflater;
092: import java.util.zip.InflaterInputStream;
093:
094: import com.lowagie.text.ExceptionConverter;
095: import com.lowagie.text.Image;
096: import com.lowagie.text.ImgRaw;
097: import com.lowagie.text.Utilities;
098: import com.lowagie.text.pdf.ByteBuffer;
099: import com.lowagie.text.pdf.PdfArray;
100: import com.lowagie.text.pdf.PdfDictionary;
101: import com.lowagie.text.pdf.PdfLiteral;
102: import com.lowagie.text.pdf.PdfName;
103: import com.lowagie.text.pdf.PdfNumber;
104: import com.lowagie.text.pdf.PdfObject;
105: import com.lowagie.text.pdf.PdfReader;
106: import com.lowagie.text.pdf.PdfString;
107:
108: /** Reads a PNG image. All types of PNG can be read.
109: * <p>
110: * It is based in part in the JAI codec.
111: *
112: * @author Paulo Soares (psoares@consiste.pt)
113: */
114: public class PngImage {
115: /** Some PNG specific values. */
116: public static final int[] PNGID = { 137, 80, 78, 71, 13, 10, 26, 10 };
117:
118: /** A PNG marker. */
119: public static final String IHDR = "IHDR";
120:
121: /** A PNG marker. */
122: public static final String PLTE = "PLTE";
123:
124: /** A PNG marker. */
125: public static final String IDAT = "IDAT";
126:
127: /** A PNG marker. */
128: public static final String IEND = "IEND";
129:
130: /** A PNG marker. */
131: public static final String tRNS = "tRNS";
132:
133: /** A PNG marker. */
134: public static final String pHYs = "pHYs";
135:
136: /** A PNG marker. */
137: public static final String gAMA = "gAMA";
138:
139: /** A PNG marker. */
140: public static final String cHRM = "cHRM";
141:
142: /** A PNG marker. */
143: public static final String sRGB = "sRGB";
144:
145: /** A PNG marker. */
146: public static final String iCCP = "iCCP";
147:
148: private static final int TRANSFERSIZE = 4096;
149: private static final int PNG_FILTER_NONE = 0;
150: private static final int PNG_FILTER_SUB = 1;
151: private static final int PNG_FILTER_UP = 2;
152: private static final int PNG_FILTER_AVERAGE = 3;
153: private static final int PNG_FILTER_PAETH = 4;
154: private static final PdfName intents[] = { PdfName.PERCEPTUAL,
155: PdfName.RELATIVECALORIMETRIC, PdfName.SATURATION,
156: PdfName.ABSOLUTECALORIMETRIC };
157:
158: InputStream is;
159: DataInputStream dataStream;
160: int width;
161: int height;
162: int bitDepth;
163: int colorType;
164: int compressionMethod;
165: int filterMethod;
166: int interlaceMethod;
167: PdfDictionary additional = new PdfDictionary();
168: byte image[];
169: byte smask[];
170: byte trans[];
171: NewByteArrayOutputStream idat = new NewByteArrayOutputStream();
172: int dpiX;
173: int dpiY;
174: float XYRatio;
175: boolean genBWMask;
176: boolean palShades;
177: int transRedGray = -1;
178: int transGreen = -1;
179: int transBlue = -1;
180: int inputBands;
181: int bytesPerPixel; // number of bytes per input pixel
182: byte colorTable[];
183: float gamma = 1f;
184: boolean hasCHRM = false;
185: float xW, yW, xR, yR, xG, yG, xB, yB;
186: PdfName intent;
187: ICC_Profile icc_profile;
188:
189: /** Creates a new instance of PngImage */
190: PngImage(InputStream is) {
191: this .is = is;
192: }
193:
194: /** Reads a PNG from an url.
195: * @param url the url
196: * @throws IOException on error
197: * @return the image
198: */
199: public static Image getImage(URL url) throws IOException {
200: InputStream is = null;
201: try {
202: is = url.openStream();
203: Image img = getImage(is);
204: img.setUrl(url);
205: return img;
206: } finally {
207: if (is != null) {
208: is.close();
209: }
210: }
211: }
212:
213: /** Reads a PNG from a stream.
214: * @param is the stream
215: * @throws IOException on error
216: * @return the image
217: */
218: public static Image getImage(InputStream is) throws IOException {
219: PngImage png = new PngImage(is);
220: return png.getImage();
221: }
222:
223: /** Reads a PNG from a file.
224: * @param file the file
225: * @throws IOException on error
226: * @return the image
227: */
228: public static Image getImage(String file) throws IOException {
229: return getImage(Utilities.toURL(file));
230: }
231:
232: /** Reads a PNG from a byte array.
233: * @param data the byte array
234: * @throws IOException on error
235: * @return the image
236: */
237: public static Image getImage(byte data[]) throws IOException {
238: ByteArrayInputStream is = new ByteArrayInputStream(data);
239: Image img = getImage(is);
240: img.setOriginalData(data);
241: return img;
242: }
243:
244: boolean checkMarker(String s) {
245: if (s.length() != 4)
246: return false;
247: for (int k = 0; k < 4; ++k) {
248: char c = s.charAt(k);
249: if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z'))
250: return false;
251: }
252: return true;
253: }
254:
255: void readPng() throws IOException {
256: for (int i = 0; i < PNGID.length; i++) {
257: if (PNGID[i] != is.read()) {
258: throw new IOException("File is not a valid PNG.");
259: }
260: }
261: byte buffer[] = new byte[TRANSFERSIZE];
262: while (true) {
263: int len = getInt(is);
264: String marker = getString(is);
265: if (len < 0 || !checkMarker(marker))
266: throw new IOException("Corrupted PNG file.");
267: if (IDAT.equals(marker)) {
268: int size;
269: while (len != 0) {
270: size = is.read(buffer, 0, Math.min(len,
271: TRANSFERSIZE));
272: if (size < 0)
273: return;
274: idat.write(buffer, 0, size);
275: len -= size;
276: }
277: } else if (tRNS.equals(marker)) {
278: switch (colorType) {
279: case 0:
280: if (len >= 2) {
281: len -= 2;
282: int gray = getWord(is);
283: if (bitDepth == 16)
284: transRedGray = gray;
285: else
286: additional.put(PdfName.MASK,
287: new PdfLiteral("[" + gray + " "
288: + gray + "]"));
289: }
290: break;
291: case 2:
292: if (len >= 6) {
293: len -= 6;
294: int red = getWord(is);
295: int green = getWord(is);
296: int blue = getWord(is);
297: if (bitDepth == 16) {
298: transRedGray = red;
299: transGreen = green;
300: transBlue = blue;
301: } else
302: additional.put(PdfName.MASK,
303: new PdfLiteral("[" + red + " "
304: + red + " " + green + " "
305: + green + " " + blue + " "
306: + blue + "]"));
307: }
308: break;
309: case 3:
310: if (len > 0) {
311: trans = new byte[len];
312: for (int k = 0; k < len; ++k)
313: trans[k] = (byte) is.read();
314: len = 0;
315: }
316: break;
317: }
318: Utilities.skip(is, len);
319: } else if (IHDR.equals(marker)) {
320: width = getInt(is);
321: height = getInt(is);
322:
323: bitDepth = is.read();
324: colorType = is.read();
325: compressionMethod = is.read();
326: filterMethod = is.read();
327: interlaceMethod = is.read();
328: } else if (PLTE.equals(marker)) {
329: if (colorType == 3) {
330: PdfArray colorspace = new PdfArray();
331: colorspace.add(PdfName.INDEXED);
332: colorspace.add(getColorspace());
333: colorspace.add(new PdfNumber(len / 3 - 1));
334: ByteBuffer colortable = new ByteBuffer();
335: while ((len--) > 0) {
336: colortable.append_i(is.read());
337: }
338: colorspace.add(new PdfString(
339: colorTable = colortable.toByteArray()));
340: additional.put(PdfName.COLORSPACE, colorspace);
341: } else {
342: Utilities.skip(is, len);
343: }
344: } else if (pHYs.equals(marker)) {
345: int dx = getInt(is);
346: int dy = getInt(is);
347: int unit = is.read();
348: if (unit == 1) {
349: dpiX = (int) ((float) dx * 0.0254f + 0.5f);
350: dpiY = (int) ((float) dy * 0.0254f + 0.5f);
351: } else {
352: if (dy != 0)
353: XYRatio = (float) dx / (float) dy;
354: }
355: } else if (cHRM.equals(marker)) {
356: xW = (float) getInt(is) / 100000f;
357: yW = (float) getInt(is) / 100000f;
358: xR = (float) getInt(is) / 100000f;
359: yR = (float) getInt(is) / 100000f;
360: xG = (float) getInt(is) / 100000f;
361: yG = (float) getInt(is) / 100000f;
362: xB = (float) getInt(is) / 100000f;
363: yB = (float) getInt(is) / 100000f;
364: hasCHRM = !(Math.abs(xW) < 0.0001f
365: || Math.abs(yW) < 0.0001f
366: || Math.abs(xR) < 0.0001f
367: || Math.abs(yR) < 0.0001f
368: || Math.abs(xG) < 0.0001f
369: || Math.abs(yG) < 0.0001f
370: || Math.abs(xB) < 0.0001f || Math.abs(yB) < 0.0001f);
371: } else if (sRGB.equals(marker)) {
372: int ri = is.read();
373: intent = intents[ri];
374: gamma = 2.2f;
375: xW = 0.3127f;
376: yW = 0.329f;
377: xR = 0.64f;
378: yR = 0.33f;
379: xG = 0.3f;
380: yG = 0.6f;
381: xB = 0.15f;
382: yB = 0.06f;
383: hasCHRM = true;
384: } else if (gAMA.equals(marker)) {
385: int gm = getInt(is);
386: if (gm != 0) {
387: gamma = 100000f / (float) gm;
388: if (!hasCHRM) {
389: xW = 0.3127f;
390: yW = 0.329f;
391: xR = 0.64f;
392: yR = 0.33f;
393: xG = 0.3f;
394: yG = 0.6f;
395: xB = 0.15f;
396: yB = 0.06f;
397: hasCHRM = true;
398: }
399: }
400: } else if (iCCP.equals(marker)) {
401: do {
402: --len;
403: } while (is.read() != 0);
404: is.read();
405: --len;
406: byte icccom[] = new byte[len];
407: int p = 0;
408: while (len > 0) {
409: int r = is.read(icccom, p, len);
410: if (r < 0)
411: throw new IOException("Premature end of file.");
412: p += r;
413: len -= r;
414: }
415: byte iccp[] = PdfReader.FlateDecode(icccom, true);
416: icccom = null;
417: try {
418: icc_profile = ICC_Profile.getInstance(iccp);
419: } catch (RuntimeException e) {
420: icc_profile = null;
421: }
422: } else if (IEND.equals(marker)) {
423: break;
424: } else {
425: Utilities.skip(is, len);
426: }
427: Utilities.skip(is, 4);
428: }
429: }
430:
431: PdfObject getColorspace() {
432: if (icc_profile != null) {
433: if ((colorType & 2) == 0)
434: return PdfName.DEVICEGRAY;
435: else
436: return PdfName.DEVICERGB;
437: }
438: if (gamma == 1f && !hasCHRM) {
439: if ((colorType & 2) == 0)
440: return PdfName.DEVICEGRAY;
441: else
442: return PdfName.DEVICERGB;
443: } else {
444: PdfArray array = new PdfArray();
445: PdfDictionary dic = new PdfDictionary();
446: if ((colorType & 2) == 0) {
447: if (gamma == 1f)
448: return PdfName.DEVICEGRAY;
449: array.add(PdfName.CALGRAY);
450: dic.put(PdfName.GAMMA, new PdfNumber(gamma));
451: dic.put(PdfName.WHITEPOINT, new PdfLiteral("[1 1 1]"));
452: array.add(dic);
453: } else {
454: PdfObject wp = new PdfLiteral("[1 1 1]");
455: array.add(PdfName.CALRGB);
456: if (gamma != 1f) {
457: PdfArray gm = new PdfArray();
458: PdfNumber n = new PdfNumber(gamma);
459: gm.add(n);
460: gm.add(n);
461: gm.add(n);
462: dic.put(PdfName.GAMMA, gm);
463: }
464: if (hasCHRM) {
465: float z = yW
466: * ((xG - xB) * yR - (xR - xB) * yG + (xR - xG)
467: * yB);
468: float YA = yR
469: * ((xG - xB) * yW - (xW - xB) * yG + (xW - xG)
470: * yB) / z;
471: float XA = YA * xR / yR;
472: float ZA = YA * ((1 - xR) / yR - 1);
473: float YB = -yG
474: * ((xR - xB) * yW - (xW - xB) * yR + (xW - xR)
475: * yB) / z;
476: float XB = YB * xG / yG;
477: float ZB = YB * ((1 - xG) / yG - 1);
478: float YC = yB
479: * ((xR - xG) * yW - (xW - xG) * yW + (xW - xR)
480: * yG) / z;
481: float XC = YC * xB / yB;
482: float ZC = YC * ((1 - xB) / yB - 1);
483: float XW = XA + XB + XC;
484: float YW = 1;//YA+YB+YC;
485: float ZW = ZA + ZB + ZC;
486: PdfArray wpa = new PdfArray();
487: wpa.add(new PdfNumber(XW));
488: wpa.add(new PdfNumber(YW));
489: wpa.add(new PdfNumber(ZW));
490: wp = wpa;
491: PdfArray matrix = new PdfArray();
492: matrix.add(new PdfNumber(XA));
493: matrix.add(new PdfNumber(YA));
494: matrix.add(new PdfNumber(ZA));
495: matrix.add(new PdfNumber(XB));
496: matrix.add(new PdfNumber(YB));
497: matrix.add(new PdfNumber(ZB));
498: matrix.add(new PdfNumber(XC));
499: matrix.add(new PdfNumber(YC));
500: matrix.add(new PdfNumber(ZC));
501: dic.put(PdfName.MATRIX, matrix);
502: }
503: dic.put(PdfName.WHITEPOINT, wp);
504: array.add(dic);
505: }
506: return array;
507: }
508: }
509:
510: Image getImage() throws IOException {
511: readPng();
512: try {
513: int pal0 = 0;
514: int palIdx = 0;
515: palShades = false;
516: if (trans != null) {
517: for (int k = 0; k < trans.length; ++k) {
518: int n = trans[k] & 0xff;
519: if (n == 0) {
520: ++pal0;
521: palIdx = k;
522: }
523: if (n != 0 && n != 255) {
524: palShades = true;
525: break;
526: }
527: }
528: }
529: if ((colorType & 4) != 0)
530: palShades = true;
531: genBWMask = (!palShades && (pal0 > 1 || transRedGray >= 0));
532: if (!palShades && !genBWMask && pal0 == 1) {
533: additional.put(PdfName.MASK, new PdfLiteral("["
534: + palIdx + " " + palIdx + "]"));
535: }
536: boolean needDecode = (interlaceMethod == 1)
537: || (bitDepth == 16) || ((colorType & 4) != 0)
538: || palShades || genBWMask;
539: switch (colorType) {
540: case 0:
541: inputBands = 1;
542: break;
543: case 2:
544: inputBands = 3;
545: break;
546: case 3:
547: inputBands = 1;
548: break;
549: case 4:
550: inputBands = 2;
551: break;
552: case 6:
553: inputBands = 4;
554: break;
555: }
556: if (needDecode)
557: decodeIdat();
558: int components = inputBands;
559: if ((colorType & 4) != 0)
560: --components;
561: int bpc = bitDepth;
562: if (bpc == 16)
563: bpc = 8;
564: Image img;
565: if (image != null)
566: img = Image.getInstance(width, height, components, bpc,
567: image);
568: else {
569: img = new ImgRaw(width, height, components, bpc, idat
570: .toByteArray());
571: img.setDeflated(true);
572: PdfDictionary decodeparms = new PdfDictionary();
573: decodeparms.put(PdfName.BITSPERCOMPONENT,
574: new PdfNumber(bitDepth));
575: decodeparms.put(PdfName.PREDICTOR, new PdfNumber(15));
576: decodeparms.put(PdfName.COLUMNS, new PdfNumber(width));
577: decodeparms.put(PdfName.COLORS, new PdfNumber(
578: (colorType == 3 || (colorType & 2) == 0) ? 1
579: : 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,
592: smask);
593: im2.makeMask();
594: img.setImageMask(im2);
595: }
596: if (genBWMask) {
597: Image im2 = Image.getInstance(width, height, 1, 1,
598: smask);
599: im2.makeMask();
600: img.setImageMask(im2);
601: }
602: img.setDpi(dpiX, dpiY);
603: img.setXYRatio(XYRatio);
604: img.setOriginalType(Image.ORIGINAL_PNG);
605: return img;
606: } catch (Exception e) {
607: throw new ExceptionConverter(e);
608: }
609: }
610:
611: void decodeIdat() {
612: int nbitDepth = bitDepth;
613: if (nbitDepth == 16)
614: nbitDepth = 8;
615: int size = -1;
616: bytesPerPixel = (bitDepth == 16) ? 2 : 1;
617: switch (colorType) {
618: case 0:
619: size = (nbitDepth * width + 7) / 8 * height;
620: break;
621: case 2:
622: size = width * 3 * height;
623: bytesPerPixel *= 3;
624: break;
625: case 3:
626: if (interlaceMethod == 1)
627: size = (nbitDepth * width + 7) / 8 * height;
628: bytesPerPixel = 1;
629: break;
630: case 4:
631: size = width * height;
632: bytesPerPixel *= 2;
633: break;
634: case 6:
635: size = width * 3 * height;
636: bytesPerPixel *= 4;
637: break;
638: }
639: if (size >= 0)
640: image = new byte[size];
641: if (palShades)
642: smask = new byte[width * height];
643: else if (genBWMask)
644: smask = new byte[(width + 7) / 8 * height];
645: ByteArrayInputStream bai = new ByteArrayInputStream(idat
646: .getBuf(), 0, idat.size());
647: InputStream infStream = new InflaterInputStream(bai,
648: new Inflater());
649: dataStream = new DataInputStream(infStream);
650:
651: if (interlaceMethod != 1) {
652: decodePass(0, 0, 1, 1, width, height);
653: } else {
654: decodePass(0, 0, 8, 8, (width + 7) / 8, (height + 7) / 8);
655: decodePass(4, 0, 8, 8, (width + 3) / 8, (height + 7) / 8);
656: decodePass(0, 4, 4, 8, (width + 3) / 4, (height + 3) / 8);
657: decodePass(2, 0, 4, 4, (width + 1) / 4, (height + 3) / 4);
658: decodePass(0, 2, 2, 4, (width + 1) / 2, (height + 1) / 4);
659: decodePass(1, 0, 2, 2, width / 2, (height + 1) / 2);
660: decodePass(0, 1, 1, 2, width, height / 2);
661: }
662:
663: }
664:
665: void decodePass(int xOffset, int yOffset, int xStep, int yStep,
666: int passWidth, int passHeight) {
667: if ((passWidth == 0) || (passHeight == 0)) {
668: return;
669: }
670:
671: int bytesPerRow = (inputBands * passWidth * bitDepth + 7) / 8;
672: byte[] curr = new byte[bytesPerRow];
673: byte[] prior = new byte[bytesPerRow];
674:
675: // Decode the (sub)image row-by-row
676: int srcY, dstY;
677: for (srcY = 0, dstY = yOffset; srcY < passHeight; srcY++, dstY += yStep) {
678: // Read the filter type byte and a row of data
679: int filter = 0;
680: try {
681: filter = dataStream.read();
682: dataStream.readFully(curr, 0, bytesPerRow);
683: } catch (Exception e) {
684: // empty on purpose
685: }
686:
687: switch (filter) {
688: case PNG_FILTER_NONE:
689: break;
690: case PNG_FILTER_SUB:
691: decodeSubFilter(curr, bytesPerRow, bytesPerPixel);
692: break;
693: case PNG_FILTER_UP:
694: decodeUpFilter(curr, prior, bytesPerRow);
695: break;
696: case PNG_FILTER_AVERAGE:
697: decodeAverageFilter(curr, prior, bytesPerRow,
698: bytesPerPixel);
699: break;
700: case PNG_FILTER_PAETH:
701: decodePaethFilter(curr, prior, bytesPerRow,
702: bytesPerPixel);
703: break;
704: default:
705: // Error -- uknown filter type
706: throw new RuntimeException("PNG filter unknown.");
707: }
708:
709: processPixels(curr, xOffset, xStep, dstY, passWidth);
710:
711: // Swap curr and prior
712: byte[] tmp = prior;
713: prior = curr;
714: curr = tmp;
715: }
716: }
717:
718: void processPixels(byte curr[], int xOffset, int step, int y,
719: int width) {
720: int srcX, dstX;
721:
722: int out[] = getPixel(curr);
723: int sizes = 0;
724: switch (colorType) {
725: case 0:
726: case 3:
727: case 4:
728: sizes = 1;
729: break;
730: case 2:
731: case 6:
732: sizes = 3;
733: break;
734: }
735: if (image != null) {
736: dstX = xOffset;
737: int yStride = (sizes * this .width
738: * (bitDepth == 16 ? 8 : bitDepth) + 7) / 8;
739: for (srcX = 0; srcX < width; srcX++) {
740: setPixel(image, out, inputBands * srcX, sizes, dstX, y,
741: bitDepth, yStride);
742: dstX += step;
743: }
744: }
745: if (palShades) {
746: if ((colorType & 4) != 0) {
747: if (bitDepth == 16) {
748: for (int k = 0; k < width; ++k)
749: out[k * inputBands + sizes] >>>= 8;
750: }
751: int yStride = this .width;
752: dstX = xOffset;
753: for (srcX = 0; srcX < width; srcX++) {
754: setPixel(smask, out, inputBands * srcX + sizes, 1,
755: dstX, y, 8, yStride);
756: dstX += step;
757: }
758: } else { //colorType 3
759: int yStride = this .width;
760: int v[] = new int[1];
761: dstX = xOffset;
762: for (srcX = 0; srcX < width; srcX++) {
763: int idx = out[srcX];
764: if (idx < trans.length)
765: v[0] = trans[idx];
766: setPixel(smask, v, 0, 1, dstX, y, 8, yStride);
767: dstX += step;
768: }
769: }
770: } else if (genBWMask) {
771: switch (colorType) {
772: case 3: {
773: int yStride = (this .width + 7) / 8;
774: int v[] = new int[1];
775: dstX = xOffset;
776: for (srcX = 0; srcX < width; srcX++) {
777: int idx = out[srcX];
778: if (idx < trans.length)
779: v[0] = (trans[idx] == 0 ? 1 : 0);
780: setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
781: dstX += step;
782: }
783: break;
784: }
785: case 0: {
786: int yStride = (this .width + 7) / 8;
787: int v[] = new int[1];
788: dstX = xOffset;
789: for (srcX = 0; srcX < width; srcX++) {
790: int g = out[srcX];
791: v[0] = (g == transRedGray ? 1 : 0);
792: setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
793: dstX += step;
794: }
795: break;
796: }
797: case 2: {
798: int yStride = (this .width + 7) / 8;
799: int v[] = new int[1];
800: dstX = xOffset;
801: for (srcX = 0; srcX < width; srcX++) {
802: int markRed = inputBands * srcX;
803: v[0] = (out[markRed] == transRedGray
804: && out[markRed + 1] == transGreen
805: && out[markRed + 2] == transBlue ? 1 : 0);
806: setPixel(smask, v, 0, 1, dstX, y, 1, yStride);
807: dstX += step;
808: }
809: break;
810: }
811: }
812: }
813: }
814:
815: static int getPixel(byte image[], int x, int y, int bitDepth,
816: int bytesPerRow) {
817: if (bitDepth == 8) {
818: int pos = bytesPerRow * y + x;
819: return image[pos] & 0xff;
820: } else {
821: int pos = bytesPerRow * y + x / (8 / bitDepth);
822: int v = image[pos] >> (8 - bitDepth * (x % (8 / bitDepth)) - bitDepth);
823: return v & ((1 << bitDepth) - 1);
824: }
825: }
826:
827: static void setPixel(byte image[], int data[], int offset,
828: int size, int x, int y, int bitDepth, int bytesPerRow) {
829: if (bitDepth == 8) {
830: int pos = bytesPerRow * y + size * x;
831: for (int k = 0; k < size; ++k)
832: image[pos + k] = (byte) data[k + offset];
833: } else if (bitDepth == 16) {
834: int pos = bytesPerRow * y + size * x;
835: for (int k = 0; k < size; ++k)
836: image[pos + k] = (byte) (data[k + offset] >>> 8);
837: } else {
838: int pos = bytesPerRow * y + x / (8 / bitDepth);
839: int v = data[offset] << (8 - bitDepth
840: * (x % (8 / bitDepth)) - bitDepth);
841: image[pos] |= v;
842: }
843: }
844:
845: int[] getPixel(byte curr[]) {
846: switch (bitDepth) {
847: case 8: {
848: int out[] = new int[curr.length];
849: for (int k = 0; k < out.length; ++k)
850: out[k] = curr[k] & 0xff;
851: return out;
852: }
853: case 16: {
854: int out[] = new int[curr.length / 2];
855: for (int k = 0; k < out.length; ++k)
856: out[k] = ((curr[k * 2] & 0xff) << 8)
857: + (curr[k * 2 + 1] & 0xff);
858: return out;
859: }
860: default: {
861: int out[] = new int[curr.length * 8 / bitDepth];
862: int idx = 0;
863: int passes = 8 / bitDepth;
864: int mask = (1 << bitDepth) - 1;
865: for (int k = 0; k < curr.length; ++k) {
866: for (int j = passes - 1; j >= 0; --j) {
867: out[idx++] = (curr[k] >>> (bitDepth * j)) & mask;
868: }
869: }
870: return out;
871: }
872: }
873: }
874:
875: private static void decodeSubFilter(byte[] curr, int count, int bpp) {
876: for (int i = bpp; i < count; i++) {
877: int val;
878:
879: val = curr[i] & 0xff;
880: val += curr[i - bpp] & 0xff;
881:
882: curr[i] = (byte) val;
883: }
884: }
885:
886: private static void decodeUpFilter(byte[] curr, byte[] prev,
887: int count) {
888: for (int i = 0; i < count; i++) {
889: int raw = curr[i] & 0xff;
890: int prior = prev[i] & 0xff;
891:
892: curr[i] = (byte) (raw + prior);
893: }
894: }
895:
896: private static void decodeAverageFilter(byte[] curr, byte[] prev,
897: int count, int bpp) {
898: int raw, priorPixel, priorRow;
899:
900: for (int i = 0; i < bpp; i++) {
901: raw = curr[i] & 0xff;
902: priorRow = prev[i] & 0xff;
903:
904: curr[i] = (byte) (raw + priorRow / 2);
905: }
906:
907: for (int i = bpp; i < count; i++) {
908: raw = curr[i] & 0xff;
909: priorPixel = curr[i - bpp] & 0xff;
910: priorRow = prev[i] & 0xff;
911:
912: curr[i] = (byte) (raw + (priorPixel + priorRow) / 2);
913: }
914: }
915:
916: private static int paethPredictor(int a, int b, int c) {
917: int p = a + b - c;
918: int pa = Math.abs(p - a);
919: int pb = Math.abs(p - b);
920: int pc = Math.abs(p - c);
921:
922: if ((pa <= pb) && (pa <= pc)) {
923: return a;
924: } else if (pb <= pc) {
925: return b;
926: } else {
927: return c;
928: }
929: }
930:
931: private static void decodePaethFilter(byte[] curr, byte[] prev,
932: int count, int bpp) {
933: int raw, priorPixel, priorRow, priorRowPixel;
934:
935: for (int i = 0; i < bpp; i++) {
936: raw = curr[i] & 0xff;
937: priorRow = prev[i] & 0xff;
938:
939: curr[i] = (byte) (raw + priorRow);
940: }
941:
942: for (int i = bpp; i < count; i++) {
943: raw = curr[i] & 0xff;
944: priorPixel = curr[i - bpp] & 0xff;
945: priorRow = prev[i] & 0xff;
946: priorRowPixel = prev[i - bpp] & 0xff;
947:
948: curr[i] = (byte) (raw + paethPredictor(priorPixel,
949: priorRow, priorRowPixel));
950: }
951: }
952:
953: static class NewByteArrayOutputStream extends ByteArrayOutputStream {
954: public byte[] getBuf() {
955: return buf;
956: }
957: }
958:
959: /**
960: * Gets an <CODE>int</CODE> from an <CODE>InputStream</CODE>.
961: *
962: * @param is an <CODE>InputStream</CODE>
963: * @return the value of an <CODE>int</CODE>
964: */
965:
966: public static final int getInt(InputStream is) throws IOException {
967: return (is.read() << 24) + (is.read() << 16) + (is.read() << 8)
968: + is.read();
969: }
970:
971: /**
972: * Gets a <CODE>word</CODE> from an <CODE>InputStream</CODE>.
973: *
974: * @param is an <CODE>InputStream</CODE>
975: * @return the value of an <CODE>int</CODE>
976: */
977:
978: public static final int getWord(InputStream is) throws IOException {
979: return (is.read() << 8) + is.read();
980: }
981:
982: /**
983: * Gets a <CODE>String</CODE> from an <CODE>InputStream</CODE>.
984: *
985: * @param is an <CODE>InputStream</CODE>
986: * @return the value of an <CODE>int</CODE>
987: */
988:
989: public static final String getString(InputStream is)
990: throws IOException {
991: StringBuffer buf = new StringBuffer();
992: for (int i = 0; i < 4; i++) {
993: buf.append((char) is.read());
994: }
995: return buf.toString();
996: }
997:
998: }
|