blob: dde421cd0c6ca0c1155d4594ec27136f6e5a5e82 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.sanselan.formats.png;
import java.awt.Dimension;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.InflaterInputStream;
import org.apache.commons.sanselan.ColorTools;
import org.apache.commons.sanselan.ImageFormat;
import org.apache.commons.sanselan.ImageInfo;
import org.apache.commons.sanselan.ImageParser;
import org.apache.commons.sanselan.ImageReadException;
import org.apache.commons.sanselan.ImageWriteException;
import org.apache.commons.sanselan.common.IImageMetadata;
import org.apache.commons.sanselan.common.ImageMetadata;
import org.apache.commons.sanselan.common.bytesource.ByteSource;
import org.apache.commons.sanselan.formats.png.chunks.PngChunk;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkIdat;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkIhdr;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkPlte;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkGama;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkIccp;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkItxt;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkPhys;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkText;
import org.apache.commons.sanselan.formats.png.chunks.PngChunkZtxt;
import org.apache.commons.sanselan.formats.png.chunks.PngTextChunk;
import org.apache.commons.sanselan.formats.transparencyfilters.TransparencyFilter;
import org.apache.commons.sanselan.formats.transparencyfilters.TransparencyFilterGrayscale;
import org.apache.commons.sanselan.formats.transparencyfilters.TransparencyFilterIndexedColor;
import org.apache.commons.sanselan.formats.transparencyfilters.TransparencyFilterTrueColor;
import org.apache.commons.sanselan.icc.IccProfileParser;
import org.apache.commons.sanselan.util.Debug;
import org.apache.commons.sanselan.util.ParamMap;
public class PngImageParser extends ImageParser implements PngConstants
{
public PngImageParser()
{
// setDebug(true);
}
@Override
public String getName()
{
return "Png-Custom";
}
@Override
public String getDefaultExtension()
{
return DEFAULT_EXTENSION;
}
private static final String DEFAULT_EXTENSION = ".png";
private static final String ACCEPTED_EXTENSIONS[] = { DEFAULT_EXTENSION, };
@Override
protected String[] getAcceptedExtensions()
{
return ACCEPTED_EXTENSIONS;
}
@Override
protected ImageFormat[] getAcceptedTypes()
{
return new ImageFormat[] { ImageFormat.IMAGE_FORMAT_PNG, //
};
}
// private final static int tRNS = CharsToQuad('t', 'R', 'N', 's');
public static final String getChunkTypeName(int chunkType) {
StringBuffer result = new StringBuffer();
result.append((char) (0xff & (chunkType >> 24)));
result.append((char) (0xff & (chunkType >> 16)));
result.append((char) (0xff & (chunkType >> 8)));
result.append((char) (0xff & (chunkType >> 0)));
return result.toString();
}
/**
* @return List of String-formatted chunk types, ie. "tRNs".
*/
public List<String> getChuckTypes(InputStream is) throws ImageReadException, IOException {
List<PngChunk> chunks = readChunks(is, null, false);
List<String> chunkTypes = new ArrayList<String>();
for (int i=0; i<chunks.size(); i++) {
PngChunk chunk = chunks.get(i);
chunkTypes.add(getChunkTypeName(chunk.chunkType));
}
return chunkTypes;
}
public boolean hasChuckType(ByteSource byteSource, int chunkType) throws ImageReadException, IOException
{
InputStream is = null;
try
{
is = byteSource.getInputStream();
List<PngChunk> chunks = null;
readSignature(is);
chunks = readChunks(is, new int[] { chunkType, }, true);
return chunks.size() > 0;
}
finally
{
try
{
if (is != null) {
is.close();
}
} catch (Exception e)
{
Debug.debug(e);
}
}
}
private boolean keepChunk(int ChunkType, int chunkTypes[])
{
// System.out.println("keepChunk: ");
if (chunkTypes == null)
return true;
for (int i = 0; i < chunkTypes.length; i++)
{
if (chunkTypes[i] == ChunkType)
return true;
}
return false;
}
private List<PngChunk> readChunks(InputStream is, int chunkTypes[], boolean returnAfterFirst) throws ImageReadException, IOException
{
List<PngChunk> result = new ArrayList<PngChunk>();
while (true)
{
if (debug)
System.out.println("");
int length = read4Bytes("Length", is, "Not a Valid PNG File");
int chunkType = read4Bytes("ChunkType", is, "Not a Valid PNG File");
if (debug)
{
printCharQuad("ChunkType", chunkType);
debugNumber("Length", length, 4);
}
boolean keep = keepChunk(chunkType, chunkTypes);
byte bytes[] = null;
if (keep)
{
bytes = readByteArray("Chunk Data", length, is,
"Not a Valid PNG File: Couldn't read Chunk Data.");
} else
skipBytes(is, length, "Not a Valid PNG File");
if (debug)
if (bytes != null)
debugNumber("bytes", bytes.length, 4);
int CRC = read4Bytes("CRC", is, "Not a Valid PNG File");
if (keep)
{
if (chunkType == iCCP)
result.add(new PngChunkIccp(length, chunkType, CRC, bytes));
else if (chunkType == tEXt)
result.add(new PngChunkText(length, chunkType, CRC, bytes));
else if (chunkType == zTXt)
result.add(new PngChunkZtxt(length, chunkType, CRC, bytes));
else if (chunkType == IHDR)
result.add(new PngChunkIhdr(length, chunkType, CRC, bytes));
else if (chunkType == PLTE)
result.add(new PngChunkPlte(length, chunkType, CRC, bytes));
else if (chunkType == pHYs)
result.add(new PngChunkPhys(length, chunkType, CRC, bytes));
else if (chunkType == IDAT)
result.add(new PngChunkIdat(length, chunkType, CRC, bytes));
else if (chunkType == gAMA)
result.add(new PngChunkGama(length, chunkType, CRC, bytes));
else if (chunkType == iTXt)
result.add(new PngChunkItxt(length, chunkType, CRC, bytes));
else
result.add(new PngChunk(length, chunkType, CRC, bytes));
if (returnAfterFirst)
return result;
}
if (chunkType == IEND)
break;
}
return result;
}
public void readSignature(InputStream is) throws ImageReadException,
IOException
{
readAndVerifyBytes(is, PNG_Signature,
"Not a Valid PNG Segment: Incorrect Signature");
}
private List<PngChunk> readChunks(ByteSource byteSource, int chunkTypes[],
boolean returnAfterFirst) throws ImageReadException, IOException
{
InputStream is = null;
try
{
is = byteSource.getInputStream();
readSignature(is);
return readChunks(is, chunkTypes, returnAfterFirst);
}
finally
{
try
{
if (is != null) {
is.close();
}
} catch (Exception e)
{
Debug.debug(e);
}
}
}
@Override
public byte[] getICCProfileBytes(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
List<PngChunk> chunks = readChunks(byteSource, new int[] { iCCP, }, true);
if ((chunks == null) || (chunks.size() < 1))
{
// throw new ImageReadException("Png: No chunks");
return null;
}
if (chunks.size() > 1)
throw new ImageReadException(
"PNG contains more than one ICC Profile ");
PngChunkIccp pngChunkiCCP = (PngChunkIccp) chunks.get(0);
byte bytes[] = pngChunkiCCP.UncompressedProfile;
return (bytes);
}
@Override
public Dimension getImageSize(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
List<PngChunk> chunks = readChunks(byteSource, new int[] { IHDR, }, true);
if ((chunks == null) || (chunks.size() < 1))
throw new ImageReadException("Png: No chunks");
if (chunks.size() > 1)
throw new ImageReadException("PNG contains more than one Header");
PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) chunks.get(0);
return new Dimension(pngChunkIHDR.width, pngChunkIHDR.height);
}
public byte[] embedICCProfile(byte image[], byte profile[])
{
return null;
}
@Override
public boolean embedICCProfile(File src, File dst, byte profile[])
{
return false;
}
@Override
public IImageMetadata getMetadata(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
List<PngChunk> chunks = readChunks(byteSource, new int[] { tEXt, zTXt, }, true);
if ((chunks == null) || (chunks.size() < 1))
return null;
ImageMetadata result = new ImageMetadata();
for (int i = 0; i < chunks.size(); i++)
{
PngTextChunk chunk = (PngTextChunk) chunks.get(i);
result.add(chunk.getKeyword(), chunk.getText());
}
return result;
}
private boolean isGrayscale(int colorType) throws ImageReadException
{
// Color type is a single-byte integer that describes the interpretation
// of the
// image data. Color type codes represent sums of the following values:
// 1 (palette used), 2 (color used), and 4 (alpha channel used).
// Valid values are 0, 2, 3, 4, and 6.
//
// Bit depth restrictions for each color type are imposed to simplify
// implementations
// and to prohibit combinations that do not compress well. Decoders must
// support all
// valid combinations of bit depth and color type. The allowed
// combinations are:
//
// Color Allowed Interpretation
// Type Bit Depths
//
// 0 1,2,4,8,16 Each pixel is a grayscale sample.
//
// 2 8,16 Each pixel is an R,G,B triple.
//
// 3 1,2,4,8 Each pixel is a palette index;
// a PLTE chunk must appear.
//
// 4 8,16 Each pixel is a grayscale sample,
// followed by an alpha sample.
//
// 6 8,16 Each pixel is an R,G,B triple,
// followed by an alpha sample.
switch (colorType)
{
case COLOR_TYPE_GREYSCALE:
return true;
case COLOR_TYPE_TRUE_COLOR:
return false;
case COLOR_TYPE_INDEXED_COLOR:
return false;
case COLOR_TYPE_GREYSCALE_WITH_ALPHA:
return true;
case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA:
return false;
}
// return -1;
throw new ImageReadException("PNG: unknown color type: " + colorType);
}
private int samplesPerPixel(int colorType) throws ImageReadException
{
// Color type is a single-byte integer that describes the interpretation
// of the
// image data. Color type codes represent sums of the following values:
// 1 (palette used), 2 (color used), and 4 (alpha channel used).
// Valid values are 0, 2, 3, 4, and 6.
//
// Bit depth restrictions for each color type are imposed to simplify
// implementations
// and to prohibit combinations that do not compress well. Decoders must
// support all
// valid combinations of bit depth and color type. The allowed
// combinations are:
//
// Color Allowed Interpretation
// Type Bit Depths
//
// 0 1,2,4,8,16 Each pixel is a grayscale sample.
//
// 2 8,16 Each pixel is an R,G,B triple.
//
// 3 1,2,4,8 Each pixel is a palette index;
// a PLTE chunk must appear.
//
// 4 8,16 Each pixel is a grayscale sample,
// followed by an alpha sample.
//
// 6 8,16 Each pixel is an R,G,B triple,
// followed by an alpha sample.
switch (colorType)
{
case COLOR_TYPE_GREYSCALE:
return 1;
case COLOR_TYPE_TRUE_COLOR:
return 3;
case COLOR_TYPE_INDEXED_COLOR:
return 1; // is this accurate ? how may bits per index?
case COLOR_TYPE_GREYSCALE_WITH_ALPHA:
return 2;
case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA:
return 4;
}
// return -1;
throw new ImageReadException("PNG: unknown color type: " + colorType);
}
private List<PngChunk> filterChunks(List<PngChunk> v, int type)
{
List<PngChunk> result = new ArrayList<PngChunk>();
for (int i = 0; i < v.size(); i++)
{
PngChunk chunk = v.get(i);
if (chunk.chunkType == type)
result.add(chunk);
}
return result;
}
private boolean hasAlphaChannel(int ColorType) throws ImageReadException
{
switch (ColorType)
{
case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
// sample.
case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index;
return false;
case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a grayscale
// sample,
// followed by an alpha sample.
case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an R,G,B
// triple,
// followed by an alpha sample.
return true;
default:
throw new ImageReadException("PNG: unknown color type: "
+ ColorType);
}
}
private String getColorTypeDescription(int ColorType)
{
switch (ColorType)
{
case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
// sample.
return "grayscale";
case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
return "rgb";
case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index;
return "indexed rgb";
case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a grayscale
// sample,
// followed by an alpha sample.
return "grayscale w/ alpha";
case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an R,G,B
// triple,
// followed by an alpha sample.
return "RGB w/ alpha";
default:
return "Unknown Color Type";
}
}
// TODO: I have been too casual about making inner classes subclass of
// BinaryFileParser
// I may not have always preserved byte order correctly.
private TransparencyFilter getTransparencyFilter(int ColorType,
PngChunk pngChunktRNS) throws ImageReadException, IOException
{
// this.printCharQuad("pngChunktRNS.ChunkType", pngChunktRNS.ChunkType);
// this.debugNumber("pngChunktRNS.Length", pngChunktRNS.Length);
switch (ColorType)
{
case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
// sample.
return new TransparencyFilterGrayscale(pngChunktRNS.bytes);
case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
return new TransparencyFilterTrueColor(pngChunktRNS.bytes);
case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index;
return new TransparencyFilterIndexedColor(pngChunktRNS.bytes);
case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a grayscale
// sample,
case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an R,G,B
// triple,
default:
throw new ImageReadException(
"Simple Transparency not compatible with ColorType: "
+ ColorType);
}
}
@Override
public ImageInfo getImageInfo(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
List<PngChunk> chunks = readChunks(byteSource, new int[] { IHDR, pHYs, tEXt, zTXt, tRNS, PLTE, iTXt, }, false);
// if(chunks!=null)
// System.out.println("chunks: " + chunks.size());
if ((chunks == null) || (chunks.size() < 1))
throw new ImageReadException("PNG: no chunks");
List<PngChunk> IHDRs = filterChunks(chunks, IHDR);
if (IHDRs.size() != 1)
throw new ImageReadException("PNG contains more than one Header");
PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
PngChunk pngChunktRNS = null;
boolean isTransparent = false;
List<PngChunk> tRNSs = filterChunks(chunks, tRNS);
if (tRNSs.size() > 0)
{
isTransparent = true;
pngChunktRNS = IHDRs.get(0);
} else {
// CE - Fix Alpha.
isTransparent = hasAlphaChannel(pngChunkIHDR.colorType);
// END FIX
}
PngChunkPhys pngChunkpHYs = null;
List<PngChunk> pHYss = filterChunks(chunks, pHYs);
if (pHYss.size() > 1)
throw new ImageReadException("PNG contains more than one pHYs: "
+ pHYss.size());
else if (pHYss.size() == 1)
pngChunkpHYs = (PngChunkPhys) pHYss.get(0);
List<PngChunk> tEXts = filterChunks(chunks, tEXt);
List<PngChunk> zTXts = filterChunks(chunks, zTXt);
List<PngChunk> iTXts = filterChunks(chunks, iTXt);
{
List<String> comments = new ArrayList<String>();
List<PngText> textChunks = new ArrayList<PngText>();
for (int i = 0; i < tEXts.size(); i++)
{
PngChunkText pngChunktEXt = (PngChunkText) tEXts.get(i);
comments.add(pngChunktEXt.keyword + ": " + pngChunktEXt.text);
textChunks.add(pngChunktEXt.getContents());
}
for (int i = 0; i < zTXts.size(); i++)
{
PngChunkZtxt pngChunkzTXt = (PngChunkZtxt) zTXts.get(i);
comments.add(pngChunkzTXt.keyword + ": " + pngChunkzTXt.text);
textChunks.add(pngChunkzTXt.getContents());
}
for (int i = 0; i < iTXts.size(); i++)
{
PngChunkItxt pngChunkiTXt = (PngChunkItxt) iTXts.get(i);
comments.add(pngChunkiTXt.keyword + ": " + pngChunkiTXt.text);
textChunks.add(pngChunkiTXt.getContents());
}
int BitsPerPixel = pngChunkIHDR.bitDepth
* samplesPerPixel(pngChunkIHDR.colorType);
ImageFormat Format = ImageFormat.IMAGE_FORMAT_PNG;
String FormatName = "PNG Portable Network Graphics";
int Height = pngChunkIHDR.height;
String MimeType = "image/png";
int NumberOfImages = 1;
int Width = pngChunkIHDR.width;
boolean isProgressive = (pngChunkIHDR.interlaceMethod != 0);
int PhysicalHeightDpi = -1;
float PhysicalHeightInch = -1;
int PhysicalWidthDpi = -1;
float PhysicalWidthInch = -1;
// if (pngChunkpHYs != null)
// {
// System.out.println("\t" + "pngChunkpHYs.UnitSpecifier: " +
// pngChunkpHYs.UnitSpecifier );
// System.out.println("\t" + "pngChunkpHYs.PixelsPerUnitYAxis: " +
// pngChunkpHYs.PixelsPerUnitYAxis );
// System.out.println("\t" + "pngChunkpHYs.PixelsPerUnitXAxis: " +
// pngChunkpHYs.PixelsPerUnitXAxis );
// }
if ((pngChunkpHYs != null) && (pngChunkpHYs.UnitSpecifier == 1)) // meters
{
double meters_per_inch = 0.0254;
PhysicalWidthDpi = (int) Math
.round(pngChunkpHYs.PixelsPerUnitXAxis
* meters_per_inch);
PhysicalWidthInch = (float) ((double) Width
/ ((double) pngChunkpHYs.PixelsPerUnitXAxis * meters_per_inch));
PhysicalHeightDpi = (int) Math
.round(pngChunkpHYs.PixelsPerUnitYAxis
* meters_per_inch);
PhysicalHeightInch = (float) ((double) Height
/ ((double) pngChunkpHYs.PixelsPerUnitYAxis * meters_per_inch));
}
String FormatDetails = "Png";
boolean usesPalette = false;
List<PngChunk> PLTEs = filterChunks(chunks, PLTE);
if (PLTEs.size() > 1)
usesPalette = true;
int ColorType;
switch (pngChunkIHDR.colorType)
{
case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
// sample.
case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a
// grayscale sample,
// followed by an alpha sample.
ColorType = ImageInfo.COLOR_TYPE_GRAYSCALE;
break;
case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette
// index;
case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an
// R,G,B triple,
// followed by an alpha sample.
ColorType = ImageInfo.COLOR_TYPE_RGB;
break;
default:
throw new ImageReadException("Png: Unknown ColorType: "
+ pngChunkIHDR.colorType);
}
String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_PNG_FILTER;
return new PngImageInfo(FormatDetails, BitsPerPixel,
comments, Format, FormatName, Height, MimeType,
NumberOfImages, PhysicalHeightDpi, PhysicalHeightInch,
PhysicalWidthDpi, PhysicalWidthInch, Width, isProgressive,
isTransparent, usesPalette, ColorType,
compressionAlgorithm, textChunks);
}
}
@Override
public BufferedImage getBufferedImage(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
boolean verbose = ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE,
false);
if (params.containsKey(PARAM_KEY_VERBOSE))
params.remove(PARAM_KEY_VERBOSE);
// if (params.size() > 0) {
// Object firstKey = params.keySet().iterator().next();
// throw new ImageWriteException("Unknown parameter: " + firstKey);
// }
List<PngChunk> chunks = readChunks(byteSource, new int[] { IHDR, PLTE, IDAT,
tRNS, iCCP, gAMA, sRGB, }, false);
if ((chunks == null) || (chunks.size() < 1))
throw new ImageReadException("PNG: no chunks");
List<PngChunk> IHDRs = filterChunks(chunks, IHDR);
if (IHDRs.size() != 1)
throw new ImageReadException("PNG contains more than one Header");
PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
List<PngChunk> PLTEs = filterChunks(chunks, PLTE);
if (PLTEs.size() > 1)
throw new ImageReadException("PNG contains more than one Palette");
PngChunkPlte pngChunkPLTE = null;
if (PLTEs.size() == 1)
pngChunkPLTE = (PngChunkPlte) PLTEs.get(0);
// -----
List<PngChunk> IDATs = filterChunks(chunks, IDAT);
if (IDATs.size() < 1)
throw new ImageReadException("PNG missing image data");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int i = 0; i < IDATs.size(); i++)
{
PngChunkIdat pngChunkIDAT = (PngChunkIdat) IDATs.get(i);
byte bytes[] = pngChunkIDAT.bytes;
// System.out.println(i + ": bytes: " + bytes.length);
baos.write(bytes);
}
byte compressed[] = baos.toByteArray();
baos = null;
TransparencyFilter transparencyFilter = null;
List<PngChunk> tRNSs = filterChunks(chunks, tRNS);
if (tRNSs.size() > 0)
{
PngChunk pngChunktRNS = tRNSs.get(0);
transparencyFilter = getTransparencyFilter(pngChunkIHDR.colorType,
pngChunktRNS);
}
ICC_Profile icc_profile = null;
GammaCorrection gammaCorrection = null;
{
List<PngChunk> sRGBs = filterChunks(chunks, sRGB);
List<PngChunk> gAMAs = filterChunks(chunks, gAMA);
List<PngChunk> iCCPs = filterChunks(chunks, iCCP);
if (sRGBs.size() > 1)
throw new ImageReadException("PNG: unexpected sRGB chunk");
if (gAMAs.size() > 1)
throw new ImageReadException("PNG: unexpected gAMA chunk");
if (iCCPs.size() > 1)
throw new ImageReadException("PNG: unexpected iCCP chunk");
if (sRGBs.size() == 1)
{
// no color management neccesary.
if (debug)
System.out.println("sRGB, no color management neccesary.");
} else if (iCCPs.size() == 1)
{
if (debug)
System.out.println("iCCP.");
PngChunkIccp pngChunkiCCP = (PngChunkIccp) iCCPs.get(0);
byte bytes[] = pngChunkiCCP.UncompressedProfile;
icc_profile = ICC_Profile.getInstance(bytes);
} else if (gAMAs.size() == 1)
{
PngChunkGama pngChunkgAMA = (PngChunkGama) gAMAs.get(0);
double gamma = pngChunkgAMA.getGamma();
// charles: what is the correct target value here?
// double targetGamma = 2.2;
double targetGamma = 1.0;
double diff = Math.abs(targetGamma - gamma);
if (diff >= 0.5)
gammaCorrection = new GammaCorrection(gamma, targetGamma);
if (gammaCorrection != null)
if (pngChunkPLTE != null)
pngChunkPLTE.correct(gammaCorrection);
}
}
{
int width = pngChunkIHDR.width;
int height = pngChunkIHDR.height;
int colorType = pngChunkIHDR.colorType;
int bitDepth = pngChunkIHDR.bitDepth;
int bitsPerSample = bitDepth;
if (pngChunkIHDR.filterMethod != 0)
throw new ImageReadException("PNG: unknown FilterMethod: "
+ pngChunkIHDR.filterMethod);
int samplesPerPixel = samplesPerPixel(pngChunkIHDR.colorType);
boolean isGrayscale = isGrayscale(pngChunkIHDR.colorType);
int bitsPerPixel = bitsPerSample * samplesPerPixel;
boolean hasAlpha = colorType == COLOR_TYPE_GREYSCALE_WITH_ALPHA
|| colorType == COLOR_TYPE_TRUE_COLOR_WITH_ALPHA;
BufferedImage result;
if (isGrayscale)
result = getBufferedImageFactory(params)
.getGrayscaleBufferedImage(width, height, hasAlpha);
else
result = getBufferedImageFactory(params).getColorBufferedImage(
width, height, hasAlpha);
ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
InflaterInputStream iis = new InflaterInputStream(bais);
ScanExpediter scanExpediter;
if (pngChunkIHDR.interlaceMethod == 0)
scanExpediter = new ScanExpediterSimple(width, height, iis,
result, colorType, bitDepth, bitsPerPixel,
pngChunkPLTE, gammaCorrection, transparencyFilter);
else if (pngChunkIHDR.interlaceMethod == 1)
scanExpediter = new ScanExpediterInterlaced(width, height, iis,
result, colorType, bitDepth, bitsPerPixel,
pngChunkPLTE, gammaCorrection, transparencyFilter);
else
throw new ImageReadException("Unknown InterlaceMethod: "
+ pngChunkIHDR.interlaceMethod);
scanExpediter.drive();
if (icc_profile != null)
{
Boolean is_srgb = new IccProfileParser().issRGB(icc_profile);
if (is_srgb == null || !is_srgb.booleanValue())
{
ICC_ColorSpace cs = new ICC_ColorSpace(icc_profile);
ColorModel srgbCM = ColorModel.getRGBdefault();
ColorSpace cs_sRGB = srgbCM.getColorSpace();
result = new ColorTools().convertBetweenColorSpaces(result,
cs, cs_sRGB);
}
}
return result;
}
}
@Override
public boolean dumpImageFile(PrintWriter pw, ByteSource byteSource)
throws ImageReadException, IOException
{
ImageInfo imageInfo = getImageInfo(byteSource);
if (imageInfo == null)
return false;
imageInfo.toString(pw, "");
{
List<PngChunk> chunks = readChunks(byteSource, null, false);
{
List<PngChunk> IHDRs = filterChunks(chunks, IHDR);
if (IHDRs.size() != 1)
{
if (debug)
System.out.println("PNG contains more than one Header");
return false;
}
PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
pw.println("Color: "
+ getColorTypeDescription(pngChunkIHDR.colorType));
}
pw.println("chunks: " + chunks.size());
if ((chunks.size() < 1))
return false;
for (int i = 0; i < chunks.size(); i++)
{
PngChunk chunk = chunks.get(i);
printCharQuad(pw, "\t" + i + ": ", chunk.chunkType);
}
}
pw.println("");
pw.flush();
return true;
}
@Override
public void writeImage(BufferedImage src, OutputStream os, Map params)
throws ImageWriteException, IOException
{
new PngWriter(params).writeImage(src, os, params);
}
/**
* Extracts embedded XML metadata as XML string.
* <p>
*
* @param byteSource
* File containing image data.
* @param params
* Map of optional parameters, defined in SanselanConstants.
* @return Xmp Xml as String, if present. Otherwise, returns null.
*/
@Override
public String getXmpXml(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
List<PngChunk> chunks = readChunks(byteSource, new int[] { iTXt, }, false);
if ((chunks == null) || (chunks.size() < 1))
return null;
List<PngChunkItxt> xmpChunks = new ArrayList<PngChunkItxt>();
for (int i = 0; i < chunks.size(); i++)
{
PngChunkItxt chunk = (PngChunkItxt) chunks.get(i);
if (!chunk.getKeyword().equals(XMP_KEYWORD))
continue;
xmpChunks.add(chunk);
}
if (xmpChunks.size() < 1)
return null;
if (xmpChunks.size() > 1)
throw new ImageReadException(
"PNG contains more than one XMP chunk.");
PngChunkItxt chunk = xmpChunks.get(0);
return chunk.getText();
}
}