You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1730 lines
57 KiB
1730 lines
57 KiB
package com.fr.third.JAI; |
|
/* |
|
* Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions are met: |
|
* |
|
* -Redistributions of source code must retain the above copyright notice, this |
|
* list of conditions and the following disclaimer. |
|
* |
|
* -Redistribution in binary form must reproduct the above copyright notice, |
|
* this list of conditions and the following disclaimer in the documentation |
|
* and/or other materials provided with the distribution. |
|
* |
|
* Neither the name of Sun Microsystems, Inc. or the names of contributors may |
|
* be used to endorse or promote products derived from this software without |
|
* specific prior written permission. |
|
* |
|
* This software is provided "AS IS," without a warranty of any kind. ALL |
|
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY |
|
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR |
|
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE |
|
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING |
|
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS |
|
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, |
|
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER |
|
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF |
|
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGES. |
|
* |
|
* You acknowledge that Software is not designed,licensed or intended for use in |
|
* the design, construction, operation or maintenance of any nuclear facility. |
|
*/ |
|
import java.awt.Color; |
|
import java.awt.Point; |
|
import java.awt.RenderingHints; |
|
import java.awt.Transparency; |
|
import java.awt.color.ColorSpace; |
|
import java.awt.geom.AffineTransform; |
|
import java.awt.image.BufferedImage; |
|
import java.awt.image.ColorModel; |
|
import java.awt.image.ComponentColorModel; |
|
import java.awt.image.DataBuffer; |
|
import java.awt.image.DataBufferByte; |
|
import java.awt.image.DataBufferUShort; |
|
import java.awt.image.IndexColorModel; |
|
import java.awt.image.MultiPixelPackedSampleModel; |
|
import java.awt.image.PixelInterleavedSampleModel; |
|
import java.awt.image.Raster; |
|
import java.awt.image.RenderedImage; |
|
import java.awt.image.SampleModel; |
|
import java.awt.image.WritableRaster; |
|
import java.io.BufferedInputStream; |
|
import java.io.ByteArrayInputStream; |
|
import java.io.DataInputStream; |
|
import java.io.FileInputStream; |
|
import java.io.InputStream; |
|
import java.io.IOException; |
|
import java.io.SequenceInputStream; |
|
import java.text.DateFormat; |
|
import java.util.Date; |
|
import java.util.Enumeration; |
|
import java.util.GregorianCalendar; |
|
import java.util.Hashtable; |
|
import java.util.TimeZone; |
|
import java.util.Vector; |
|
import java.util.zip.Inflater; |
|
import java.util.zip.InflaterInputStream; |
|
|
|
/** |
|
*/ |
|
public class PNGImageDecoder extends ImageDecoderImpl { |
|
|
|
public PNGImageDecoder(InputStream input, |
|
PNGDecodeParam param) { |
|
super(input, param); |
|
} |
|
|
|
public RenderedImage decodeAsRenderedImage(int page) throws IOException { |
|
if (page != 0) { |
|
throw new IOException(JaiI18N.getString("PNGImageDecoder19")); |
|
} |
|
return new PNGImage(input, (PNGDecodeParam)param); |
|
} |
|
} |
|
|
|
class PNGChunk { |
|
int length; |
|
int type; |
|
byte[] data; |
|
int crc; |
|
|
|
String typeString; |
|
|
|
public PNGChunk(int length, int type, byte[] data, int crc) { |
|
this.length = length; |
|
this.type = type; |
|
this.data = data; |
|
this.crc = crc; |
|
|
|
typeString = new String(); |
|
typeString += (char)(type >> 24); |
|
typeString += (char)((type >> 16) & 0xff); |
|
typeString += (char)((type >> 8) & 0xff); |
|
typeString += (char)(type & 0xff); |
|
} |
|
|
|
public int getLength() { |
|
return length; |
|
} |
|
|
|
public int getType() { |
|
return type; |
|
} |
|
|
|
public String getTypeString() { |
|
return typeString; |
|
} |
|
|
|
public byte[] getData() { |
|
return data; |
|
} |
|
|
|
public byte getByte(int offset) { |
|
return data[offset]; |
|
} |
|
|
|
public int getInt1(int offset) { |
|
return data[offset] & 0xff; |
|
} |
|
|
|
public int getInt2(int offset) { |
|
return ((data[offset] & 0xff) << 8) | |
|
(data[offset + 1] & 0xff); |
|
} |
|
|
|
public int getInt4(int offset) { |
|
return ((data[offset] & 0xff) << 24) | |
|
((data[offset + 1] & 0xff) << 16) | |
|
((data[offset + 2] & 0xff) << 8) | |
|
(data[offset + 3] & 0xff); |
|
} |
|
|
|
public String getString4(int offset) { |
|
String s = new String(); |
|
s += (char)data[offset]; |
|
s += (char)data[offset + 1]; |
|
s += (char)data[offset + 2]; |
|
s += (char)data[offset + 3]; |
|
return s; |
|
} |
|
|
|
public boolean isType(String typeName) { |
|
return typeString.equals(typeName); |
|
} |
|
} |
|
|
|
/** |
|
* TO DO: |
|
* |
|
* zTXt chunks |
|
* |
|
*/ |
|
class PNGImage extends SimpleRenderedImage { |
|
|
|
public static final int PNG_COLOR_GRAY = 0; |
|
public static final int PNG_COLOR_RGB = 2; |
|
public static final int PNG_COLOR_PALETTE = 3; |
|
public static final int PNG_COLOR_GRAY_ALPHA = 4; |
|
public static final int PNG_COLOR_RGB_ALPHA = 6; |
|
|
|
private static final String[] colorTypeNames = { |
|
"Grayscale", "Error", "Truecolor", "Index", |
|
"Grayscale with alpha", "Error", "Truecolor with alpha" |
|
}; |
|
|
|
public static final int PNG_FILTER_NONE = 0; |
|
public static final int PNG_FILTER_SUB = 1; |
|
public static final int PNG_FILTER_UP = 2; |
|
public static final int PNG_FILTER_AVERAGE = 3; |
|
public static final int PNG_FILTER_PAETH = 4; |
|
|
|
private static final int RED_OFFSET = 2; |
|
private static final int GREEN_OFFSET = 1; |
|
private static final int BLUE_OFFSET = 0; |
|
|
|
private int[][] bandOffsets = { |
|
null, |
|
{ 0 }, // G |
|
{ 0, 1 }, // GA in GA order |
|
{ 0, 1, 2 }, // RGB in RGB order |
|
{ 0, 1, 2, 3 } // RGBA in RGBA order |
|
}; |
|
|
|
private int bitDepth; |
|
private int colorType; |
|
|
|
private int compressionMethod; |
|
private int filterMethod; |
|
private int interlaceMethod; |
|
|
|
private int paletteEntries; |
|
private byte[] redPalette; |
|
private byte[] greenPalette; |
|
private byte[] bluePalette; |
|
private byte[] alphaPalette; |
|
|
|
private int bkgdRed; |
|
private int bkgdGreen; |
|
private int bkgdBlue; |
|
|
|
private int grayTransparentAlpha; |
|
private int redTransparentAlpha; |
|
private int greenTransparentAlpha; |
|
private int blueTransparentAlpha; |
|
|
|
private int maxOpacity; |
|
|
|
private int[] significantBits = null; |
|
|
|
private boolean hasBackground = false; |
|
|
|
// Parameter information |
|
|
|
// If true, the user wants destination alpha where applicable. |
|
private boolean suppressAlpha = false; |
|
|
|
// If true, perform palette lookup internally |
|
private boolean expandPalette = false; |
|
|
|
// If true, output < 8 bit gray images in 8 bit components format |
|
private boolean output8BitGray = false; |
|
|
|
// Create an alpha channel in the destination color model. |
|
private boolean outputHasAlphaPalette = false; |
|
|
|
// Perform gamma correction on the image |
|
private boolean performGammaCorrection = false; |
|
|
|
// Expand GA to GGGA for compatbility with Java2D |
|
private boolean expandGrayAlpha = false; |
|
|
|
// Produce an instance of PNGEncodeParam |
|
private boolean generateEncodeParam = false; |
|
|
|
// PNGDecodeParam controlling decode process |
|
private PNGDecodeParam decodeParam = null; |
|
|
|
// PNGEncodeParam to store file details in |
|
private PNGEncodeParam encodeParam = null; |
|
|
|
private boolean emitProperties = true; |
|
|
|
private float fileGamma = 45455/100000.0F; |
|
|
|
private float userExponent = 1.0F; |
|
|
|
private float displayExponent = 2.2F; |
|
|
|
private float[] chromaticity = null; |
|
|
|
private int sRGBRenderingIntent = -1; |
|
|
|
// Post-processing step implied by above parameters |
|
private int postProcess = POST_NONE; |
|
|
|
// Possible post-processing steps |
|
|
|
// Do nothing |
|
private static final int POST_NONE = 0; |
|
|
|
// Gamma correct only |
|
private static final int POST_GAMMA = 1; |
|
|
|
// Push gray values through grayLut to expand to 8 bits |
|
private static final int POST_GRAY_LUT = 2; |
|
|
|
// Push gray values through grayLut to expand to 8 bits, add alpha |
|
private static final int POST_GRAY_LUT_ADD_TRANS = 3; |
|
|
|
// Push palette value through R,G,B lookup tables |
|
private static final int POST_PALETTE_TO_RGB = 4; |
|
|
|
// Push palette value through R,G,B,A lookup tables |
|
private static final int POST_PALETTE_TO_RGBA = 5; |
|
|
|
// Add transparency to a given gray value (w/ optional gamma) |
|
private static final int POST_ADD_GRAY_TRANS = 6; |
|
|
|
// Add transparency to a given RGB value (w/ optional gamma) |
|
private static final int POST_ADD_RGB_TRANS = 7; |
|
|
|
// Remove the alpha channel from a gray image (w/ optional gamma) |
|
private static final int POST_REMOVE_GRAY_TRANS = 8; |
|
|
|
// Remove the alpha channel from an RGB image (w/optional gamma) |
|
private static final int POST_REMOVE_RGB_TRANS = 9; |
|
|
|
// Mask to add expansion of GA -> GGGA |
|
private static final int POST_EXP_MASK = 16; |
|
|
|
// Expand gray to G/G/G |
|
private static final int POST_GRAY_ALPHA_EXP = |
|
POST_NONE | POST_EXP_MASK; |
|
|
|
// Expand gray to G/G/G through a gamma lut |
|
private static final int POST_GAMMA_EXP = |
|
POST_GAMMA | POST_EXP_MASK; |
|
|
|
// Push gray values through grayLut to expand to 8 bits, expand, add alpha |
|
private static final int POST_GRAY_LUT_ADD_TRANS_EXP = |
|
POST_GRAY_LUT_ADD_TRANS | POST_EXP_MASK; |
|
|
|
// Add transparency to a given gray value, expand |
|
private static final int POST_ADD_GRAY_TRANS_EXP = |
|
POST_ADD_GRAY_TRANS | POST_EXP_MASK; |
|
|
|
private Vector streamVec = new Vector(); |
|
private DataInputStream dataStream; |
|
|
|
private int bytesPerPixel; // number of bytes per input pixel |
|
private int inputBands; |
|
private int outputBands; |
|
|
|
// Number of private chunks |
|
private int chunkIndex = 0; |
|
|
|
private Vector textKeys = new Vector(); |
|
private Vector textStrings = new Vector(); |
|
|
|
private Vector ztextKeys = new Vector(); |
|
private Vector ztextStrings = new Vector(); |
|
|
|
private WritableRaster theTile; |
|
|
|
private int[] gammaLut = null; |
|
|
|
private void initGammaLut(int bits) { |
|
double exp = (double)userExponent/(fileGamma*displayExponent); |
|
int numSamples = 1 << bits; |
|
int maxOutSample = (bits == 16) ? 65535 : 255; |
|
|
|
gammaLut = new int[numSamples]; |
|
for (int i = 0; i < numSamples; i++) { |
|
double gbright = (double)i/(numSamples - 1); |
|
double gamma = Math.pow(gbright, exp); |
|
int igamma = (int)(gamma*maxOutSample + 0.5); |
|
if (igamma > maxOutSample) { |
|
igamma = maxOutSample; |
|
} |
|
gammaLut[i] = igamma; |
|
} |
|
} |
|
|
|
private final byte[][] expandBits = { |
|
null, |
|
{ (byte)0x00, (byte)0xff }, |
|
{ (byte)0x00, (byte)0x55, (byte)0xaa, (byte)0xff }, |
|
null, |
|
{ (byte)0x00, (byte)0x11, (byte)0x22, (byte)0x33, |
|
(byte)0x44, (byte)0x55, (byte)0x66, (byte)0x77, |
|
(byte)0x88, (byte)0x99, (byte)0xaa, (byte)0xbb, |
|
(byte)0xcc, (byte)0xdd, (byte)0xee, (byte)0xff } |
|
}; |
|
|
|
private int[] grayLut = null; |
|
|
|
private void initGrayLut(int bits) { |
|
int len = 1 << bits; |
|
grayLut = new int[len]; |
|
|
|
if (performGammaCorrection) { |
|
for (int i = 0; i < len; i++) { |
|
grayLut[i] = gammaLut[i]; |
|
} |
|
} else { |
|
for (int i = 0; i < len; i++) { |
|
grayLut[i] = expandBits[bits][i]; |
|
} |
|
} |
|
} |
|
|
|
public PNGImage(InputStream stream, PNGDecodeParam decodeParam) |
|
throws IOException { |
|
|
|
if (!stream.markSupported()) { |
|
stream = new BufferedInputStream(stream); |
|
} |
|
DataInputStream distream = new DataInputStream(stream); |
|
|
|
if (decodeParam == null) { |
|
decodeParam = new PNGDecodeParam(); |
|
} |
|
this.decodeParam = decodeParam; |
|
|
|
// Get parameter values |
|
this.suppressAlpha = decodeParam.getSuppressAlpha(); |
|
this.expandPalette = decodeParam.getExpandPalette(); |
|
this.output8BitGray = decodeParam.getOutput8BitGray(); |
|
this.expandGrayAlpha = decodeParam.getExpandGrayAlpha(); |
|
if (decodeParam.getPerformGammaCorrection()) { |
|
this.userExponent = decodeParam.getUserExponent(); |
|
this.displayExponent = decodeParam.getDisplayExponent(); |
|
performGammaCorrection = true; |
|
output8BitGray = true; |
|
} |
|
this.generateEncodeParam = decodeParam.getGenerateEncodeParam(); |
|
|
|
if (emitProperties) { |
|
properties.put("file_type", "PNG v. 1.0"); |
|
} |
|
|
|
try { |
|
long magic = distream.readLong(); |
|
if (magic != 0x89504e470d0a1a0aL) { |
|
String msg = JaiI18N.getString("PNGImageDecoder0"); |
|
throw new RuntimeException(msg); |
|
} |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
String msg = JaiI18N.getString("PNGImageDecoder1"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
do { |
|
try { |
|
PNGChunk chunk; |
|
|
|
String chunkType = getChunkType(distream); |
|
if (chunkType.equals("IHDR")) { |
|
chunk = readChunk(distream); |
|
parse_IHDR_chunk(chunk); |
|
} else if (chunkType.equals("PLTE")) { |
|
chunk = readChunk(distream); |
|
parse_PLTE_chunk(chunk); |
|
} else if (chunkType.equals("IDAT")) { |
|
chunk = readChunk(distream); |
|
streamVec.add(new ByteArrayInputStream(chunk.getData())); |
|
} else if (chunkType.equals("IEND")) { |
|
chunk = readChunk(distream); |
|
parse_IEND_chunk(chunk); |
|
break; // fall through to the bottom |
|
} else if (chunkType.equals("bKGD")) { |
|
chunk = readChunk(distream); |
|
parse_bKGD_chunk(chunk); |
|
} else if (chunkType.equals("cHRM")) { |
|
chunk = readChunk(distream); |
|
parse_cHRM_chunk(chunk); |
|
} else if (chunkType.equals("gAMA")) { |
|
chunk = readChunk(distream); |
|
parse_gAMA_chunk(chunk); |
|
} else if (chunkType.equals("hIST")) { |
|
chunk = readChunk(distream); |
|
parse_hIST_chunk(chunk); |
|
} else if (chunkType.equals("iCCP")) { |
|
chunk = readChunk(distream); |
|
parse_iCCP_chunk(chunk); |
|
} else if (chunkType.equals("pHYs")) { |
|
chunk = readChunk(distream); |
|
parse_pHYs_chunk(chunk); |
|
} else if (chunkType.equals("sBIT")) { |
|
chunk = readChunk(distream); |
|
parse_sBIT_chunk(chunk); |
|
} else if (chunkType.equals("sRGB")) { |
|
chunk = readChunk(distream); |
|
parse_sRGB_chunk(chunk); |
|
} else if (chunkType.equals("tEXt")) { |
|
chunk = readChunk(distream); |
|
parse_tEXt_chunk(chunk); |
|
} else if (chunkType.equals("tIME")) { |
|
chunk = readChunk(distream); |
|
parse_tIME_chunk(chunk); |
|
} else if (chunkType.equals("tRNS")) { |
|
chunk = readChunk(distream); |
|
parse_tRNS_chunk(chunk); |
|
} else if (chunkType.equals("zTXt")) { |
|
chunk = readChunk(distream); |
|
parse_zTXt_chunk(chunk); |
|
} else { |
|
chunk = readChunk(distream); |
|
// Output the chunk data in raw form |
|
|
|
String type = chunk.getTypeString(); |
|
byte[] data = chunk.getData(); |
|
if (encodeParam != null) { |
|
encodeParam.addPrivateChunk(type, data); |
|
} |
|
if (emitProperties) { |
|
String key = "chunk_" + chunkIndex++ + ":" + type; |
|
properties.put(key.toLowerCase(), data); |
|
} |
|
} |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
String msg = JaiI18N.getString("PNGImageDecoder2"); |
|
throw new RuntimeException(msg); |
|
} |
|
} while (true); |
|
|
|
// Final post-processing |
|
|
|
if (significantBits == null) { |
|
significantBits = new int[inputBands]; |
|
for (int i = 0; i < inputBands; i++) { |
|
significantBits[i] = bitDepth; |
|
} |
|
|
|
if (emitProperties) { |
|
properties.put("significant_bits", significantBits); |
|
} |
|
} |
|
} |
|
|
|
private static String getChunkType(DataInputStream distream) { |
|
try { |
|
distream.mark(8); |
|
int length = distream.readInt(); |
|
int type = distream.readInt(); |
|
distream.reset(); |
|
|
|
String typeString = new String(); |
|
typeString += (char)(type >> 24); |
|
typeString += (char)((type >> 16) & 0xff); |
|
typeString += (char)((type >> 8) & 0xff); |
|
typeString += (char)(type & 0xff); |
|
return typeString; |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
return null; |
|
} |
|
} |
|
|
|
private static PNGChunk readChunk(DataInputStream distream) { |
|
try { |
|
int length = distream.readInt(); |
|
int type = distream.readInt(); |
|
byte[] data = new byte[length]; |
|
distream.readFully(data); |
|
int crc = distream.readInt(); |
|
|
|
return new PNGChunk(length, type, data, crc); |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
return null; |
|
} |
|
} |
|
|
|
private void parse_IHDR_chunk(PNGChunk chunk) { |
|
tileWidth = width = chunk.getInt4(0); |
|
tileHeight = height = chunk.getInt4(4); |
|
|
|
bitDepth = chunk.getInt1(8); |
|
|
|
if ((bitDepth != 1) && (bitDepth != 2) && (bitDepth != 4) && |
|
(bitDepth != 8) && (bitDepth != 16)) { |
|
// Error -- bad bit depth |
|
String msg = JaiI18N.getString("PNGImageDecoder3"); |
|
throw new RuntimeException(msg); |
|
} |
|
maxOpacity = (1 << bitDepth) - 1; |
|
|
|
colorType = chunk.getInt1(9); |
|
if ((colorType != PNG_COLOR_GRAY) && |
|
(colorType != PNG_COLOR_RGB) && |
|
(colorType != PNG_COLOR_PALETTE) && |
|
(colorType != PNG_COLOR_GRAY_ALPHA) && |
|
(colorType != PNG_COLOR_RGB_ALPHA)) { |
|
System.out.println(JaiI18N.getString("PNGImageDecoder4")); |
|
} |
|
|
|
if ((colorType == PNG_COLOR_RGB) && (bitDepth < 8)) { |
|
// Error -- RGB images must have 8 or 16 bits |
|
String msg = JaiI18N.getString("PNGImageDecoder5"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
if ((colorType == PNG_COLOR_PALETTE) && (bitDepth == 16)) { |
|
// Error -- palette images must have < 16 bits |
|
String msg = JaiI18N.getString("PNGImageDecoder6"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
if ((colorType == PNG_COLOR_GRAY_ALPHA) && (bitDepth < 8)) { |
|
// Error -- gray/alpha images must have >= 8 bits |
|
String msg = JaiI18N.getString("PNGImageDecoder7"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
if ((colorType == PNG_COLOR_RGB_ALPHA) && (bitDepth < 8)) { |
|
// Error -- RGB/alpha images must have >= 8 bits |
|
String msg = JaiI18N.getString("PNGImageDecoder8"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
if (emitProperties) { |
|
properties.put("color_type", colorTypeNames[colorType]); |
|
} |
|
|
|
if (generateEncodeParam) { |
|
if (colorType == PNG_COLOR_PALETTE) { |
|
encodeParam = new PNGEncodeParam.Palette(); |
|
} else if (colorType == PNG_COLOR_GRAY || |
|
colorType == PNG_COLOR_GRAY_ALPHA) { |
|
encodeParam = new PNGEncodeParam.Gray(); |
|
} else { |
|
encodeParam = new PNGEncodeParam.RGB(); |
|
} |
|
decodeParam.setEncodeParam(encodeParam); |
|
} |
|
|
|
if (encodeParam != null) { |
|
encodeParam.setBitDepth(bitDepth); |
|
} |
|
if (emitProperties) { |
|
properties.put("bit_depth", new Integer(bitDepth)); |
|
} |
|
|
|
if (performGammaCorrection) { |
|
// Assume file gamma is 1/2.2 unless we get a gAMA chunk |
|
float gamma = (1.0F/2.2F)*(displayExponent/userExponent); |
|
if (encodeParam != null) { |
|
encodeParam.setGamma(gamma); |
|
} |
|
if (emitProperties) { |
|
properties.put("gamma", new Float(gamma)); |
|
} |
|
} |
|
|
|
compressionMethod = chunk.getInt1(10); |
|
if (compressionMethod != 0) { |
|
// Error -- only know about compression method 0 |
|
String msg = JaiI18N.getString("PNGImageDecoder9"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
filterMethod = chunk.getInt1(11); |
|
if (filterMethod != 0) { |
|
// Error -- only know about filter method 0 |
|
String msg = JaiI18N.getString("PNGImageDecoder10"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
interlaceMethod = chunk.getInt1(12); |
|
if (interlaceMethod == 0) { |
|
if (encodeParam != null) { |
|
encodeParam.setInterlacing(false); |
|
} |
|
if (emitProperties) { |
|
properties.put("interlace_method", "None"); |
|
} |
|
} else if (interlaceMethod == 1) { |
|
if (encodeParam != null) { |
|
encodeParam.setInterlacing(true); |
|
} |
|
if (emitProperties) { |
|
properties.put("interlace_method", "Adam7"); |
|
} |
|
} else { |
|
// Error -- only know about Adam7 interlacing |
|
String msg = JaiI18N.getString("PNGImageDecoder11"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
bytesPerPixel = (bitDepth == 16) ? 2 : 1; |
|
|
|
switch (colorType) { |
|
case PNG_COLOR_GRAY: |
|
inputBands = 1; |
|
outputBands = 1; |
|
|
|
if (output8BitGray && (bitDepth < 8)) { |
|
postProcess = POST_GRAY_LUT; |
|
} else if (performGammaCorrection) { |
|
postProcess = POST_GAMMA; |
|
} else { |
|
postProcess = POST_NONE; |
|
} |
|
break; |
|
|
|
case PNG_COLOR_RGB: |
|
inputBands = 3; |
|
bytesPerPixel *= 3; |
|
outputBands = 3; |
|
|
|
if (performGammaCorrection) { |
|
postProcess = POST_GAMMA; |
|
} else { |
|
postProcess = POST_NONE; |
|
} |
|
break; |
|
|
|
case PNG_COLOR_PALETTE: |
|
inputBands = 1; |
|
bytesPerPixel = 1; |
|
outputBands = expandPalette ? 3 : 1; |
|
|
|
if (expandPalette) { |
|
postProcess = POST_PALETTE_TO_RGB; |
|
} else { |
|
postProcess = POST_NONE; |
|
} |
|
break; |
|
|
|
case PNG_COLOR_GRAY_ALPHA: |
|
inputBands = 2; |
|
bytesPerPixel *= 2; |
|
|
|
if (suppressAlpha) { |
|
outputBands = 1; |
|
postProcess = POST_REMOVE_GRAY_TRANS; |
|
} else { |
|
if (performGammaCorrection) { |
|
postProcess = POST_GAMMA; |
|
} else { |
|
postProcess = POST_NONE; |
|
} |
|
if (expandGrayAlpha) { |
|
postProcess |= POST_EXP_MASK; |
|
outputBands = 4; |
|
} else { |
|
outputBands = 2; |
|
} |
|
} |
|
break; |
|
|
|
case PNG_COLOR_RGB_ALPHA: |
|
inputBands = 4; |
|
bytesPerPixel *= 4; |
|
outputBands = (!suppressAlpha) ? 4 : 3; |
|
|
|
if (suppressAlpha) { |
|
postProcess = POST_REMOVE_RGB_TRANS; |
|
} else if (performGammaCorrection) { |
|
postProcess = POST_GAMMA; |
|
} else { |
|
postProcess = POST_NONE; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
private void parse_IEND_chunk(PNGChunk chunk) throws Exception { |
|
// Store text strings |
|
int textLen = textKeys.size(); |
|
String[] textArray = new String[2*textLen]; |
|
for (int i = 0; i < textLen; i++) { |
|
String key = (String)textKeys.elementAt(i); |
|
String val = (String)textStrings.elementAt(i); |
|
textArray[2*i] = key; |
|
textArray[2*i + 1] = val; |
|
if (emitProperties) { |
|
String uniqueKey = "text_" + i + ":" + key; |
|
properties.put(uniqueKey.toLowerCase(), val); |
|
} |
|
} |
|
if (encodeParam != null) { |
|
encodeParam.setText(textArray); |
|
} |
|
|
|
// Store compressed text strings |
|
int ztextLen = ztextKeys.size(); |
|
String[] ztextArray = new String[2*ztextLen]; |
|
for (int i = 0; i < ztextLen; i++) { |
|
String key = (String)ztextKeys.elementAt(i); |
|
String val = (String)ztextStrings.elementAt(i); |
|
ztextArray[2*i] = key; |
|
ztextArray[2*i + 1] = val; |
|
if (emitProperties) { |
|
String uniqueKey = "ztext_" + i + ":" + key; |
|
properties.put(uniqueKey.toLowerCase(), val); |
|
} |
|
} |
|
if (encodeParam != null) { |
|
encodeParam.setCompressedText(ztextArray); |
|
} |
|
|
|
// Parse prior IDAT chunks |
|
InputStream seqStream = |
|
new SequenceInputStream(streamVec.elements()); |
|
InputStream infStream = |
|
new InflaterInputStream(seqStream, new Inflater()); |
|
dataStream = new DataInputStream(infStream); |
|
|
|
// Create an empty WritableRaster |
|
int depth = bitDepth; |
|
if ((colorType == PNG_COLOR_GRAY) && |
|
(bitDepth < 8) && output8BitGray) { |
|
depth = 8; |
|
} |
|
if ((colorType == PNG_COLOR_PALETTE) && expandPalette) { |
|
depth = 8; |
|
} |
|
int bytesPerRow = (outputBands*width*depth + 7)/8; |
|
int scanlineStride = |
|
(depth == 16) ? (bytesPerRow/2) : bytesPerRow; |
|
|
|
theTile = createRaster(width, height, outputBands, |
|
scanlineStride, |
|
depth); |
|
|
|
if (performGammaCorrection && (gammaLut == null)) { |
|
initGammaLut(bitDepth); |
|
} |
|
if ((postProcess == POST_GRAY_LUT) || |
|
(postProcess == POST_GRAY_LUT_ADD_TRANS) || |
|
(postProcess == POST_GRAY_LUT_ADD_TRANS_EXP)) { |
|
initGrayLut(bitDepth); |
|
} |
|
|
|
decodeImage(interlaceMethod == 1); |
|
sampleModel = theTile.getSampleModel(); |
|
|
|
if ((colorType == PNG_COLOR_PALETTE) && !expandPalette) { |
|
if (outputHasAlphaPalette) { |
|
colorModel = new IndexColorModel(bitDepth, |
|
paletteEntries, |
|
redPalette, |
|
greenPalette, |
|
bluePalette, |
|
alphaPalette); |
|
} else { |
|
colorModel = new IndexColorModel(bitDepth, |
|
paletteEntries, |
|
redPalette, |
|
greenPalette, |
|
bluePalette); |
|
} |
|
} else if ((colorType == PNG_COLOR_GRAY) && |
|
(bitDepth < 8) && !output8BitGray) { |
|
byte[] palette = expandBits[bitDepth]; |
|
colorModel = new IndexColorModel(bitDepth, |
|
palette.length, |
|
palette, |
|
palette, |
|
palette); |
|
} else { |
|
colorModel = |
|
ImageCodec.createComponentColorModel(sampleModel); |
|
} |
|
} |
|
|
|
private void parse_PLTE_chunk(PNGChunk chunk) { |
|
paletteEntries = chunk.getLength()/3; |
|
redPalette = new byte[paletteEntries]; |
|
greenPalette = new byte[paletteEntries]; |
|
bluePalette = new byte[paletteEntries]; |
|
|
|
int pltIndex = 0; |
|
|
|
// gAMA chunk must precede PLTE chunk |
|
if (performGammaCorrection) { |
|
if (gammaLut == null) { |
|
initGammaLut(bitDepth == 16 ? 16 : 8); |
|
} |
|
|
|
for (int i = 0; i < paletteEntries; i++) { |
|
byte r = chunk.getByte(pltIndex++); |
|
byte g = chunk.getByte(pltIndex++); |
|
byte b = chunk.getByte(pltIndex++); |
|
|
|
redPalette[i] = (byte)gammaLut[r & 0xff]; |
|
greenPalette[i] = (byte)gammaLut[g & 0xff]; |
|
bluePalette[i] = (byte)gammaLut[b & 0xff]; |
|
} |
|
} else { |
|
for (int i = 0; i < paletteEntries; i++) { |
|
redPalette[i] = chunk.getByte(pltIndex++); |
|
greenPalette[i] = chunk.getByte(pltIndex++); |
|
bluePalette[i] = chunk.getByte(pltIndex++); |
|
} |
|
} |
|
} |
|
|
|
private void parse_bKGD_chunk(PNGChunk chunk) { |
|
hasBackground = true; |
|
|
|
switch (colorType) { |
|
case PNG_COLOR_PALETTE: |
|
int bkgdIndex = chunk.getByte(0) & 0xff; |
|
|
|
bkgdRed = redPalette[bkgdIndex] & 0xff; |
|
bkgdGreen = greenPalette[bkgdIndex] & 0xff; |
|
bkgdBlue = bluePalette[bkgdIndex] & 0xff; |
|
|
|
if (encodeParam != null) { |
|
((PNGEncodeParam.Palette)encodeParam). |
|
setBackgroundPaletteIndex(bkgdIndex); |
|
} |
|
break; |
|
case PNG_COLOR_GRAY: case PNG_COLOR_GRAY_ALPHA: |
|
int bkgdGray = chunk.getInt2(0); |
|
bkgdRed = bkgdGreen = bkgdBlue = bkgdGray; |
|
|
|
if (encodeParam != null) { |
|
((PNGEncodeParam.Gray)encodeParam). |
|
setBackgroundGray(bkgdGray); |
|
} |
|
break; |
|
case PNG_COLOR_RGB: case PNG_COLOR_RGB_ALPHA: |
|
bkgdRed = chunk.getInt2(0); |
|
bkgdGreen = chunk.getInt2(2); |
|
bkgdBlue = chunk.getInt2(4); |
|
|
|
int[] bkgdRGB = new int[3]; |
|
bkgdRGB[0] = bkgdRed; |
|
bkgdRGB[1] = bkgdGreen; |
|
bkgdRGB[2] = bkgdBlue; |
|
if (encodeParam != null) { |
|
((PNGEncodeParam.RGB)encodeParam). |
|
setBackgroundRGB(bkgdRGB); |
|
} |
|
break; |
|
} |
|
|
|
int r = 0, g = 0, b = 0; |
|
if (bitDepth < 8) { |
|
r = expandBits[bitDepth][bkgdRed]; |
|
g = expandBits[bitDepth][bkgdGreen]; |
|
b = expandBits[bitDepth][bkgdBlue]; |
|
} else if (bitDepth == 8) { |
|
r = bkgdRed; |
|
g = bkgdGreen; |
|
b = bkgdBlue; |
|
} else if (bitDepth == 16) { |
|
r = bkgdRed >> 8; |
|
g = bkgdGreen >> 8; |
|
b = bkgdBlue >> 8; |
|
} |
|
if (emitProperties) { |
|
properties.put("background_color", new Color(r, g, b)); |
|
} |
|
} |
|
|
|
private void parse_cHRM_chunk(PNGChunk chunk) { |
|
// If an sRGB chunk exists, ignore cHRM chunks |
|
if (sRGBRenderingIntent != -1) { |
|
return; |
|
} |
|
|
|
chromaticity = new float[8]; |
|
chromaticity[0] = chunk.getInt4(0)/100000.0F; |
|
chromaticity[1] = chunk.getInt4(4)/100000.0F; |
|
chromaticity[2] = chunk.getInt4(8)/100000.0F; |
|
chromaticity[3] = chunk.getInt4(12)/100000.0F; |
|
chromaticity[4] = chunk.getInt4(16)/100000.0F; |
|
chromaticity[5] = chunk.getInt4(20)/100000.0F; |
|
chromaticity[6] = chunk.getInt4(24)/100000.0F; |
|
chromaticity[7] = chunk.getInt4(28)/100000.0F; |
|
|
|
if (encodeParam != null) { |
|
encodeParam.setChromaticity(chromaticity); |
|
} |
|
if (emitProperties) { |
|
properties.put("white_point_x", new Float(chromaticity[0])); |
|
properties.put("white_point_y", new Float(chromaticity[1])); |
|
properties.put("red_x", new Float(chromaticity[2])); |
|
properties.put("red_y", new Float(chromaticity[3])); |
|
properties.put("green_x", new Float(chromaticity[4])); |
|
properties.put("green_y", new Float(chromaticity[5])); |
|
properties.put("blue_x", new Float(chromaticity[6])); |
|
properties.put("blue_y", new Float(chromaticity[7])); |
|
} |
|
} |
|
|
|
private void parse_gAMA_chunk(PNGChunk chunk) { |
|
// If an sRGB chunk exists, ignore gAMA chunks |
|
if (sRGBRenderingIntent != -1) { |
|
return; |
|
} |
|
|
|
fileGamma = chunk.getInt4(0)/100000.0F; |
|
|
|
float exp = |
|
performGammaCorrection ? displayExponent/userExponent : 1.0F; |
|
if (encodeParam != null) { |
|
encodeParam.setGamma(fileGamma*exp); |
|
} |
|
if (emitProperties) { |
|
properties.put("gamma", new Float(fileGamma*exp)); |
|
} |
|
} |
|
|
|
private void parse_hIST_chunk(PNGChunk chunk) { |
|
if (redPalette == null) { |
|
String msg = JaiI18N.getString("PNGImageDecoder18"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
int length = redPalette.length; |
|
int[] hist = new int[length]; |
|
for (int i = 0; i < length; i++) { |
|
hist[i] = chunk.getInt2(2*i); |
|
} |
|
|
|
if (encodeParam != null) { |
|
encodeParam.setPaletteHistogram(hist); |
|
} |
|
} |
|
|
|
private void parse_iCCP_chunk(PNGChunk chunk) { |
|
String name = new String(); |
|
byte b; |
|
|
|
int textIndex = 0; |
|
while ((b = chunk.getByte(textIndex++)) != 0) { |
|
name += (char)b; |
|
} |
|
} |
|
|
|
private void parse_pHYs_chunk(PNGChunk chunk) { |
|
int xPixelsPerUnit = chunk.getInt4(0); |
|
int yPixelsPerUnit = chunk.getInt4(4); |
|
int unitSpecifier = chunk.getInt1(8); |
|
|
|
if (encodeParam != null) { |
|
encodeParam.setPhysicalDimension(xPixelsPerUnit, |
|
yPixelsPerUnit, |
|
unitSpecifier); |
|
} |
|
if (emitProperties) { |
|
properties.put("x_pixels_per_unit", new Integer(xPixelsPerUnit)); |
|
properties.put("y_pixels_per_unit", new Integer(yPixelsPerUnit)); |
|
properties.put("pixel_aspect_ratio", |
|
new Float((float)xPixelsPerUnit/yPixelsPerUnit)); |
|
if (unitSpecifier == 1) { |
|
properties.put("pixel_units", "Meters"); |
|
} else if (unitSpecifier != 0) { |
|
// Error -- unit specifier must be 0 or 1 |
|
String msg = JaiI18N.getString("PNGImageDecoder12"); |
|
throw new RuntimeException(msg); |
|
} |
|
} |
|
} |
|
|
|
private void parse_sBIT_chunk(PNGChunk chunk) { |
|
if (colorType == PNG_COLOR_PALETTE) { |
|
significantBits = new int[3]; |
|
} else { |
|
significantBits = new int[inputBands]; |
|
} |
|
for (int i = 0; i < significantBits.length; i++) { |
|
int bits = (int)chunk.getByte(i); |
|
int depth = (colorType == PNG_COLOR_PALETTE) ? 8 : bitDepth; |
|
if (bits <= 0 || bits > depth) { |
|
// Error -- significant bits must be between 0 and |
|
// image bit depth. |
|
String msg = JaiI18N.getString("PNGImageDecoder13"); |
|
throw new RuntimeException(msg); |
|
} |
|
significantBits[i] = bits; |
|
} |
|
|
|
if (encodeParam != null) { |
|
encodeParam.setSignificantBits(significantBits); |
|
} |
|
if (emitProperties) { |
|
properties.put("significant_bits", significantBits); |
|
} |
|
} |
|
|
|
private void parse_sRGB_chunk(PNGChunk chunk) { |
|
sRGBRenderingIntent = chunk.getByte(0); |
|
|
|
// The presence of an sRGB chunk implies particular |
|
// settings for gamma and chroma. |
|
fileGamma = 45455/100000.0F; |
|
|
|
chromaticity = new float[8]; |
|
chromaticity[0] = 31270/10000.0F; |
|
chromaticity[1] = 32900/10000.0F; |
|
chromaticity[2] = 64000/10000.0F; |
|
chromaticity[3] = 33000/10000.0F; |
|
chromaticity[4] = 30000/10000.0F; |
|
chromaticity[5] = 60000/10000.0F; |
|
chromaticity[6] = 15000/10000.0F; |
|
chromaticity[7] = 6000/10000.0F; |
|
|
|
if (performGammaCorrection) { |
|
// File gamma is 1/2.2 |
|
float gamma = fileGamma*(displayExponent/userExponent); |
|
if (encodeParam != null) { |
|
encodeParam.setGamma(gamma); |
|
encodeParam.setChromaticity(chromaticity); |
|
} |
|
if (emitProperties) { |
|
properties.put("gamma", new Float(gamma)); |
|
properties.put("white_point_x", new Float(chromaticity[0])); |
|
properties.put("white_point_y", new Float(chromaticity[1])); |
|
properties.put("red_x", new Float(chromaticity[2])); |
|
properties.put("red_y", new Float(chromaticity[3])); |
|
properties.put("green_x", new Float(chromaticity[4])); |
|
properties.put("green_y", new Float(chromaticity[5])); |
|
properties.put("blue_x", new Float(chromaticity[6])); |
|
properties.put("blue_y", new Float(chromaticity[7])); |
|
} |
|
} |
|
} |
|
|
|
private void parse_tEXt_chunk(PNGChunk chunk) { |
|
String key = new String(); |
|
String value = new String(); |
|
byte b; |
|
|
|
int textIndex = 0; |
|
while ((b = chunk.getByte(textIndex++)) != 0) { |
|
key += (char)b; |
|
} |
|
|
|
for (int i = textIndex; i < chunk.getLength(); i++) { |
|
value += (char)chunk.getByte(i); |
|
} |
|
|
|
textKeys.add(key); |
|
textStrings.add(value); |
|
} |
|
|
|
private void parse_tIME_chunk(PNGChunk chunk) { |
|
int year = chunk.getInt2(0); |
|
int month = chunk.getInt1(2) - 1; |
|
int day = chunk.getInt1(3); |
|
int hour = chunk.getInt1(4); |
|
int minute = chunk.getInt1(5); |
|
int second = chunk.getInt1(6); |
|
|
|
TimeZone gmt = TimeZone.getTimeZone("GMT"); |
|
|
|
GregorianCalendar cal = new GregorianCalendar(gmt); |
|
cal.set(year, month, day, |
|
hour, minute, second); |
|
Date date = cal.getTime(); |
|
|
|
if (encodeParam != null) { |
|
encodeParam.setModificationTime(date); |
|
} |
|
if (emitProperties) { |
|
properties.put("timestamp", date); |
|
} |
|
} |
|
|
|
private void parse_tRNS_chunk(PNGChunk chunk) { |
|
if (colorType == PNG_COLOR_PALETTE) { |
|
int entries = chunk.getLength(); |
|
if (entries > paletteEntries) { |
|
// Error -- mustn't have more alpha than RGB palette entries |
|
String msg = JaiI18N.getString("PNGImageDecoder14"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
// Load beginning of palette from the chunk |
|
alphaPalette = new byte[paletteEntries]; |
|
for (int i = 0; i < entries; i++) { |
|
alphaPalette[i] = chunk.getByte(i); |
|
} |
|
|
|
// Fill rest of palette with 255 |
|
for (int i = entries; i < paletteEntries; i++) { |
|
alphaPalette[i] = (byte)255; |
|
} |
|
|
|
if (!suppressAlpha) { |
|
if (expandPalette) { |
|
postProcess = POST_PALETTE_TO_RGBA; |
|
outputBands = 4; |
|
} else { |
|
outputHasAlphaPalette = true; |
|
} |
|
} |
|
} else if (colorType == PNG_COLOR_GRAY) { |
|
grayTransparentAlpha = chunk.getInt2(0); |
|
|
|
if (!suppressAlpha) { |
|
if (bitDepth < 8) { |
|
output8BitGray = true; |
|
maxOpacity = 255; |
|
postProcess = POST_GRAY_LUT_ADD_TRANS; |
|
} else { |
|
postProcess = POST_ADD_GRAY_TRANS; |
|
} |
|
|
|
if (expandGrayAlpha) { |
|
outputBands = 4; |
|
postProcess |= POST_EXP_MASK; |
|
} else { |
|
outputBands = 2; |
|
} |
|
|
|
if (encodeParam != null) { |
|
((PNGEncodeParam.Gray)encodeParam). |
|
setTransparentGray(grayTransparentAlpha); |
|
} |
|
} |
|
} else if (colorType == PNG_COLOR_RGB) { |
|
redTransparentAlpha = chunk.getInt2(0); |
|
greenTransparentAlpha = chunk.getInt2(2); |
|
blueTransparentAlpha = chunk.getInt2(4); |
|
|
|
if (!suppressAlpha) { |
|
outputBands = 4; |
|
postProcess = POST_ADD_RGB_TRANS; |
|
|
|
if (encodeParam != null) { |
|
int[] rgbTrans = new int[3]; |
|
rgbTrans[0] = redTransparentAlpha; |
|
rgbTrans[1] = greenTransparentAlpha; |
|
rgbTrans[2] = blueTransparentAlpha; |
|
((PNGEncodeParam.RGB)encodeParam). |
|
setTransparentRGB(rgbTrans); |
|
} |
|
} |
|
} else if (colorType == PNG_COLOR_GRAY_ALPHA || |
|
colorType == PNG_COLOR_RGB_ALPHA) { |
|
// Error -- GA or RGBA image can't have a tRNS chunk. |
|
String msg = JaiI18N.getString("PNGImageDecoder15"); |
|
throw new RuntimeException(msg); |
|
} |
|
} |
|
|
|
private void parse_zTXt_chunk(PNGChunk chunk) { |
|
String key = new String(); |
|
String value = new String(); |
|
byte b; |
|
|
|
int textIndex = 0; |
|
while ((b = chunk.getByte(textIndex++)) != 0) { |
|
key += (char)b; |
|
} |
|
int method = chunk.getByte(textIndex++); |
|
|
|
try { |
|
int length = chunk.getLength() - textIndex; |
|
byte[] data = chunk.getData(); |
|
InputStream cis = |
|
new ByteArrayInputStream(data, textIndex, length); |
|
InputStream iis = new InflaterInputStream(cis); |
|
|
|
int c; |
|
while ((c = iis.read()) != -1) { |
|
value += (char)c; |
|
} |
|
|
|
ztextKeys.add(key); |
|
ztextStrings.add(value); |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
private WritableRaster createRaster(int width, int height, int bands, |
|
int scanlineStride, |
|
int bitDepth) { |
|
|
|
DataBuffer dataBuffer; |
|
WritableRaster ras = null; |
|
Point origin = new Point(0, 0); |
|
if ((bitDepth < 8) && (bands == 1)) { |
|
dataBuffer = new DataBufferByte(height*scanlineStride); |
|
ras = Raster.createPackedRaster(dataBuffer, |
|
width, height, |
|
bitDepth, |
|
origin); |
|
} else if (bitDepth <= 8) { |
|
dataBuffer = new DataBufferByte(height*scanlineStride); |
|
ras = Raster.createInterleavedRaster(dataBuffer, |
|
width, height, |
|
scanlineStride, |
|
bands, |
|
bandOffsets[bands], |
|
origin); |
|
} else { |
|
dataBuffer = new DataBufferUShort(height*scanlineStride); |
|
ras = Raster.createInterleavedRaster(dataBuffer, |
|
width, height, |
|
scanlineStride, |
|
bands, |
|
bandOffsets[bands], |
|
origin); |
|
} |
|
|
|
return ras; |
|
} |
|
|
|
// Data filtering methods |
|
|
|
private static void decodeSubFilter(byte[] curr, int count, int bpp) { |
|
for (int i = bpp; i < count; i++) { |
|
int val; |
|
|
|
val = curr[i] & 0xff; |
|
val += curr[i - bpp] & 0xff; |
|
|
|
curr[i] = (byte)val; |
|
} |
|
} |
|
|
|
private static void decodeUpFilter(byte[] curr, byte[] prev, |
|
int count) { |
|
for (int i = 0; i < count; i++) { |
|
int raw = curr[i] & 0xff; |
|
int prior = prev[i] & 0xff; |
|
|
|
curr[i] = (byte)(raw + prior); |
|
} |
|
} |
|
|
|
private static void decodeAverageFilter(byte[] curr, byte[] prev, |
|
int count, int bpp) { |
|
int raw, priorPixel, priorRow; |
|
|
|
for (int i = 0; i < bpp; i++) { |
|
raw = curr[i] & 0xff; |
|
priorRow = prev[i] & 0xff; |
|
|
|
curr[i] = (byte)(raw + priorRow/2); |
|
} |
|
|
|
for (int i = bpp; i < count; i++) { |
|
raw = curr[i] & 0xff; |
|
priorPixel = curr[i - bpp] & 0xff; |
|
priorRow = prev[i] & 0xff; |
|
|
|
curr[i] = (byte)(raw + (priorPixel + priorRow)/2); |
|
} |
|
} |
|
|
|
private static int paethPredictor(int a, int b, int c) { |
|
int p = a + b - c; |
|
int pa = Math.abs(p - a); |
|
int pb = Math.abs(p - b); |
|
int pc = Math.abs(p - c); |
|
|
|
if ((pa <= pb) && (pa <= pc)) { |
|
return a; |
|
} else if (pb <= pc) { |
|
return b; |
|
} else { |
|
return c; |
|
} |
|
} |
|
|
|
private static void decodePaethFilter(byte[] curr, byte[] prev, |
|
int count, int bpp) { |
|
int raw, priorPixel, priorRow, priorRowPixel; |
|
|
|
for (int i = 0; i < bpp; i++) { |
|
raw = curr[i] & 0xff; |
|
priorRow = prev[i] & 0xff; |
|
|
|
curr[i] = (byte)(raw + priorRow); |
|
} |
|
|
|
for (int i = bpp; i < count; i++) { |
|
raw = curr[i] & 0xff; |
|
priorPixel = curr[i - bpp] & 0xff; |
|
priorRow = prev[i] & 0xff; |
|
priorRowPixel = prev[i - bpp] & 0xff; |
|
|
|
curr[i] = (byte)(raw + paethPredictor(priorPixel, |
|
priorRow, |
|
priorRowPixel)); |
|
} |
|
} |
|
|
|
private void processPixels(int process, |
|
Raster src, WritableRaster dst, |
|
int xOffset, int step, int y, int width) { |
|
int srcX, dstX; |
|
|
|
// Create an array suitable for holding one pixel |
|
int[] ps = src.getPixel(0, 0, (int[])null); |
|
int[] pd = dst.getPixel(0, 0, (int[])null); |
|
|
|
dstX = xOffset; |
|
switch (process) { |
|
case POST_NONE: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
dst.setPixel(dstX, y, ps); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_GAMMA: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
for (int i = 0; i < inputBands; i++) { |
|
int x = ps[i]; |
|
ps[i] = gammaLut[x]; |
|
} |
|
|
|
dst.setPixel(dstX, y, ps); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_GRAY_LUT: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
pd[0] = grayLut[ps[0]]; |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_GRAY_LUT_ADD_TRANS: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
pd[0] = grayLut[val]; |
|
if (val == grayTransparentAlpha) { |
|
pd[1] = 0; |
|
} else { |
|
pd[1] = maxOpacity; |
|
} |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_PALETTE_TO_RGB: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
pd[0] = redPalette[val]; |
|
pd[1] = greenPalette[val]; |
|
pd[2] = bluePalette[val]; |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_PALETTE_TO_RGBA: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
pd[0] = redPalette[val]; |
|
pd[1] = greenPalette[val]; |
|
pd[2] = bluePalette[val]; |
|
pd[3] = alphaPalette[val]; |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_ADD_GRAY_TRANS: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
if (performGammaCorrection) { |
|
val = gammaLut[val]; |
|
} |
|
pd[0] = val; |
|
if (val == grayTransparentAlpha) { |
|
pd[1] = 0; |
|
} else { |
|
pd[1] = maxOpacity; |
|
} |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_ADD_RGB_TRANS: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int r = ps[0]; |
|
int g = ps[1]; |
|
int b = ps[2]; |
|
if (performGammaCorrection) { |
|
pd[0] = gammaLut[r]; |
|
pd[1] = gammaLut[g]; |
|
pd[2] = gammaLut[b]; |
|
} else { |
|
pd[0] = r; |
|
pd[1] = g; |
|
pd[2] = b; |
|
} |
|
if ((r == redTransparentAlpha) && |
|
(g == greenTransparentAlpha) && |
|
(b == blueTransparentAlpha)) { |
|
pd[3] = 0; |
|
} else { |
|
pd[3] = maxOpacity; |
|
} |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_REMOVE_GRAY_TRANS: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int g = ps[0]; |
|
if (performGammaCorrection) { |
|
pd[0] = gammaLut[g]; |
|
} else { |
|
pd[0] = g; |
|
} |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_REMOVE_RGB_TRANS: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int r = ps[0]; |
|
int g = ps[1]; |
|
int b = ps[2]; |
|
if (performGammaCorrection) { |
|
pd[0] = gammaLut[r]; |
|
pd[1] = gammaLut[g]; |
|
pd[2] = gammaLut[b]; |
|
} else { |
|
pd[0] = r; |
|
pd[1] = g; |
|
pd[2] = b; |
|
} |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_GAMMA_EXP: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
int alpha = ps[1]; |
|
int gamma = gammaLut[val]; |
|
pd[0] = gamma; |
|
pd[1] = gamma; |
|
pd[2] = gamma; |
|
pd[3] = alpha; |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_GRAY_ALPHA_EXP: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
int alpha = ps[1]; |
|
pd[0] = val; |
|
pd[1] = val; |
|
pd[2] = val; |
|
pd[3] = alpha; |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_ADD_GRAY_TRANS_EXP: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
if (performGammaCorrection) { |
|
val = gammaLut[val]; |
|
} |
|
pd[0] = val; |
|
pd[1] = val; |
|
pd[2] = val; |
|
if (val == grayTransparentAlpha) { |
|
pd[3] = 0; |
|
} else { |
|
pd[3] = maxOpacity; |
|
} |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
|
|
case POST_GRAY_LUT_ADD_TRANS_EXP: |
|
for (srcX = 0; srcX < width; srcX++) { |
|
src.getPixel(srcX, 0, ps); |
|
|
|
int val = ps[0]; |
|
int val2 = grayLut[val]; |
|
pd[0] = val2; |
|
pd[1] = val2; |
|
pd[2] = val2; |
|
if (val == grayTransparentAlpha) { |
|
pd[3] = 0; |
|
} else { |
|
pd[3] = maxOpacity; |
|
} |
|
|
|
dst.setPixel(dstX, y, pd); |
|
dstX += step; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
/** |
|
* Reads in an image of a given size and returns it as a |
|
* WritableRaster. |
|
*/ |
|
private void decodePass(WritableRaster imRas, |
|
int xOffset, int yOffset, |
|
int xStep, int yStep, |
|
int passWidth, int passHeight) { |
|
if ((passWidth == 0) || (passHeight == 0)) { |
|
return; |
|
} |
|
|
|
int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; |
|
int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow; |
|
byte[] curr = new byte[bytesPerRow]; |
|
byte[] prior = new byte[bytesPerRow]; |
|
|
|
// Create a 1-row tall Raster to hold the data |
|
WritableRaster passRow = |
|
createRaster(passWidth, 1, inputBands, |
|
eltsPerRow, |
|
bitDepth); |
|
DataBuffer dataBuffer = passRow.getDataBuffer(); |
|
int type = dataBuffer.getDataType(); |
|
byte[] byteData = null; |
|
short[] shortData = null; |
|
if (type == DataBuffer.TYPE_BYTE) { |
|
byteData = ((DataBufferByte)dataBuffer).getData(); |
|
} else { |
|
shortData = ((DataBufferUShort)dataBuffer).getData(); |
|
} |
|
|
|
// Decode the (sub)image row-by-row |
|
int srcY, dstY; |
|
for (srcY = 0, dstY = yOffset; |
|
srcY < passHeight; |
|
srcY++, dstY += yStep) { |
|
// Read the filter type byte and a row of data |
|
int filter = 0; |
|
try { |
|
filter = dataStream.read(); |
|
dataStream.readFully(curr, 0, bytesPerRow); |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
} |
|
|
|
switch (filter) { |
|
case PNG_FILTER_NONE: |
|
break; |
|
case PNG_FILTER_SUB: |
|
decodeSubFilter(curr, bytesPerRow, bytesPerPixel); |
|
break; |
|
case PNG_FILTER_UP: |
|
decodeUpFilter(curr, prior, bytesPerRow); |
|
break; |
|
case PNG_FILTER_AVERAGE: |
|
decodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel); |
|
break; |
|
case PNG_FILTER_PAETH: |
|
decodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel); |
|
break; |
|
default: |
|
// Error -- uknown filter type |
|
String msg = JaiI18N.getString("PNGImageDecoder16"); |
|
throw new RuntimeException(msg); |
|
} |
|
|
|
// Copy data into passRow byte by byte |
|
if (bitDepth < 16) { |
|
System.arraycopy(curr, 0, byteData, 0, bytesPerRow); |
|
} else { |
|
int idx = 0; |
|
for (int j = 0; j < eltsPerRow; j++) { |
|
shortData[j] = |
|
(short)((curr[idx] << 8) | (curr[idx + 1] & 0xff)); |
|
idx += 2; |
|
} |
|
} |
|
|
|
processPixels(postProcess, |
|
passRow, imRas, xOffset, xStep, dstY, passWidth); |
|
|
|
// Swap curr and prior |
|
byte[] tmp = prior; |
|
prior = curr; |
|
curr = tmp; |
|
} |
|
} |
|
|
|
private void decodeImage(boolean useInterlacing) { |
|
if (!useInterlacing) { |
|
decodePass(theTile, 0, 0, 1, 1, width, height); |
|
} else { |
|
decodePass(theTile, 0, 0, 8, 8, (width + 7)/8, (height + 7)/8); |
|
decodePass(theTile, 4, 0, 8, 8, (width + 3)/8, (height + 7)/8); |
|
decodePass(theTile, 0, 4, 4, 8, (width + 3)/4, (height + 3)/8); |
|
decodePass(theTile, 2, 0, 4, 4, (width + 1)/4, (height + 3)/4); |
|
decodePass(theTile, 0, 2, 2, 4, (width + 1)/2, (height + 1)/4); |
|
decodePass(theTile, 1, 0, 2, 2, width/2, (height + 1)/2); |
|
decodePass(theTile, 0, 1, 1, 2, width, height/2); |
|
} |
|
} |
|
|
|
// RenderedImage stuff |
|
|
|
public Raster getTile(int tileX, int tileY) { |
|
if (tileX != 0 || tileY != 0) { |
|
// Error -- bad tile requested |
|
String msg = JaiI18N.getString("PNGImageDecoder17"); |
|
throw new IllegalArgumentException(msg); |
|
} |
|
return theTile; |
|
} |
|
}
|
|
|