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