| /* |
| * 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(); |
| } |
| |
| } |