blob: 77454ece85026f475f04b0ffa9065b86eb7ec56d [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.imaging.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.imaging.ColorTools;
import org.apache.commons.imaging.ImageFormat;
import org.apache.commons.imaging.ImageFormats;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.ImageParser;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.common.ImageMetadata;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.formats.png.chunks.PngChunk;
import org.apache.commons.imaging.formats.png.chunks.PngChunkGama;
import org.apache.commons.imaging.formats.png.chunks.PngChunkIccp;
import org.apache.commons.imaging.formats.png.chunks.PngChunkIdat;
import org.apache.commons.imaging.formats.png.chunks.PngChunkIhdr;
import org.apache.commons.imaging.formats.png.chunks.PngChunkItxt;
import org.apache.commons.imaging.formats.png.chunks.PngChunkPhys;
import org.apache.commons.imaging.formats.png.chunks.PngChunkPlte;
import org.apache.commons.imaging.formats.png.chunks.PngChunkText;
import org.apache.commons.imaging.formats.png.chunks.PngChunkZtxt;
import org.apache.commons.imaging.formats.png.chunks.PngTextChunk;
import org.apache.commons.imaging.formats.png.transparencyfilters.TransparencyFilter;
import org.apache.commons.imaging.formats.png.transparencyfilters.TransparencyFilterGrayscale;
import org.apache.commons.imaging.formats.png.transparencyfilters.TransparencyFilterIndexedColor;
import org.apache.commons.imaging.formats.png.transparencyfilters.TransparencyFilterTrueColor;
import org.apache.commons.imaging.icc.IccProfileParser;
import org.apache.commons.imaging.util.IoUtils;
import org.apache.commons.imaging.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[] { ImageFormats.PNG, //
};
}
// private final static int tRNS = CharsToQuad('t', 'R', 'N', 's');
public static final String getChunkTypeName(final int chunkType) {
final StringBuilder result = new StringBuilder();
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(final InputStream is)
throws ImageReadException, IOException {
final List<PngChunk> chunks = readChunks(is, null, false);
final List<String> chunkTypes = new ArrayList<String>();
for (int i = 0; i < chunks.size(); i++) {
final PngChunk chunk = chunks.get(i);
chunkTypes.add(getChunkTypeName(chunk.chunkType));
}
return chunkTypes;
}
public boolean hasChuckType(final ByteSource byteSource, final int chunkType)
throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
readSignature(is);
List<PngChunk> chunks = readChunks(is, new int[] { chunkType, }, true);
canThrow = true;
return chunks.size() > 0;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
private boolean keepChunk(final int ChunkType, final int chunkTypes[]) {
// System.out.println("keepChunk: ");
if (chunkTypes == null) {
return true;
}
for (final int chunkType2 : chunkTypes) {
if (chunkType2 == ChunkType) {
return true;
}
}
return false;
}
private List<PngChunk> readChunks(final InputStream is, final int chunkTypes[],
final boolean returnAfterFirst) throws ImageReadException, IOException {
final List<PngChunk> result = new ArrayList<PngChunk>();
while (true) {
if (getDebug()) {
System.out.println("");
}
final int length = read4Bytes("Length", is, "Not a Valid PNG File");
final int chunkType = read4Bytes("ChunkType", is, "Not a Valid PNG File");
if (getDebug()) {
printCharQuad("ChunkType", chunkType);
debugNumber("Length", length, 4);
}
final boolean keep = keepChunk(chunkType, chunkTypes);
byte bytes[] = null;
if (keep) {
bytes = readBytes("Chunk Data", is, length,
"Not a Valid PNG File: Couldn't read Chunk Data.");
} else {
skipBytes(is, length, "Not a Valid PNG File");
}
if (getDebug()) {
if (bytes != null) {
debugNumber("bytes", bytes.length, 4);
}
}
final 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(final InputStream is) throws ImageReadException,
IOException {
readAndVerifyBytes(is, PNG_Signature,
"Not a Valid PNG Segment: Incorrect Signature");
}
private List<PngChunk> readChunks(final ByteSource byteSource, final int chunkTypes[],
final boolean returnAfterFirst) throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
readSignature(is);
final List<PngChunk> ret = readChunks(is, chunkTypes, returnAfterFirst);
canThrow = true;
return ret;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
@Override
public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final 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 ");
}
final PngChunkIccp pngChunkiCCP = (PngChunkIccp) chunks.get(0);
final byte bytes[] = pngChunkiCCP.UncompressedProfile;
return (bytes);
}
@Override
public Dimension getImageSize(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final 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");
}
final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) chunks.get(0);
return new Dimension(pngChunkIHDR.width, pngChunkIHDR.height);
}
public byte[] embedICCProfile(final byte image[], final byte profile[]) {
return null;
}
@Override
public boolean embedICCProfile(final File src, final File dst, final byte profile[]) {
return false;
}
@Override
public IImageMetadata getMetadata(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final List<PngChunk> chunks = readChunks(byteSource,
new int[] { tEXt, zTXt, }, true);
if ((chunks == null) || (chunks.size() < 1)) {
return null;
}
final ImageMetadata result = new ImageMetadata();
for (int i = 0; i < chunks.size(); i++) {
final PngTextChunk chunk = (PngTextChunk) chunks.get(i);
result.add(chunk.getKeyword(), chunk.getText());
}
return result;
}
private boolean isGrayscale(final 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(final 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(final List<PngChunk> v, final int type) {
final List<PngChunk> result = new ArrayList<PngChunk>();
for (int i = 0; i < v.size(); i++) {
final PngChunk chunk = v.get(i);
if (chunk.chunkType == type) {
result.add(chunk);
}
}
return result;
}
private boolean hasAlphaChannel(final 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(final 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(final int ColorType,
final 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.getBytes());
case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
return new TransparencyFilterTrueColor(pngChunktRNS.getBytes());
case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index;
return new TransparencyFilterIndexedColor(pngChunktRNS.getBytes());
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(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final 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");
}
final List<PngChunk> IHDRs = filterChunks(chunks, IHDR);
if (IHDRs.size() != 1) {
throw new ImageReadException("PNG contains more than one Header");
}
final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
boolean isTransparent = false;
final List<PngChunk> tRNSs = filterChunks(chunks, tRNS);
if (tRNSs.size() > 0) {
isTransparent = true;
} else {
// CE - Fix Alpha.
isTransparent = hasAlphaChannel(pngChunkIHDR.colorType);
// END FIX
}
PngChunkPhys pngChunkpHYs = null;
final 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);
}
final List<PngChunk> tEXts = filterChunks(chunks, tEXt);
final List<PngChunk> zTXts = filterChunks(chunks, zTXt);
final List<PngChunk> iTXts = filterChunks(chunks, iTXt);
final List<String> comments = new ArrayList<String>();
final List<PngText> textChunks = new ArrayList<PngText>();
for (int i = 0; i < tEXts.size(); i++) {
final PngChunkText pngChunktEXt = (PngChunkText) tEXts.get(i);
comments.add(pngChunktEXt.keyword + ": " + pngChunktEXt.text);
textChunks.add(pngChunktEXt.getContents());
}
for (int i = 0; i < zTXts.size(); i++) {
final PngChunkZtxt pngChunkzTXt = (PngChunkZtxt) zTXts.get(i);
comments.add(pngChunkzTXt.keyword + ": " + pngChunkzTXt.text);
textChunks.add(pngChunkzTXt.getContents());
}
for (int i = 0; i < iTXts.size(); i++) {
final PngChunkItxt pngChunkiTXt = (PngChunkItxt) iTXts.get(i);
comments.add(pngChunkiTXt.keyword + ": " + pngChunkiTXt.text);
textChunks.add(pngChunkiTXt.getContents());
}
final int BitsPerPixel = pngChunkIHDR.bitDepth
* samplesPerPixel(pngChunkIHDR.colorType);
final ImageFormat Format = ImageFormats.PNG;
final String FormatName = "PNG Portable Network Graphics";
final int Height = pngChunkIHDR.height;
final String MimeType = "image/png";
final int NumberOfImages = 1;
final int Width = pngChunkIHDR.width;
final 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
final double meters_per_inch = 0.0254;
PhysicalWidthDpi = (int) Math
.round(pngChunkpHYs.PixelsPerUnitXAxis
* meters_per_inch);
PhysicalWidthInch = (float) (Width / (pngChunkpHYs.PixelsPerUnitXAxis * meters_per_inch));
PhysicalHeightDpi = (int) Math
.round(pngChunkpHYs.PixelsPerUnitYAxis
* meters_per_inch);
PhysicalHeightInch = (float) (Height / (pngChunkpHYs.PixelsPerUnitYAxis * meters_per_inch));
}
final String FormatDetails = "Png";
boolean usesPalette = false;
final 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);
}
final 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(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
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);
// }
final 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");
}
final List<PngChunk> IHDRs = filterChunks(chunks, IHDR);
if (IHDRs.size() != 1) {
throw new ImageReadException("PNG contains more than one Header");
}
final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
final 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);
}
// -----
final 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++) {
final PngChunkIdat pngChunkIDAT = (PngChunkIdat) IDATs.get(i);
final byte bytes[] = pngChunkIDAT.getBytes();
// System.out.println(i + ": bytes: " + bytes.length);
baos.write(bytes);
}
final byte compressed[] = baos.toByteArray();
baos = null;
TransparencyFilter transparencyFilter = null;
final List<PngChunk> tRNSs = filterChunks(chunks, tRNS);
if (tRNSs.size() > 0) {
final PngChunk pngChunktRNS = tRNSs.get(0);
transparencyFilter = getTransparencyFilter(pngChunkIHDR.colorType,
pngChunktRNS);
}
ICC_Profile icc_profile = null;
GammaCorrection gammaCorrection = null;
{
final List<PngChunk> sRGBs = filterChunks(chunks, sRGB);
final List<PngChunk> gAMAs = filterChunks(chunks, gAMA);
final 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 (getDebug()) {
System.out.println("sRGB, no color management neccesary.");
}
} else if (iCCPs.size() == 1) {
if (getDebug()) {
System.out.println("iCCP.");
}
final PngChunkIccp pngChunkiCCP = (PngChunkIccp) iCCPs.get(0);
final byte bytes[] = pngChunkiCCP.UncompressedProfile;
icc_profile = ICC_Profile.getInstance(bytes);
} else if (gAMAs.size() == 1) {
final PngChunkGama pngChunkgAMA = (PngChunkGama) gAMAs.get(0);
final double gamma = pngChunkgAMA.getGamma();
// charles: what is the correct target value here?
// double targetGamma = 2.2;
final double targetGamma = 1.0;
final double diff = Math.abs(targetGamma - gamma);
if (diff >= 0.5) {
gammaCorrection = new GammaCorrection(gamma, targetGamma);
}
if (gammaCorrection != null) {
if (pngChunkPLTE != null) {
pngChunkPLTE.correct(gammaCorrection);
}
}
}
}
{
final int width = pngChunkIHDR.width;
final int height = pngChunkIHDR.height;
final int colorType = pngChunkIHDR.colorType;
final int bitDepth = pngChunkIHDR.bitDepth;
final int bitsPerSample = bitDepth;
if (pngChunkIHDR.filterMethod != 0) {
throw new ImageReadException("PNG: unknown FilterMethod: "
+ pngChunkIHDR.filterMethod);
}
final int samplesPerPixel = samplesPerPixel(pngChunkIHDR.colorType);
final boolean isGrayscale = isGrayscale(pngChunkIHDR.colorType);
final int bitsPerPixel = bitsPerSample * samplesPerPixel;
final boolean hasAlpha = colorType == COLOR_TYPE_GREYSCALE_WITH_ALPHA
|| colorType == COLOR_TYPE_TRUE_COLOR_WITH_ALPHA
|| transparencyFilter != null;
BufferedImage result;
if (isGrayscale) {
result = getBufferedImageFactory(params)
.getGrayscaleBufferedImage(width, height, hasAlpha);
} else {
result = getBufferedImageFactory(params).getColorBufferedImage(
width, height, hasAlpha);
}
final ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
final 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) {
final Boolean is_srgb = new IccProfileParser().issRGB(icc_profile);
if (is_srgb == null || !is_srgb.booleanValue()) {
final ICC_ColorSpace cs = new ICC_ColorSpace(icc_profile);
final ColorModel srgbCM = ColorModel.getRGBdefault();
final ColorSpace cs_sRGB = srgbCM.getColorSpace();
result = new ColorTools().convertBetweenColorSpaces(result,
cs, cs_sRGB);
}
}
return result;
}
}
@Override
public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
throws ImageReadException, IOException {
final ImageInfo imageInfo = getImageInfo(byteSource);
if (imageInfo == null) {
return false;
}
imageInfo.toString(pw, "");
final List<PngChunk> chunks = readChunks(byteSource, null, false);
final List<PngChunk> IHDRs = filterChunks(chunks, IHDR);
if (IHDRs.size() != 1) {
if (getDebug()) {
System.out.println("PNG contains more than one Header");
}
return false;
}
final 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++) {
final PngChunk chunk = chunks.get(i);
printCharQuad(pw, "\t" + i + ": ", chunk.chunkType);
}
pw.println("");
pw.flush();
return true;
}
@Override
public void writeImage(final BufferedImage src, final OutputStream os, final Map<String,Object> 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 ImagingConstants.
* @return Xmp Xml as String, if present. Otherwise, returns null.
*/
@Override
public String getXmpXml(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final List<PngChunk> chunks = readChunks(byteSource, new int[] { iTXt, },
false);
if ((chunks == null) || (chunks.size() < 1)) {
return null;
}
final List<PngChunkItxt> xmpChunks = new ArrayList<PngChunkItxt>();
for (int i = 0; i < chunks.size(); i++) {
final 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.");
}
final PngChunkItxt chunk = xmpChunks.get(0);
return chunk.getText();
}
}