0001: /*
0002: * $RCSfile: PNGImageDecoder.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.3 $
0009: * $Date: 2006/08/22 00:12:04 $
0010: * $State: Exp $
0011: */
0012: package com.sun.media.jai.codecimpl;
0013:
0014: import java.awt.Color;
0015: import java.awt.Point;
0016: import java.awt.RenderingHints;
0017: import java.awt.Transparency;
0018: import java.awt.color.ColorSpace;
0019: import java.awt.color.ICC_ColorSpace;
0020: import java.awt.color.ICC_Profile;
0021: import java.awt.geom.AffineTransform;
0022: import java.awt.image.BufferedImage;
0023: import java.awt.image.ColorModel;
0024: import java.awt.image.ComponentColorModel;
0025: import java.awt.image.DataBuffer;
0026: import java.awt.image.DataBufferByte;
0027: import java.awt.image.DataBufferUShort;
0028: import java.awt.image.IndexColorModel;
0029: import java.awt.image.MultiPixelPackedSampleModel;
0030: import java.awt.image.PixelInterleavedSampleModel;
0031: import java.awt.image.Raster;
0032: import java.awt.image.RenderedImage;
0033: import java.awt.image.SampleModel;
0034: import java.awt.image.WritableRaster;
0035: import java.io.BufferedInputStream;
0036: import java.io.ByteArrayInputStream;
0037: import java.io.DataInputStream;
0038: import java.io.FileInputStream;
0039: import java.io.InputStream;
0040: import java.io.IOException;
0041: import java.io.SequenceInputStream;
0042: import java.text.DateFormat;
0043: import java.util.Date;
0044: import java.util.Enumeration;
0045: import java.util.GregorianCalendar;
0046: import java.util.Hashtable;
0047: import java.util.TimeZone;
0048: import java.util.Vector;
0049: import java.util.zip.Inflater;
0050: import java.util.zip.InflaterInputStream;
0051: import com.sun.media.jai.codec.ImageCodec;
0052: import com.sun.media.jai.codec.ImageDecoder;
0053: import com.sun.media.jai.codec.ImageDecoderImpl;
0054: import com.sun.media.jai.codec.ImageDecodeParam;
0055: import com.sun.media.jai.codec.PNGDecodeParam;
0056: import com.sun.media.jai.codec.PNGEncodeParam;
0057: import com.sun.media.jai.codecimpl.ImagingListenerProxy;
0058: import com.sun.media.jai.codecimpl.util.ImagingException;
0059:
0060: /**
0061: * @since EA3
0062: */
0063: public class PNGImageDecoder extends ImageDecoderImpl {
0064:
0065: public PNGImageDecoder(InputStream input, PNGDecodeParam param) {
0066: super (input, param);
0067: }
0068:
0069: public RenderedImage decodeAsRenderedImage(int page)
0070: throws IOException {
0071: if (page != 0) {
0072: throw new IOException(JaiI18N
0073: .getString("PNGImageDecoder19"));
0074: }
0075: try {
0076: return new PNGImage(input, (PNGDecodeParam) param);
0077: } catch (Exception e) {
0078: throw CodecUtils.toIOException(e);
0079: }
0080: }
0081: }
0082:
0083: class PNGChunk {
0084: int length;
0085: int type;
0086: byte[] data;
0087: int crc;
0088:
0089: String typeString;
0090:
0091: public PNGChunk(int length, int type, byte[] data, int crc) {
0092: this .length = length;
0093: this .type = type;
0094: this .data = data;
0095: this .crc = crc;
0096:
0097: typeString = new String();
0098: typeString += (char) (type >> 24);
0099: typeString += (char) ((type >> 16) & 0xff);
0100: typeString += (char) ((type >> 8) & 0xff);
0101: typeString += (char) (type & 0xff);
0102: }
0103:
0104: public int getLength() {
0105: return length;
0106: }
0107:
0108: public int getType() {
0109: return type;
0110: }
0111:
0112: public String getTypeString() {
0113: return typeString;
0114: }
0115:
0116: public byte[] getData() {
0117: return data;
0118: }
0119:
0120: public byte getByte(int offset) {
0121: return data[offset];
0122: }
0123:
0124: public int getInt1(int offset) {
0125: return data[offset] & 0xff;
0126: }
0127:
0128: public int getInt2(int offset) {
0129: return ((data[offset] & 0xff) << 8) | (data[offset + 1] & 0xff);
0130: }
0131:
0132: public int getInt4(int offset) {
0133: return ((data[offset] & 0xff) << 24)
0134: | ((data[offset + 1] & 0xff) << 16)
0135: | ((data[offset + 2] & 0xff) << 8)
0136: | (data[offset + 3] & 0xff);
0137: }
0138:
0139: public String getString4(int offset) {
0140: String s = new String();
0141: s += (char) data[offset];
0142: s += (char) data[offset + 1];
0143: s += (char) data[offset + 2];
0144: s += (char) data[offset + 3];
0145: return s;
0146: }
0147:
0148: public boolean isType(String typeName) {
0149: return typeString.equals(typeName);
0150: }
0151: }
0152:
0153: /**
0154: * TO DO:
0155: *
0156: * zTXt chunks
0157: *
0158: */
0159: class PNGImage extends SimpleRenderedImage {
0160:
0161: public static final int PNG_COLOR_GRAY = 0;
0162: public static final int PNG_COLOR_RGB = 2;
0163: public static final int PNG_COLOR_PALETTE = 3;
0164: public static final int PNG_COLOR_GRAY_ALPHA = 4;
0165: public static final int PNG_COLOR_RGB_ALPHA = 6;
0166:
0167: private static final String[] colorTypeNames = { "Grayscale",
0168: "Error", "Truecolor", "Index", "Grayscale with alpha",
0169: "Error", "Truecolor with alpha" };
0170:
0171: public static final int PNG_FILTER_NONE = 0;
0172: public static final int PNG_FILTER_SUB = 1;
0173: public static final int PNG_FILTER_UP = 2;
0174: public static final int PNG_FILTER_AVERAGE = 3;
0175: public static final int PNG_FILTER_PAETH = 4;
0176:
0177: private static final int RED_OFFSET = 2;
0178: private static final int GREEN_OFFSET = 1;
0179: private static final int BLUE_OFFSET = 0;
0180:
0181: private int[][] bandOffsets = { null, { 0 }, // G
0182: { 0, 1 }, // GA in GA order
0183: { 0, 1, 2 }, // RGB in RGB order
0184: { 0, 1, 2, 3 } // RGBA in RGBA order
0185: };
0186:
0187: private int bitDepth;
0188: private int colorType;
0189:
0190: private int compressionMethod;
0191: private int filterMethod;
0192: private int interlaceMethod;
0193:
0194: private int paletteEntries;
0195: private byte[] redPalette;
0196: private byte[] greenPalette;
0197: private byte[] bluePalette;
0198: private byte[] alphaPalette;
0199:
0200: private int bkgdRed;
0201: private int bkgdGreen;
0202: private int bkgdBlue;
0203:
0204: private int grayTransparentAlpha;
0205: private int redTransparentAlpha;
0206: private int greenTransparentAlpha;
0207: private int blueTransparentAlpha;
0208:
0209: private int maxOpacity;
0210:
0211: private int[] significantBits = null;
0212:
0213: private boolean hasBackground = false;
0214:
0215: // Parameter information
0216:
0217: // If true, the user wants destination alpha where applicable.
0218: private boolean suppressAlpha = false;
0219:
0220: // If true, perform palette lookup internally
0221: private boolean expandPalette = false;
0222:
0223: // If true, output < 8 bit gray images in 8 bit components format
0224: private boolean output8BitGray = false;
0225:
0226: // Create an alpha channel in the destination color model.
0227: private boolean outputHasAlphaPalette = false;
0228:
0229: // Perform gamma correction on the image
0230: private boolean performGammaCorrection = false;
0231:
0232: // Expand GA to GGGA for compatbility with Java2D
0233: private boolean expandGrayAlpha = false;
0234:
0235: // Produce an instance of PNGEncodeParam
0236: private boolean generateEncodeParam = false;
0237:
0238: // PNGDecodeParam controlling decode process
0239: private PNGDecodeParam decodeParam = null;
0240:
0241: // PNGEncodeParam to store file details in
0242: private PNGEncodeParam encodeParam = null;
0243:
0244: private boolean emitProperties = true;
0245:
0246: private float fileGamma = 45455 / 100000.0F;
0247:
0248: private float userExponent = 1.0F;
0249:
0250: private float displayExponent = 2.2F;
0251:
0252: private float[] chromaticity = null;
0253:
0254: private int sRGBRenderingIntent = -1;
0255:
0256: // ICCP parameters
0257: private ICC_Profile iccProfile = null;
0258: private String iccProfileName = null;
0259:
0260: // Post-processing step implied by above parameters
0261: private int postProcess = POST_NONE;
0262:
0263: // Possible post-processing steps
0264:
0265: // Do nothing
0266: private static final int POST_NONE = 0;
0267:
0268: // Gamma correct only
0269: private static final int POST_GAMMA = 1;
0270:
0271: // Push gray values through grayLut to expand to 8 bits
0272: private static final int POST_GRAY_LUT = 2;
0273:
0274: // Push gray values through grayLut to expand to 8 bits, add alpha
0275: private static final int POST_GRAY_LUT_ADD_TRANS = 3;
0276:
0277: // Push palette value through R,G,B lookup tables
0278: private static final int POST_PALETTE_TO_RGB = 4;
0279:
0280: // Push palette value through R,G,B,A lookup tables
0281: private static final int POST_PALETTE_TO_RGBA = 5;
0282:
0283: // Add transparency to a given gray value (w/ optional gamma)
0284: private static final int POST_ADD_GRAY_TRANS = 6;
0285:
0286: // Add transparency to a given RGB value (w/ optional gamma)
0287: private static final int POST_ADD_RGB_TRANS = 7;
0288:
0289: // Remove the alpha channel from a gray image (w/ optional gamma)
0290: private static final int POST_REMOVE_GRAY_TRANS = 8;
0291:
0292: // Remove the alpha channel from an RGB image (w/optional gamma)
0293: private static final int POST_REMOVE_RGB_TRANS = 9;
0294:
0295: // Mask to add expansion of GA -> GGGA
0296: private static final int POST_EXP_MASK = 16;
0297:
0298: // Expand gray to G/G/G
0299: private static final int POST_GRAY_ALPHA_EXP = POST_NONE
0300: | POST_EXP_MASK;
0301:
0302: // Expand gray to G/G/G through a gamma lut
0303: private static final int POST_GAMMA_EXP = POST_GAMMA
0304: | POST_EXP_MASK;
0305:
0306: // Push gray values through grayLut to expand to 8 bits, expand, add alpha
0307: private static final int POST_GRAY_LUT_ADD_TRANS_EXP = POST_GRAY_LUT_ADD_TRANS
0308: | POST_EXP_MASK;
0309:
0310: // Add transparency to a given gray value, expand
0311: private static final int POST_ADD_GRAY_TRANS_EXP = POST_ADD_GRAY_TRANS
0312: | POST_EXP_MASK;
0313:
0314: private Vector streamVec = new Vector();
0315: private DataInputStream dataStream;
0316:
0317: private int bytesPerPixel; // number of bytes per input pixel
0318: private int inputBands;
0319: private int outputBands;
0320:
0321: // Number of private chunks
0322: private int chunkIndex = 0;
0323:
0324: private Vector textKeys = new Vector();
0325: private Vector textStrings = new Vector();
0326:
0327: private Vector ztextKeys = new Vector();
0328: private Vector ztextStrings = new Vector();
0329:
0330: private WritableRaster theTile;
0331:
0332: private int[] gammaLut = null;
0333:
0334: private void initGammaLut(int bits) {
0335: double exp = (double) userExponent
0336: / (fileGamma * displayExponent);
0337: int numSamples = 1 << bits;
0338: int maxOutSample = (bits == 16) ? 65535 : 255;
0339:
0340: gammaLut = new int[numSamples];
0341: for (int i = 0; i < numSamples; i++) {
0342: double gbright = (double) i / (numSamples - 1);
0343: double gamma = Math.pow(gbright, exp);
0344: int igamma = (int) (gamma * maxOutSample + 0.5);
0345: if (igamma > maxOutSample) {
0346: igamma = maxOutSample;
0347: }
0348: gammaLut[i] = igamma;
0349: }
0350: }
0351:
0352: private final byte[][] expandBits = {
0353: null,
0354: { (byte) 0x00, (byte) 0xff },
0355: { (byte) 0x00, (byte) 0x55, (byte) 0xaa, (byte) 0xff },
0356: null,
0357: { (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
0358: (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77,
0359: (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb,
0360: (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff } };
0361:
0362: private int[] grayLut = null;
0363:
0364: private void initGrayLut(int bits) {
0365: int len = 1 << bits;
0366: grayLut = new int[len];
0367:
0368: if (performGammaCorrection) {
0369: for (int i = 0; i < len; i++) {
0370: grayLut[i] = gammaLut[i];
0371: }
0372: } else {
0373: for (int i = 0; i < len; i++) {
0374: grayLut[i] = expandBits[bits][i];
0375: }
0376: }
0377: }
0378:
0379: public PNGImage(InputStream stream, PNGDecodeParam decodeParam)
0380: throws IOException {
0381:
0382: if (!stream.markSupported()) {
0383: stream = new BufferedInputStream(stream);
0384: }
0385: DataInputStream distream = new DataInputStream(stream);
0386:
0387: if (decodeParam == null) {
0388: decodeParam = new PNGDecodeParam();
0389: }
0390: this .decodeParam = decodeParam;
0391:
0392: // Get parameter values
0393: this .suppressAlpha = decodeParam.getSuppressAlpha();
0394: this .expandPalette = decodeParam.getExpandPalette();
0395: this .output8BitGray = decodeParam.getOutput8BitGray();
0396: this .expandGrayAlpha = decodeParam.getExpandGrayAlpha();
0397: if (decodeParam.getPerformGammaCorrection()) {
0398: this .userExponent = decodeParam.getUserExponent();
0399: this .displayExponent = decodeParam.getDisplayExponent();
0400: performGammaCorrection = true;
0401: output8BitGray = true;
0402: }
0403: this .generateEncodeParam = decodeParam.getGenerateEncodeParam();
0404:
0405: if (emitProperties) {
0406: properties.put("file_type", "PNG v. 1.0");
0407: }
0408:
0409: try {
0410: long magic = distream.readLong();
0411: if (magic != 0x89504e470d0a1a0aL) {
0412: String msg = JaiI18N.getString("PNGImageDecoder0");
0413: throw new RuntimeException(msg);
0414: }
0415: } catch (Exception e) {
0416: String message = JaiI18N.getString("PNGImageDecoder1");
0417: ImagingListenerProxy.errorOccurred(message,
0418: new ImagingException(message, e), this , false);
0419: /*
0420: e.printStackTrace();
0421: String msg = JaiI18N.getString("PNGImageDecoder1");
0422: throw new RuntimeException(msg);
0423: */
0424: }
0425:
0426: do {
0427: try {
0428: PNGChunk chunk;
0429:
0430: String chunkType = getChunkType(distream);
0431: if (chunkType.equals("IHDR")) {
0432: chunk = readChunk(distream);
0433: parse_IHDR_chunk(chunk);
0434: } else if (chunkType.equals("PLTE")) {
0435: chunk = readChunk(distream);
0436: parse_PLTE_chunk(chunk);
0437: } else if (chunkType.equals("IDAT")) {
0438: chunk = readChunk(distream);
0439: streamVec.add(new ByteArrayInputStream(chunk
0440: .getData()));
0441: } else if (chunkType.equals("IEND")) {
0442: chunk = readChunk(distream);
0443: parse_IEND_chunk(chunk);
0444: break; // fall through to the bottom
0445: } else if (chunkType.equals("bKGD")) {
0446: chunk = readChunk(distream);
0447: parse_bKGD_chunk(chunk);
0448: } else if (chunkType.equals("cHRM")) {
0449: chunk = readChunk(distream);
0450: parse_cHRM_chunk(chunk);
0451: } else if (chunkType.equals("gAMA")) {
0452: chunk = readChunk(distream);
0453: parse_gAMA_chunk(chunk);
0454: } else if (chunkType.equals("hIST")) {
0455: chunk = readChunk(distream);
0456: parse_hIST_chunk(chunk);
0457: } else if (chunkType.equals("iCCP")) {
0458: chunk = readChunk(distream);
0459: parse_iCCP_chunk(chunk);
0460: } else if (chunkType.equals("pHYs")) {
0461: chunk = readChunk(distream);
0462: parse_pHYs_chunk(chunk);
0463: } else if (chunkType.equals("sBIT")) {
0464: chunk = readChunk(distream);
0465: parse_sBIT_chunk(chunk);
0466: } else if (chunkType.equals("sRGB")) {
0467: chunk = readChunk(distream);
0468: parse_sRGB_chunk(chunk);
0469: } else if (chunkType.equals("tEXt")) {
0470: chunk = readChunk(distream);
0471: parse_tEXt_chunk(chunk);
0472: } else if (chunkType.equals("tIME")) {
0473: chunk = readChunk(distream);
0474: parse_tIME_chunk(chunk);
0475: } else if (chunkType.equals("tRNS")) {
0476: chunk = readChunk(distream);
0477: parse_tRNS_chunk(chunk);
0478: } else if (chunkType.equals("zTXt")) {
0479: chunk = readChunk(distream);
0480: parse_zTXt_chunk(chunk);
0481: } else {
0482: chunk = readChunk(distream);
0483: // Output the chunk data in raw form
0484:
0485: String type = chunk.getTypeString();
0486: byte[] data = chunk.getData();
0487: if (encodeParam != null) {
0488: encodeParam.addPrivateChunk(type, data);
0489: }
0490: if (emitProperties) {
0491: String key = "chunk_" + chunkIndex++ + ":"
0492: + type;
0493: properties.put(key.toLowerCase(), data);
0494: }
0495: }
0496: } catch (Exception e) {
0497: String message = JaiI18N.getString("PNGImageDecoder2");
0498: ImagingListenerProxy.errorOccurred(message,
0499: new ImagingException(message, e), this , false);
0500: /* e.printStackTrace();
0501: String msg = JaiI18N.getString("PNGImageDecoder2");
0502: throw new RuntimeException(msg);
0503: */
0504: }
0505: } while (true);
0506:
0507: // Final post-processing
0508:
0509: if (significantBits == null) {
0510: significantBits = new int[inputBands];
0511: for (int i = 0; i < inputBands; i++) {
0512: significantBits[i] = bitDepth;
0513: }
0514:
0515: if (emitProperties) {
0516: properties.put("significant_bits", significantBits);
0517: }
0518: }
0519: }
0520:
0521: private static String getChunkType(DataInputStream distream) {
0522: try {
0523: distream.mark(8);
0524: int length = distream.readInt();
0525: int type = distream.readInt();
0526: distream.reset();
0527:
0528: String typeString = new String();
0529: typeString += (char) (type >> 24);
0530: typeString += (char) ((type >> 16) & 0xff);
0531: typeString += (char) ((type >> 8) & 0xff);
0532: typeString += (char) (type & 0xff);
0533: return typeString;
0534: } catch (Exception e) {
0535: ImagingListenerProxy.errorOccurred(JaiI18N
0536: .getString("PNGImageDecoder20"), e,
0537: PNGImageDecoder.class, false);
0538: // e.printStackTrace();
0539: return null;
0540: }
0541: }
0542:
0543: private static PNGChunk readChunk(DataInputStream distream) {
0544: try {
0545: int length = distream.readInt();
0546: int type = distream.readInt();
0547: byte[] data = new byte[length];
0548: distream.readFully(data);
0549: int crc = distream.readInt();
0550:
0551: return new PNGChunk(length, type, data, crc);
0552: } catch (Exception e) {
0553: ImagingListenerProxy.errorOccurred(JaiI18N
0554: .getString("PNGImageDecoder21"), e,
0555: PNGImageDecoder.class, false);
0556: // e.printStackTrace();
0557: return null;
0558: }
0559: }
0560:
0561: private void parse_IHDR_chunk(PNGChunk chunk) {
0562: tileWidth = width = chunk.getInt4(0);
0563: tileHeight = height = chunk.getInt4(4);
0564:
0565: bitDepth = chunk.getInt1(8);
0566:
0567: if ((bitDepth != 1) && (bitDepth != 2) && (bitDepth != 4)
0568: && (bitDepth != 8) && (bitDepth != 16)) {
0569: // Error -- bad bit depth
0570: throw new RuntimeException(JaiI18N
0571: .getString("PNGImageDecoder3"));
0572: }
0573: maxOpacity = (1 << bitDepth) - 1;
0574:
0575: colorType = chunk.getInt1(9);
0576: if ((colorType != PNG_COLOR_GRAY)
0577: && (colorType != PNG_COLOR_RGB)
0578: && (colorType != PNG_COLOR_PALETTE)
0579: && (colorType != PNG_COLOR_GRAY_ALPHA)
0580: && (colorType != PNG_COLOR_RGB_ALPHA)) {
0581: System.out.println(JaiI18N.getString("PNGImageDecoder4"));
0582: }
0583:
0584: if ((colorType == PNG_COLOR_RGB) && (bitDepth < 8)) {
0585: // Error -- RGB images must have 8 or 16 bits
0586: throw new RuntimeException(JaiI18N
0587: .getString("PNGImageDecoder5"));
0588: }
0589:
0590: if ((colorType == PNG_COLOR_PALETTE) && (bitDepth == 16)) {
0591: // Error -- palette images must have < 16 bits
0592: throw new RuntimeException(JaiI18N
0593: .getString("PNGImageDecoder6"));
0594: }
0595:
0596: if ((colorType == PNG_COLOR_GRAY_ALPHA) && (bitDepth < 8)) {
0597: // Error -- gray/alpha images must have >= 8 bits
0598: throw new RuntimeException(JaiI18N
0599: .getString("PNGImageDecoder7"));
0600: }
0601:
0602: if ((colorType == PNG_COLOR_RGB_ALPHA) && (bitDepth < 8)) {
0603: // Error -- RGB/alpha images must have >= 8 bits
0604: throw new RuntimeException(JaiI18N
0605: .getString("PNGImageDecoder8"));
0606: }
0607:
0608: if (emitProperties) {
0609: properties.put("color_type", colorTypeNames[colorType]);
0610: }
0611:
0612: if (generateEncodeParam) {
0613: if (colorType == PNG_COLOR_PALETTE) {
0614: encodeParam = new PNGEncodeParam.Palette();
0615: } else if (colorType == PNG_COLOR_GRAY
0616: || colorType == PNG_COLOR_GRAY_ALPHA) {
0617: encodeParam = new PNGEncodeParam.Gray();
0618: } else {
0619: encodeParam = new PNGEncodeParam.RGB();
0620: }
0621: decodeParam.setEncodeParam(encodeParam);
0622: }
0623:
0624: if (encodeParam != null) {
0625: encodeParam.setBitDepth(bitDepth);
0626: }
0627: if (emitProperties) {
0628: properties.put("bit_depth", new Integer(bitDepth));
0629: }
0630:
0631: if (performGammaCorrection) {
0632: // Assume file gamma is 1/2.2 unless we get a gAMA chunk
0633: float gamma = (1.0F / 2.2F)
0634: * (displayExponent / userExponent);
0635: if (encodeParam != null) {
0636: encodeParam.setGamma(gamma);
0637: }
0638: if (emitProperties) {
0639: properties.put("gamma", new Float(gamma));
0640: }
0641: }
0642:
0643: compressionMethod = chunk.getInt1(10);
0644: if (compressionMethod != 0) {
0645: // Error -- only know about compression method 0
0646: throw new RuntimeException(JaiI18N
0647: .getString("PNGImageDecoder9"));
0648: }
0649:
0650: filterMethod = chunk.getInt1(11);
0651: if (filterMethod != 0) {
0652: // Error -- only know about filter method 0
0653: throw new RuntimeException(JaiI18N
0654: .getString("PNGImageDecoder10"));
0655: }
0656:
0657: interlaceMethod = chunk.getInt1(12);
0658: if (interlaceMethod == 0) {
0659: if (encodeParam != null) {
0660: encodeParam.setInterlacing(false);
0661: }
0662: if (emitProperties) {
0663: properties.put("interlace_method", "None");
0664: }
0665: } else if (interlaceMethod == 1) {
0666: if (encodeParam != null) {
0667: encodeParam.setInterlacing(true);
0668: }
0669: if (emitProperties) {
0670: properties.put("interlace_method", "Adam7");
0671: }
0672: } else {
0673: // Error -- only know about Adam7 interlacing
0674: throw new RuntimeException(JaiI18N
0675: .getString("PNGImageDecoder11"));
0676: }
0677:
0678: bytesPerPixel = (bitDepth == 16) ? 2 : 1;
0679:
0680: switch (colorType) {
0681: case PNG_COLOR_GRAY:
0682: inputBands = 1;
0683: outputBands = 1;
0684:
0685: if (output8BitGray && (bitDepth < 8)) {
0686: postProcess = POST_GRAY_LUT;
0687: } else if (performGammaCorrection) {
0688: postProcess = POST_GAMMA;
0689: } else {
0690: postProcess = POST_NONE;
0691: }
0692: break;
0693:
0694: case PNG_COLOR_RGB:
0695: inputBands = 3;
0696: bytesPerPixel *= 3;
0697: outputBands = 3;
0698:
0699: if (performGammaCorrection) {
0700: postProcess = POST_GAMMA;
0701: } else {
0702: postProcess = POST_NONE;
0703: }
0704: break;
0705:
0706: case PNG_COLOR_PALETTE:
0707: inputBands = 1;
0708: bytesPerPixel = 1;
0709: outputBands = expandPalette ? 3 : 1;
0710:
0711: if (expandPalette) {
0712: postProcess = POST_PALETTE_TO_RGB;
0713: } else {
0714: postProcess = POST_NONE;
0715: }
0716: break;
0717:
0718: case PNG_COLOR_GRAY_ALPHA:
0719: inputBands = 2;
0720: bytesPerPixel *= 2;
0721:
0722: if (suppressAlpha) {
0723: outputBands = 1;
0724: postProcess = POST_REMOVE_GRAY_TRANS;
0725: } else {
0726: if (performGammaCorrection) {
0727: postProcess = POST_GAMMA;
0728: } else {
0729: postProcess = POST_NONE;
0730: }
0731: if (expandGrayAlpha) {
0732: postProcess |= POST_EXP_MASK;
0733: outputBands = 4;
0734: } else {
0735: outputBands = 2;
0736: }
0737: }
0738: break;
0739:
0740: case PNG_COLOR_RGB_ALPHA:
0741: inputBands = 4;
0742: bytesPerPixel *= 4;
0743: outputBands = (!suppressAlpha) ? 4 : 3;
0744:
0745: if (suppressAlpha) {
0746: postProcess = POST_REMOVE_RGB_TRANS;
0747: } else if (performGammaCorrection) {
0748: postProcess = POST_GAMMA;
0749: } else {
0750: postProcess = POST_NONE;
0751: }
0752: break;
0753: }
0754: }
0755:
0756: private void parse_IEND_chunk(PNGChunk chunk) throws Exception {
0757: // Store text strings
0758: int textLen = textKeys.size();
0759: String[] textArray = new String[2 * textLen];
0760: for (int i = 0; i < textLen; i++) {
0761: String key = (String) textKeys.elementAt(i);
0762: String val = (String) textStrings.elementAt(i);
0763: textArray[2 * i] = key;
0764: textArray[2 * i + 1] = val;
0765: if (emitProperties) {
0766: String uniqueKey = "text_" + i + ":" + key;
0767: properties.put(uniqueKey.toLowerCase(), val);
0768: }
0769: }
0770: if (encodeParam != null) {
0771: encodeParam.setText(textArray);
0772: }
0773:
0774: // Store compressed text strings
0775: int ztextLen = ztextKeys.size();
0776: String[] ztextArray = new String[2 * ztextLen];
0777: for (int i = 0; i < ztextLen; i++) {
0778: String key = (String) ztextKeys.elementAt(i);
0779: String val = (String) ztextStrings.elementAt(i);
0780: ztextArray[2 * i] = key;
0781: ztextArray[2 * i + 1] = val;
0782: if (emitProperties) {
0783: String uniqueKey = "ztext_" + i + ":" + key;
0784: properties.put(uniqueKey.toLowerCase(), val);
0785: }
0786: }
0787: if (encodeParam != null) {
0788: encodeParam.setCompressedText(ztextArray);
0789: }
0790:
0791: // detect sRGB & iCCP conflict
0792: if (sRGBRenderingIntent != -1 && iccProfile != null) {
0793: // resolve by dropping ICC Profile
0794: iccProfile = null;
0795: }
0796:
0797: // add iccProfile to encodeParam
0798: if (encodeParam != null && iccProfile != null) {
0799: encodeParam.setICCProfileData(iccProfile.getData());
0800: encodeParam.setICCProfileName(iccProfileName);
0801: }
0802:
0803: // Parse prior IDAT chunks
0804: InputStream seqStream = new SequenceInputStream(streamVec
0805: .elements());
0806: InputStream infStream = new InflaterInputStream(seqStream,
0807: new Inflater());
0808: dataStream = new DataInputStream(infStream);
0809:
0810: // Create an empty WritableRaster
0811: int depth = bitDepth;
0812: if ((colorType == PNG_COLOR_GRAY) && (bitDepth < 8)
0813: && output8BitGray) {
0814: depth = 8;
0815: }
0816: if ((colorType == PNG_COLOR_PALETTE) && expandPalette) {
0817: depth = 8;
0818: }
0819: int bytesPerRow = (outputBands * width * depth + 7) / 8;
0820: int scanlineStride = (depth == 16) ? (bytesPerRow / 2)
0821: : bytesPerRow;
0822:
0823: theTile = createRaster(width, height, outputBands,
0824: scanlineStride, depth);
0825:
0826: if (performGammaCorrection && (gammaLut == null)) {
0827: initGammaLut(bitDepth);
0828: }
0829: if ((postProcess == POST_GRAY_LUT)
0830: || (postProcess == POST_GRAY_LUT_ADD_TRANS)
0831: || (postProcess == POST_GRAY_LUT_ADD_TRANS_EXP)) {
0832: initGrayLut(bitDepth);
0833: }
0834:
0835: decodeImage(interlaceMethod == 1);
0836: sampleModel = theTile.getSampleModel();
0837:
0838: if ((colorType == PNG_COLOR_PALETTE) && !expandPalette) {
0839: if (outputHasAlphaPalette) {
0840: colorModel = new IndexColorModel(bitDepth,
0841: paletteEntries, redPalette, greenPalette,
0842: bluePalette, alphaPalette);
0843: } else {
0844: colorModel = new IndexColorModel(bitDepth,
0845: paletteEntries, redPalette, greenPalette,
0846: bluePalette);
0847: }
0848: } else if ((colorType == PNG_COLOR_GRAY) && (bitDepth < 8)
0849: && !output8BitGray) {
0850: byte[] palette = expandBits[bitDepth];
0851: colorModel = new IndexColorModel(bitDepth, palette.length,
0852: palette, palette, palette);
0853: } else {
0854: colorModel = ImageCodec.createComponentColorModel(
0855: sampleModel, iccProfile == null ? null
0856: : new ICC_ColorSpace(iccProfile));
0857: }
0858: }
0859:
0860: private void parse_PLTE_chunk(PNGChunk chunk) {
0861: paletteEntries = chunk.getLength() / 3;
0862: redPalette = new byte[paletteEntries];
0863: greenPalette = new byte[paletteEntries];
0864: bluePalette = new byte[paletteEntries];
0865:
0866: int pltIndex = 0;
0867:
0868: // gAMA chunk must precede PLTE chunk
0869: if (performGammaCorrection) {
0870: if (gammaLut == null) {
0871: initGammaLut(bitDepth == 16 ? 16 : 8);
0872: }
0873:
0874: for (int i = 0; i < paletteEntries; i++) {
0875: byte r = chunk.getByte(pltIndex++);
0876: byte g = chunk.getByte(pltIndex++);
0877: byte b = chunk.getByte(pltIndex++);
0878:
0879: redPalette[i] = (byte) gammaLut[r & 0xff];
0880: greenPalette[i] = (byte) gammaLut[g & 0xff];
0881: bluePalette[i] = (byte) gammaLut[b & 0xff];
0882: }
0883: } else {
0884: for (int i = 0; i < paletteEntries; i++) {
0885: redPalette[i] = chunk.getByte(pltIndex++);
0886: greenPalette[i] = chunk.getByte(pltIndex++);
0887: bluePalette[i] = chunk.getByte(pltIndex++);
0888: }
0889: }
0890: }
0891:
0892: private void parse_bKGD_chunk(PNGChunk chunk) {
0893: hasBackground = true;
0894:
0895: switch (colorType) {
0896: case PNG_COLOR_PALETTE:
0897: int bkgdIndex = chunk.getByte(0) & 0xff;
0898:
0899: bkgdRed = redPalette[bkgdIndex] & 0xff;
0900: bkgdGreen = greenPalette[bkgdIndex] & 0xff;
0901: bkgdBlue = bluePalette[bkgdIndex] & 0xff;
0902:
0903: if (encodeParam != null) {
0904: ((PNGEncodeParam.Palette) encodeParam)
0905: .setBackgroundPaletteIndex(bkgdIndex);
0906: }
0907: break;
0908: case PNG_COLOR_GRAY:
0909: case PNG_COLOR_GRAY_ALPHA:
0910: int bkgdGray = chunk.getInt2(0);
0911: bkgdRed = bkgdGreen = bkgdBlue = bkgdGray;
0912:
0913: if (encodeParam != null) {
0914: ((PNGEncodeParam.Gray) encodeParam)
0915: .setBackgroundGray(bkgdGray);
0916: }
0917: break;
0918: case PNG_COLOR_RGB:
0919: case PNG_COLOR_RGB_ALPHA:
0920: // Fix 4625294: In the case of bitDepth = 8,
0921: // when the background color values is larger
0922: // than 128, and the encoder copies the byte into a short
0923: // without masking, the decoded background values may be
0924: // out of 8 bit range. So mask them here to avoid the
0925: // exception thrown by the constructor of Color.
0926: // So mask to make it safe even when the values exceeds
0927: // the range.
0928: int mask = (1 << bitDepth) - 1;
0929: bkgdRed = chunk.getInt2(0) & mask;
0930: bkgdGreen = chunk.getInt2(2) & mask;
0931: bkgdBlue = chunk.getInt2(4) & mask;
0932:
0933: int[] bkgdRGB = new int[3];
0934: bkgdRGB[0] = bkgdRed;
0935: bkgdRGB[1] = bkgdGreen;
0936: bkgdRGB[2] = bkgdBlue;
0937: if (encodeParam != null) {
0938: ((PNGEncodeParam.RGB) encodeParam)
0939: .setBackgroundRGB(bkgdRGB);
0940: }
0941: break;
0942: }
0943:
0944: int r = 0, g = 0, b = 0;
0945: if (bitDepth < 8) {
0946: r = expandBits[bitDepth][bkgdRed];
0947: g = expandBits[bitDepth][bkgdGreen];
0948: b = expandBits[bitDepth][bkgdBlue];
0949: } else if (bitDepth == 8) {
0950: r = bkgdRed;
0951: g = bkgdGreen;
0952: b = bkgdBlue;
0953: } else if (bitDepth == 16) {
0954: r = bkgdRed >> 8;
0955: g = bkgdGreen >> 8;
0956: b = bkgdBlue >> 8;
0957: }
0958: if (emitProperties) {
0959: properties.put("background_color", new Color(r, g, b));
0960: }
0961: }
0962:
0963: private void parse_cHRM_chunk(PNGChunk chunk) {
0964: // If an sRGB chunk exists, ignore cHRM chunks
0965: if (sRGBRenderingIntent != -1) {
0966: return;
0967: }
0968:
0969: chromaticity = new float[8];
0970: chromaticity[0] = chunk.getInt4(0) / 100000.0F;
0971: chromaticity[1] = chunk.getInt4(4) / 100000.0F;
0972: chromaticity[2] = chunk.getInt4(8) / 100000.0F;
0973: chromaticity[3] = chunk.getInt4(12) / 100000.0F;
0974: chromaticity[4] = chunk.getInt4(16) / 100000.0F;
0975: chromaticity[5] = chunk.getInt4(20) / 100000.0F;
0976: chromaticity[6] = chunk.getInt4(24) / 100000.0F;
0977: chromaticity[7] = chunk.getInt4(28) / 100000.0F;
0978:
0979: if (encodeParam != null) {
0980: encodeParam.setChromaticity(chromaticity);
0981: }
0982: if (emitProperties) {
0983: properties.put("white_point_x", new Float(chromaticity[0]));
0984: properties.put("white_point_y", new Float(chromaticity[1]));
0985: properties.put("red_x", new Float(chromaticity[2]));
0986: properties.put("red_y", new Float(chromaticity[3]));
0987: properties.put("green_x", new Float(chromaticity[4]));
0988: properties.put("green_y", new Float(chromaticity[5]));
0989: properties.put("blue_x", new Float(chromaticity[6]));
0990: properties.put("blue_y", new Float(chromaticity[7]));
0991: }
0992: }
0993:
0994: private void parse_gAMA_chunk(PNGChunk chunk) {
0995: // If an sRGB chunk exists, ignore gAMA chunks
0996: if (sRGBRenderingIntent != -1) {
0997: return;
0998: }
0999:
1000: fileGamma = chunk.getInt4(0) / 100000.0F;
1001:
1002: float exp = performGammaCorrection ? displayExponent
1003: / userExponent : 1.0F;
1004: if (encodeParam != null) {
1005: encodeParam.setGamma(fileGamma * exp);
1006: }
1007: if (emitProperties) {
1008: properties.put("gamma", new Float(fileGamma * exp));
1009: }
1010: }
1011:
1012: private void parse_hIST_chunk(PNGChunk chunk) {
1013: if (redPalette == null) {
1014: throw new RuntimeException(JaiI18N
1015: .getString("PNGImageDecoder18"));
1016: }
1017:
1018: int length = redPalette.length;
1019: int[] hist = new int[length];
1020: for (int i = 0; i < length; i++) {
1021: hist[i] = chunk.getInt2(2 * i);
1022: }
1023:
1024: if (encodeParam != null) {
1025: encodeParam.setPaletteHistogram(hist);
1026: }
1027: }
1028:
1029: private void parse_iCCP_chunk(PNGChunk chunk) {
1030: byte b;
1031: byte[] data = new byte[80];
1032: int pos = 0;
1033: while (pos < 79 && (b = chunk.getByte(pos)) != 0) {
1034: data[pos++] = b;
1035: }
1036:
1037: data[pos] = 0;
1038: String name = new String(data);
1039: byte compMethod = chunk.getByte(pos++);
1040: InflaterInputStream infls = new InflaterInputStream(
1041: new ByteArrayInputStream(chunk.getData(), pos, chunk
1042: .getLength()
1043: - pos));
1044: try {
1045: iccProfile = ICC_Profile.getInstance(infls);
1046: iccProfileName = name;
1047: } catch (IOException e) {
1048: iccProfile = null;
1049: iccProfileName = null;
1050: }
1051:
1052: }
1053:
1054: private void parse_pHYs_chunk(PNGChunk chunk) {
1055: int xPixelsPerUnit = chunk.getInt4(0);
1056: int yPixelsPerUnit = chunk.getInt4(4);
1057: int unitSpecifier = chunk.getInt1(8);
1058:
1059: if (encodeParam != null) {
1060: encodeParam.setPhysicalDimension(xPixelsPerUnit,
1061: yPixelsPerUnit, unitSpecifier);
1062: }
1063: if (emitProperties) {
1064: properties.put("x_pixels_per_unit", new Integer(
1065: xPixelsPerUnit));
1066: properties.put("y_pixels_per_unit", new Integer(
1067: yPixelsPerUnit));
1068: properties.put("pixel_aspect_ratio", new Float(
1069: (float) xPixelsPerUnit / yPixelsPerUnit));
1070: if (unitSpecifier == 1) {
1071: properties.put("pixel_units", "Meters");
1072: } else if (unitSpecifier != 0) {
1073: // Error -- unit specifier must be 0 or 1
1074: throw new RuntimeException(JaiI18N
1075: .getString("PNGImageDecoder12"));
1076: }
1077: }
1078: }
1079:
1080: private void parse_sBIT_chunk(PNGChunk chunk) {
1081: if (colorType == PNG_COLOR_PALETTE) {
1082: significantBits = new int[3];
1083: } else {
1084: significantBits = new int[inputBands];
1085: }
1086: for (int i = 0; i < significantBits.length; i++) {
1087: int bits = (int) chunk.getByte(i);
1088: int depth = (colorType == PNG_COLOR_PALETTE) ? 8 : bitDepth;
1089: if (bits <= 0 || bits > depth) {
1090: // Error -- significant bits must be between 0 and
1091: // image bit depth.
1092: throw new RuntimeException(JaiI18N
1093: .getString("PNGImageDecoder13"));
1094: }
1095: significantBits[i] = bits;
1096: }
1097:
1098: if (encodeParam != null) {
1099: encodeParam.setSignificantBits(significantBits);
1100: }
1101: if (emitProperties) {
1102: properties.put("significant_bits", significantBits);
1103: }
1104: }
1105:
1106: private void parse_sRGB_chunk(PNGChunk chunk) {
1107: sRGBRenderingIntent = chunk.getByte(0);
1108:
1109: // The presence of an sRGB chunk implies particular
1110: // settings for gamma and chroma.
1111: fileGamma = 45455 / 100000.0F;
1112:
1113: chromaticity = new float[8];
1114: chromaticity[0] = 31270 / 10000.0F;
1115: chromaticity[1] = 32900 / 10000.0F;
1116: chromaticity[2] = 64000 / 10000.0F;
1117: chromaticity[3] = 33000 / 10000.0F;
1118: chromaticity[4] = 30000 / 10000.0F;
1119: chromaticity[5] = 60000 / 10000.0F;
1120: chromaticity[6] = 15000 / 10000.0F;
1121: chromaticity[7] = 6000 / 10000.0F;
1122:
1123: if (performGammaCorrection) {
1124: // File gamma is 1/2.2
1125: float gamma = fileGamma * (displayExponent / userExponent);
1126: if (encodeParam != null) {
1127: encodeParam.setGamma(gamma);
1128: encodeParam.setChromaticity(chromaticity);
1129: }
1130: if (emitProperties) {
1131: properties.put("gamma", new Float(gamma));
1132: properties.put("white_point_x", new Float(
1133: chromaticity[0]));
1134: properties.put("white_point_y", new Float(
1135: chromaticity[1]));
1136: properties.put("red_x", new Float(chromaticity[2]));
1137: properties.put("red_y", new Float(chromaticity[3]));
1138: properties.put("green_x", new Float(chromaticity[4]));
1139: properties.put("green_y", new Float(chromaticity[5]));
1140: properties.put("blue_x", new Float(chromaticity[6]));
1141: properties.put("blue_y", new Float(chromaticity[7]));
1142: }
1143: }
1144: }
1145:
1146: private void parse_tEXt_chunk(PNGChunk chunk) {
1147: String key = new String();
1148: String value = new String();
1149: byte b;
1150:
1151: int textIndex = 0;
1152: while ((b = chunk.getByte(textIndex++)) != 0) {
1153: key += (char) b;
1154: }
1155:
1156: for (int i = textIndex; i < chunk.getLength(); i++) {
1157: value += (char) chunk.getByte(i);
1158: }
1159:
1160: textKeys.add(key);
1161: textStrings.add(value);
1162: }
1163:
1164: private void parse_tIME_chunk(PNGChunk chunk) {
1165: int year = chunk.getInt2(0);
1166: int month = chunk.getInt1(2) - 1;
1167: int day = chunk.getInt1(3);
1168: int hour = chunk.getInt1(4);
1169: int minute = chunk.getInt1(5);
1170: int second = chunk.getInt1(6);
1171:
1172: TimeZone gmt = TimeZone.getTimeZone("GMT");
1173:
1174: GregorianCalendar cal = new GregorianCalendar(gmt);
1175: cal.set(year, month, day, hour, minute, second);
1176: Date date = cal.getTime();
1177:
1178: if (encodeParam != null) {
1179: encodeParam.setModificationTime(date);
1180: }
1181: if (emitProperties) {
1182: properties.put("timestamp", date);
1183: }
1184: }
1185:
1186: private void parse_tRNS_chunk(PNGChunk chunk) {
1187: if (colorType == PNG_COLOR_PALETTE) {
1188: int entries = chunk.getLength();
1189: if (entries > paletteEntries) {
1190: // Error -- mustn't have more alpha than RGB palette entries
1191: throw new RuntimeException(JaiI18N
1192: .getString("PNGImageDecoder14"));
1193: }
1194:
1195: // Load beginning of palette from the chunk
1196: alphaPalette = new byte[paletteEntries];
1197: for (int i = 0; i < entries; i++) {
1198: alphaPalette[i] = chunk.getByte(i);
1199: }
1200:
1201: // Fill rest of palette with 255
1202: for (int i = entries; i < paletteEntries; i++) {
1203: alphaPalette[i] = (byte) 255;
1204: }
1205:
1206: if (!suppressAlpha) {
1207: if (expandPalette) {
1208: postProcess = POST_PALETTE_TO_RGBA;
1209: outputBands = 4;
1210: } else {
1211: outputHasAlphaPalette = true;
1212: }
1213: }
1214: } else if (colorType == PNG_COLOR_GRAY) {
1215: grayTransparentAlpha = chunk.getInt2(0);
1216:
1217: if (!suppressAlpha) {
1218: if (bitDepth < 8) {
1219: output8BitGray = true;
1220: maxOpacity = 255;
1221: postProcess = POST_GRAY_LUT_ADD_TRANS;
1222: } else {
1223: postProcess = POST_ADD_GRAY_TRANS;
1224: }
1225:
1226: if (expandGrayAlpha) {
1227: outputBands = 4;
1228: postProcess |= POST_EXP_MASK;
1229: } else {
1230: outputBands = 2;
1231: }
1232:
1233: if (encodeParam != null) {
1234: ((PNGEncodeParam.Gray) encodeParam)
1235: .setTransparentGray(grayTransparentAlpha);
1236: }
1237: }
1238: } else if (colorType == PNG_COLOR_RGB) {
1239: redTransparentAlpha = chunk.getInt2(0);
1240: greenTransparentAlpha = chunk.getInt2(2);
1241: blueTransparentAlpha = chunk.getInt2(4);
1242:
1243: if (!suppressAlpha) {
1244: outputBands = 4;
1245: postProcess = POST_ADD_RGB_TRANS;
1246:
1247: if (encodeParam != null) {
1248: int[] rgbTrans = new int[3];
1249: rgbTrans[0] = redTransparentAlpha;
1250: rgbTrans[1] = greenTransparentAlpha;
1251: rgbTrans[2] = blueTransparentAlpha;
1252: ((PNGEncodeParam.RGB) encodeParam)
1253: .setTransparentRGB(rgbTrans);
1254: }
1255: }
1256: } else if (colorType == PNG_COLOR_GRAY_ALPHA
1257: || colorType == PNG_COLOR_RGB_ALPHA) {
1258: // Error -- GA or RGBA image can't have a tRNS chunk.
1259: throw new RuntimeException(JaiI18N
1260: .getString("PNGImageDecoder15"));
1261: }
1262: }
1263:
1264: private void parse_zTXt_chunk(PNGChunk chunk) {
1265: String key = new String();
1266: String value = new String();
1267: byte b;
1268:
1269: int textIndex = 0;
1270: while ((b = chunk.getByte(textIndex++)) != 0) {
1271: key += (char) b;
1272: }
1273: int method = chunk.getByte(textIndex++);
1274:
1275: try {
1276: int length = chunk.getLength() - textIndex;
1277: byte[] data = chunk.getData();
1278: InputStream cis = new ByteArrayInputStream(data, textIndex,
1279: length);
1280: InputStream iis = new InflaterInputStream(cis);
1281:
1282: int c;
1283: while ((c = iis.read()) != -1) {
1284: value += (char) c;
1285: }
1286:
1287: ztextKeys.add(key);
1288: ztextStrings.add(value);
1289: } catch (Exception e) {
1290: ImagingListenerProxy.errorOccurred(JaiI18N
1291: .getString("PNGImageDecoder21"), e, this , false);
1292: // e.printStackTrace();
1293: }
1294: }
1295:
1296: private WritableRaster createRaster(int width, int height,
1297: int bands, int scanlineStride, int bitDepth) {
1298:
1299: DataBuffer dataBuffer;
1300: WritableRaster ras = null;
1301: Point origin = new Point(0, 0);
1302: if ((bitDepth < 8) && (bands == 1)) {
1303: dataBuffer = new DataBufferByte(height * scanlineStride);
1304: ras = Raster.createPackedRaster(dataBuffer, width, height,
1305: bitDepth, origin);
1306: } else if (bitDepth <= 8) {
1307: dataBuffer = new DataBufferByte(height * scanlineStride);
1308: ras = Raster.createInterleavedRaster(dataBuffer, width,
1309: height, scanlineStride, bands, bandOffsets[bands],
1310: origin);
1311: } else {
1312: dataBuffer = new DataBufferUShort(height * scanlineStride);
1313: ras = Raster.createInterleavedRaster(dataBuffer, width,
1314: height, scanlineStride, bands, bandOffsets[bands],
1315: origin);
1316: }
1317:
1318: return ras;
1319: }
1320:
1321: // Data filtering methods
1322:
1323: private static void decodeSubFilter(byte[] curr, int count, int bpp) {
1324: for (int i = bpp; i < count; i++) {
1325: int val;
1326:
1327: val = curr[i] & 0xff;
1328: val += curr[i - bpp] & 0xff;
1329:
1330: curr[i] = (byte) val;
1331: }
1332: }
1333:
1334: private static void decodeUpFilter(byte[] curr, byte[] prev,
1335: int count) {
1336: for (int i = 0; i < count; i++) {
1337: int raw = curr[i] & 0xff;
1338: int prior = prev[i] & 0xff;
1339:
1340: curr[i] = (byte) (raw + prior);
1341: }
1342: }
1343:
1344: private static void decodeAverageFilter(byte[] curr, byte[] prev,
1345: int count, int bpp) {
1346: int raw, priorPixel, priorRow;
1347:
1348: for (int i = 0; i < bpp; i++) {
1349: raw = curr[i] & 0xff;
1350: priorRow = prev[i] & 0xff;
1351:
1352: curr[i] = (byte) (raw + priorRow / 2);
1353: }
1354:
1355: for (int i = bpp; i < count; i++) {
1356: raw = curr[i] & 0xff;
1357: priorPixel = curr[i - bpp] & 0xff;
1358: priorRow = prev[i] & 0xff;
1359:
1360: curr[i] = (byte) (raw + (priorPixel + priorRow) / 2);
1361: }
1362: }
1363:
1364: private static int paethPredictor(int a, int b, int c) {
1365: int p = a + b - c;
1366: int pa = Math.abs(p - a);
1367: int pb = Math.abs(p - b);
1368: int pc = Math.abs(p - c);
1369:
1370: if ((pa <= pb) && (pa <= pc)) {
1371: return a;
1372: } else if (pb <= pc) {
1373: return b;
1374: } else {
1375: return c;
1376: }
1377: }
1378:
1379: private static void decodePaethFilter(byte[] curr, byte[] prev,
1380: int count, int bpp) {
1381: int raw, priorPixel, priorRow, priorRowPixel;
1382:
1383: for (int i = 0; i < bpp; i++) {
1384: raw = curr[i] & 0xff;
1385: priorRow = prev[i] & 0xff;
1386:
1387: curr[i] = (byte) (raw + priorRow);
1388: }
1389:
1390: for (int i = bpp; i < count; i++) {
1391: raw = curr[i] & 0xff;
1392: priorPixel = curr[i - bpp] & 0xff;
1393: priorRow = prev[i] & 0xff;
1394: priorRowPixel = prev[i - bpp] & 0xff;
1395:
1396: curr[i] = (byte) (raw + paethPredictor(priorPixel,
1397: priorRow, priorRowPixel));
1398: }
1399: }
1400:
1401: private void processPixels(int process, Raster src,
1402: WritableRaster dst, int xOffset, int step, int y, int width) {
1403: int srcX, dstX;
1404:
1405: // Create an array suitable for holding one pixel
1406: int[] ps = src.getPixel(0, 0, (int[]) null);
1407: int[] pd = dst.getPixel(0, 0, (int[]) null);
1408:
1409: dstX = xOffset;
1410: switch (process) {
1411: case POST_NONE:
1412: for (srcX = 0; srcX < width; srcX++) {
1413: src.getPixel(srcX, 0, ps);
1414: dst.setPixel(dstX, y, ps);
1415: dstX += step;
1416: }
1417: break;
1418:
1419: case POST_GAMMA:
1420: for (srcX = 0; srcX < width; srcX++) {
1421: src.getPixel(srcX, 0, ps);
1422:
1423: for (int i = 0; i < inputBands; i++) {
1424: int x = ps[i];
1425: ps[i] = gammaLut[x];
1426: }
1427:
1428: dst.setPixel(dstX, y, ps);
1429: dstX += step;
1430: }
1431: break;
1432:
1433: case POST_GRAY_LUT:
1434: for (srcX = 0; srcX < width; srcX++) {
1435: src.getPixel(srcX, 0, ps);
1436:
1437: pd[0] = grayLut[ps[0]];
1438:
1439: dst.setPixel(dstX, y, pd);
1440: dstX += step;
1441: }
1442: break;
1443:
1444: case POST_GRAY_LUT_ADD_TRANS:
1445: for (srcX = 0; srcX < width; srcX++) {
1446: src.getPixel(srcX, 0, ps);
1447:
1448: int val = ps[0];
1449: pd[0] = grayLut[val];
1450: if (val == grayTransparentAlpha) {
1451: pd[1] = 0;
1452: } else {
1453: pd[1] = maxOpacity;
1454: }
1455:
1456: dst.setPixel(dstX, y, pd);
1457: dstX += step;
1458: }
1459: break;
1460:
1461: case POST_PALETTE_TO_RGB:
1462: for (srcX = 0; srcX < width; srcX++) {
1463: src.getPixel(srcX, 0, ps);
1464:
1465: int val = ps[0];
1466: pd[0] = redPalette[val];
1467: pd[1] = greenPalette[val];
1468: pd[2] = bluePalette[val];
1469:
1470: dst.setPixel(dstX, y, pd);
1471: dstX += step;
1472: }
1473: break;
1474:
1475: case POST_PALETTE_TO_RGBA:
1476: for (srcX = 0; srcX < width; srcX++) {
1477: src.getPixel(srcX, 0, ps);
1478:
1479: int val = ps[0];
1480: pd[0] = redPalette[val];
1481: pd[1] = greenPalette[val];
1482: pd[2] = bluePalette[val];
1483: pd[3] = alphaPalette[val];
1484:
1485: dst.setPixel(dstX, y, pd);
1486: dstX += step;
1487: }
1488: break;
1489:
1490: case POST_ADD_GRAY_TRANS:
1491: for (srcX = 0; srcX < width; srcX++) {
1492: src.getPixel(srcX, 0, ps);
1493:
1494: int val = ps[0];
1495: if (performGammaCorrection) {
1496: val = gammaLut[val];
1497: }
1498: pd[0] = val;
1499: if (val == grayTransparentAlpha) {
1500: pd[1] = 0;
1501: } else {
1502: pd[1] = maxOpacity;
1503: }
1504:
1505: dst.setPixel(dstX, y, pd);
1506: dstX += step;
1507: }
1508: break;
1509:
1510: case POST_ADD_RGB_TRANS:
1511: for (srcX = 0; srcX < width; srcX++) {
1512: src.getPixel(srcX, 0, ps);
1513:
1514: int r = ps[0];
1515: int g = ps[1];
1516: int b = ps[2];
1517: if (performGammaCorrection) {
1518: pd[0] = gammaLut[r];
1519: pd[1] = gammaLut[g];
1520: pd[2] = gammaLut[b];
1521: } else {
1522: pd[0] = r;
1523: pd[1] = g;
1524: pd[2] = b;
1525: }
1526: if ((r == redTransparentAlpha)
1527: && (g == greenTransparentAlpha)
1528: && (b == blueTransparentAlpha)) {
1529: pd[3] = 0;
1530: } else {
1531: pd[3] = maxOpacity;
1532: }
1533:
1534: dst.setPixel(dstX, y, pd);
1535: dstX += step;
1536: }
1537: break;
1538:
1539: case POST_REMOVE_GRAY_TRANS:
1540: for (srcX = 0; srcX < width; srcX++) {
1541: src.getPixel(srcX, 0, ps);
1542:
1543: int g = ps[0];
1544: if (performGammaCorrection) {
1545: pd[0] = gammaLut[g];
1546: } else {
1547: pd[0] = g;
1548: }
1549:
1550: dst.setPixel(dstX, y, pd);
1551: dstX += step;
1552: }
1553: break;
1554:
1555: case POST_REMOVE_RGB_TRANS:
1556: for (srcX = 0; srcX < width; srcX++) {
1557: src.getPixel(srcX, 0, ps);
1558:
1559: int r = ps[0];
1560: int g = ps[1];
1561: int b = ps[2];
1562: if (performGammaCorrection) {
1563: pd[0] = gammaLut[r];
1564: pd[1] = gammaLut[g];
1565: pd[2] = gammaLut[b];
1566: } else {
1567: pd[0] = r;
1568: pd[1] = g;
1569: pd[2] = b;
1570: }
1571:
1572: dst.setPixel(dstX, y, pd);
1573: dstX += step;
1574: }
1575: break;
1576:
1577: case POST_GAMMA_EXP:
1578: for (srcX = 0; srcX < width; srcX++) {
1579: src.getPixel(srcX, 0, ps);
1580:
1581: int val = ps[0];
1582: int alpha = ps[1];
1583: int gamma = gammaLut[val];
1584: pd[0] = gamma;
1585: pd[1] = gamma;
1586: pd[2] = gamma;
1587: pd[3] = alpha;
1588:
1589: dst.setPixel(dstX, y, pd);
1590: dstX += step;
1591: }
1592: break;
1593:
1594: case POST_GRAY_ALPHA_EXP:
1595: for (srcX = 0; srcX < width; srcX++) {
1596: src.getPixel(srcX, 0, ps);
1597:
1598: int val = ps[0];
1599: int alpha = ps[1];
1600: pd[0] = val;
1601: pd[1] = val;
1602: pd[2] = val;
1603: pd[3] = alpha;
1604:
1605: dst.setPixel(dstX, y, pd);
1606: dstX += step;
1607: }
1608: break;
1609:
1610: case POST_ADD_GRAY_TRANS_EXP:
1611: for (srcX = 0; srcX < width; srcX++) {
1612: src.getPixel(srcX, 0, ps);
1613:
1614: int val = ps[0];
1615: if (performGammaCorrection) {
1616: val = gammaLut[val];
1617: }
1618: pd[0] = val;
1619: pd[1] = val;
1620: pd[2] = val;
1621: if (val == grayTransparentAlpha) {
1622: pd[3] = 0;
1623: } else {
1624: pd[3] = maxOpacity;
1625: }
1626:
1627: dst.setPixel(dstX, y, pd);
1628: dstX += step;
1629: }
1630: break;
1631:
1632: case POST_GRAY_LUT_ADD_TRANS_EXP:
1633: for (srcX = 0; srcX < width; srcX++) {
1634: src.getPixel(srcX, 0, ps);
1635:
1636: int val = ps[0];
1637: int val2 = grayLut[val];
1638: pd[0] = val2;
1639: pd[1] = val2;
1640: pd[2] = val2;
1641: if (val == grayTransparentAlpha) {
1642: pd[3] = 0;
1643: } else {
1644: pd[3] = maxOpacity;
1645: }
1646:
1647: dst.setPixel(dstX, y, pd);
1648: dstX += step;
1649: }
1650: break;
1651: }
1652: }
1653:
1654: /**
1655: * Reads in an image of a given size and returns it as a
1656: * WritableRaster.
1657: */
1658: private void decodePass(WritableRaster imRas, int xOffset,
1659: int yOffset, int xStep, int yStep, int passWidth,
1660: int passHeight) {
1661: if ((passWidth == 0) || (passHeight == 0)) {
1662: return;
1663: }
1664:
1665: int bytesPerRow = (inputBands * passWidth * bitDepth + 7) / 8;
1666: int eltsPerRow = (bitDepth == 16) ? bytesPerRow / 2
1667: : bytesPerRow;
1668: byte[] curr = new byte[bytesPerRow];
1669: byte[] prior = new byte[bytesPerRow];
1670:
1671: // Create a 1-row tall Raster to hold the data
1672: WritableRaster passRow = createRaster(passWidth, 1, inputBands,
1673: eltsPerRow, bitDepth);
1674: DataBuffer dataBuffer = passRow.getDataBuffer();
1675: int type = dataBuffer.getDataType();
1676: byte[] byteData = null;
1677: short[] shortData = null;
1678: if (type == DataBuffer.TYPE_BYTE) {
1679: byteData = ((DataBufferByte) dataBuffer).getData();
1680: } else {
1681: shortData = ((DataBufferUShort) dataBuffer).getData();
1682: }
1683:
1684: // Decode the (sub)image row-by-row
1685: int srcY, dstY;
1686: for (srcY = 0, dstY = yOffset; srcY < passHeight; srcY++, dstY += yStep) {
1687: // Read the filter type byte and a row of data
1688: int filter = 0;
1689: try {
1690: filter = dataStream.read();
1691: dataStream.readFully(curr, 0, bytesPerRow);
1692: } catch (Exception e) {
1693: ImagingListenerProxy.errorOccurred(JaiI18N
1694: .getString("PNGImageDecoder2"), e, this , false);
1695: // e.printStackTrace();
1696: }
1697:
1698: switch (filter) {
1699: case PNG_FILTER_NONE:
1700: break;
1701: case PNG_FILTER_SUB:
1702: decodeSubFilter(curr, bytesPerRow, bytesPerPixel);
1703: break;
1704: case PNG_FILTER_UP:
1705: decodeUpFilter(curr, prior, bytesPerRow);
1706: break;
1707: case PNG_FILTER_AVERAGE:
1708: decodeAverageFilter(curr, prior, bytesPerRow,
1709: bytesPerPixel);
1710: break;
1711: case PNG_FILTER_PAETH:
1712: decodePaethFilter(curr, prior, bytesPerRow,
1713: bytesPerPixel);
1714: break;
1715: default:
1716: // Error -- uknown filter type
1717: throw new RuntimeException(JaiI18N
1718: .getString("PNGImageDecoder16"));
1719: }
1720:
1721: // Copy data into passRow byte by byte
1722: if (bitDepth < 16) {
1723: System.arraycopy(curr, 0, byteData, 0, bytesPerRow);
1724: } else {
1725: int idx = 0;
1726: for (int j = 0; j < eltsPerRow; j++) {
1727: shortData[j] = (short) ((curr[idx] << 8) | (curr[idx + 1] & 0xff));
1728: idx += 2;
1729: }
1730: }
1731:
1732: processPixels(postProcess, passRow, imRas, xOffset, xStep,
1733: dstY, passWidth);
1734:
1735: // Swap curr and prior
1736: byte[] tmp = prior;
1737: prior = curr;
1738: curr = tmp;
1739: }
1740: }
1741:
1742: private void decodeImage(boolean useInterlacing) {
1743: if (!useInterlacing) {
1744: decodePass(theTile, 0, 0, 1, 1, width, height);
1745: } else {
1746: decodePass(theTile, 0, 0, 8, 8, (width + 7) / 8,
1747: (height + 7) / 8);
1748: decodePass(theTile, 4, 0, 8, 8, (width + 3) / 8,
1749: (height + 7) / 8);
1750: decodePass(theTile, 0, 4, 4, 8, (width + 3) / 4,
1751: (height + 3) / 8);
1752: decodePass(theTile, 2, 0, 4, 4, (width + 1) / 4,
1753: (height + 3) / 4);
1754: decodePass(theTile, 0, 2, 2, 4, (width + 1) / 2,
1755: (height + 1) / 4);
1756: decodePass(theTile, 1, 0, 2, 2, width / 2, (height + 1) / 2);
1757: decodePass(theTile, 0, 1, 1, 2, width, height / 2);
1758: }
1759: }
1760:
1761: // RenderedImage stuff
1762:
1763: public Raster getTile(int tileX, int tileY) {
1764: if (tileX != 0 || tileY != 0) {
1765: // Error -- bad tile requested
1766: throw new IllegalArgumentException(JaiI18N
1767: .getString("PNGImageDecoder17"));
1768: }
1769: return theTile;
1770: }
1771:
1772: public void dispose() {
1773: theTile = null;
1774: }
1775: }
|