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