| /* |
| * 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 static org.apache.commons.imaging.common.BinaryFunctions.printCharQuad; |
| import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes; |
| import static org.apache.commons.imaging.common.BinaryFunctions.readAndVerifyBytes; |
| import static org.apache.commons.imaging.common.BinaryFunctions.readBytes; |
| import static org.apache.commons.imaging.common.BinaryFunctions.skipBytes; |
| |
| 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.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| 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.ImagingException; |
| import org.apache.commons.imaging.bytesource.ByteSource; |
| import org.apache.commons.imaging.common.Allocator; |
| import org.apache.commons.imaging.common.GenericImageMetadata; |
| import org.apache.commons.imaging.common.ImageMetadata; |
| import org.apache.commons.imaging.common.XmpEmbeddable; |
| import org.apache.commons.imaging.common.XmpImagingParameters; |
| 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.PngChunkScal; |
| 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; |
| |
| public class PngImageParser extends ImageParser<PngImagingParameters> implements XmpEmbeddable<PngImagingParameters> { |
| |
| private static final Logger LOGGER = Logger.getLogger(PngImageParser.class.getName()); |
| |
| private static final String DEFAULT_EXTENSION = ImageFormats.PNG.getDefaultExtension(); |
| private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.PNG.getExtensions(); |
| |
| public static 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(); |
| } |
| |
| @Override |
| public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) |
| throws ImagingException, 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, ChunkType.IHDR); |
| if (IHDRs.size() != 1) { |
| if (LOGGER.isLoggable(Level.FINEST)) { |
| LOGGER.finest("PNG contains more than one Header"); |
| } |
| return false; |
| } |
| final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0); |
| pw.println("Color: " + pngChunkIHDR.getPngColorType().name()); |
| |
| pw.println("chunks: " + chunks.size()); |
| |
| if ((chunks.isEmpty())) { |
| return false; |
| } |
| |
| for (int i = 0; i < chunks.size(); i++) { |
| final PngChunk chunk = chunks.get(i); |
| printCharQuad(pw, "\t" + i + ": ", chunk.getChunkType()); |
| } |
| |
| pw.println(""); |
| |
| pw.flush(); |
| |
| return true; |
| } |
| |
| private List<PngChunk> filterChunks(final List<PngChunk> chunks, final ChunkType type) { |
| final List<PngChunk> result = new ArrayList<>(); |
| |
| for (final PngChunk chunk : chunks) { |
| if (chunk.getChunkType() == type.value) { |
| result.add(chunk); |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected String[] getAcceptedExtensions() { |
| return ACCEPTED_EXTENSIONS.clone(); |
| } |
| |
| @Override |
| protected ImageFormat[] getAcceptedTypes() { |
| return new ImageFormat[] { ImageFormats.PNG, // |
| }; |
| } |
| |
| // private final static int tRNS = CharsToQuad('t', 'R', 'N', 's'); |
| |
| @Override |
| public BufferedImage getBufferedImage(final ByteSource byteSource, final PngImagingParameters params) |
| throws ImagingException, IOException { |
| |
| final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { |
| ChunkType.IHDR, |
| ChunkType.PLTE, |
| ChunkType.IDAT, |
| ChunkType.tRNS, |
| ChunkType.iCCP, |
| ChunkType.gAMA, |
| ChunkType.sRGB, |
| }, false); |
| |
| if (chunks.isEmpty()) { |
| throw new ImagingException("PNG: no chunks"); |
| } |
| |
| final List<PngChunk> IHDRs = filterChunks(chunks, ChunkType.IHDR); |
| if (IHDRs.size() != 1) { |
| throw new ImagingException("PNG contains more than one Header"); |
| } |
| |
| final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0); |
| |
| final List<PngChunk> PLTEs = filterChunks(chunks, ChunkType.PLTE); |
| if (PLTEs.size() > 1) { |
| throw new ImagingException("PNG contains more than one Palette"); |
| } |
| |
| PngChunkPlte pngChunkPLTE = null; |
| if (PLTEs.size() == 1) { |
| pngChunkPLTE = (PngChunkPlte) PLTEs.get(0); |
| } |
| |
| final List<PngChunk> IDATs = filterChunks(chunks, ChunkType.IDAT); |
| if (IDATs.isEmpty()) { |
| throw new ImagingException("PNG missing image data"); |
| } |
| |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| for (final PngChunk IDAT : IDATs) { |
| final PngChunkIdat pngChunkIDAT = (PngChunkIdat) IDAT; |
| 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, ChunkType.tRNS); |
| if (!tRNSs.isEmpty()) { |
| final PngChunk pngChunktRNS = tRNSs.get(0); |
| transparencyFilter = getTransparencyFilter(pngChunkIHDR.getPngColorType(), pngChunktRNS); |
| } |
| |
| ICC_Profile iccProfile = null; |
| GammaCorrection gammaCorrection = null; |
| { |
| final List<PngChunk> sRGBs = filterChunks(chunks, ChunkType.sRGB); |
| final List<PngChunk> gAMAs = filterChunks(chunks, ChunkType.gAMA); |
| final List<PngChunk> iCCPs = filterChunks(chunks, ChunkType.iCCP); |
| if (sRGBs.size() > 1) { |
| throw new ImagingException("PNG: unexpected sRGB chunk"); |
| } |
| if (gAMAs.size() > 1) { |
| throw new ImagingException("PNG: unexpected gAMA chunk"); |
| } |
| if (iCCPs.size() > 1) { |
| throw new ImagingException("PNG: unexpected iCCP chunk"); |
| } |
| |
| if (sRGBs.size() == 1) { |
| // no color management necessary. |
| if (LOGGER.isLoggable(Level.FINEST)) { |
| LOGGER.finest("sRGB, no color management necessary."); |
| } |
| } else if (iCCPs.size() == 1) { |
| if (LOGGER.isLoggable(Level.FINEST)) { |
| LOGGER.finest("iCCP."); |
| } |
| |
| final PngChunkIccp pngChunkiCCP = (PngChunkIccp) iCCPs.get(0); |
| final byte[] bytes = pngChunkiCCP.getUncompressedProfile(); |
| |
| try { |
| iccProfile = ICC_Profile.getInstance(bytes); |
| } catch (final IllegalArgumentException iae) { |
| throw new ImagingException("The image data does not correspond to a valid ICC Profile", iae); |
| } |
| } 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.getWidth(); |
| final int height = pngChunkIHDR.getHeight(); |
| final PngColorType pngColorType = pngChunkIHDR.getPngColorType(); |
| final int bitDepth = pngChunkIHDR.getBitDepth(); |
| |
| if (pngChunkIHDR.getFilterMethod() != 0) { |
| throw new ImagingException("PNG: unknown FilterMethod: " + pngChunkIHDR.getFilterMethod()); |
| } |
| |
| final int bitsPerPixel = bitDepth * pngColorType.getSamplesPerPixel(); |
| |
| final boolean hasAlpha = pngColorType.hasAlpha() || transparencyFilter != null; |
| |
| BufferedImage result; |
| if (pngColorType.isGreyscale()) { |
| 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; |
| |
| switch (pngChunkIHDR.getInterlaceMethod()) { |
| case NONE: |
| scanExpediter = new ScanExpediterSimple(width, height, iis, |
| result, pngColorType, bitDepth, bitsPerPixel, |
| pngChunkPLTE, gammaCorrection, transparencyFilter); |
| break; |
| case ADAM7: |
| scanExpediter = new ScanExpediterInterlaced(width, height, iis, |
| result, pngColorType, bitDepth, bitsPerPixel, |
| pngChunkPLTE, gammaCorrection, transparencyFilter); |
| break; |
| default: |
| throw new ImagingException("Unknown InterlaceMethod: " + pngChunkIHDR.getInterlaceMethod()); |
| } |
| |
| scanExpediter.drive(); |
| |
| if (iccProfile != null) { |
| final boolean is_srgb = new IccProfileParser().issRGB(iccProfile); |
| if (!is_srgb) { |
| final ICC_ColorSpace cs = new ICC_ColorSpace(iccProfile); |
| |
| final ColorModel srgbCM = ColorModel.getRGBdefault(); |
| final ColorSpace cs_sRGB = srgbCM.getColorSpace(); |
| |
| result = new ColorTools().convertBetweenColorSpaces(result, cs, cs_sRGB); |
| } |
| } |
| |
| return result; |
| |
| } |
| |
| } |
| |
| /** |
| * @param is PNG image input stream |
| * @return List of String-formatted chunk types, ie. "tRNs". |
| * @throws ImagingException if it fail to read the PNG chunks |
| * @throws IOException if it fails to read the input stream data |
| */ |
| public List<String> getChunkTypes(final InputStream is) |
| throws ImagingException, IOException { |
| final List<PngChunk> chunks = readChunks(is, null, false); |
| final List<String> chunkTypes = Allocator.arrayList(chunks.size()); |
| for (final PngChunk chunk : chunks) { |
| chunkTypes.add(getChunkTypeName(chunk.getChunkType())); |
| } |
| return chunkTypes; |
| } |
| |
| @Override |
| public String getDefaultExtension() { |
| return DEFAULT_EXTENSION; |
| } |
| |
| @Override |
| public PngImagingParameters getDefaultParameters() { |
| return new PngImagingParameters(); |
| } |
| |
| @Override |
| public byte[] getICCProfileBytes(final ByteSource byteSource, final PngImagingParameters params) |
| throws ImagingException, IOException { |
| final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.iCCP }, |
| true); |
| |
| if (chunks.isEmpty()) { |
| return null; |
| } |
| |
| if (chunks.size() > 1) { |
| throw new ImagingException( |
| "PNG contains more than one ICC Profile "); |
| } |
| |
| final PngChunkIccp pngChunkiCCP = (PngChunkIccp) chunks.get(0); |
| |
| return (pngChunkiCCP.getUncompressedProfile());// TODO should this be a clone? |
| } |
| |
| @Override |
| public ImageInfo getImageInfo(final ByteSource byteSource, final PngImagingParameters params) |
| throws ImagingException, IOException { |
| final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { |
| ChunkType.IHDR, |
| ChunkType.pHYs, |
| ChunkType.sCAL, |
| ChunkType.tEXt, |
| ChunkType.zTXt, |
| ChunkType.tRNS, |
| ChunkType.PLTE, |
| ChunkType.iTXt, |
| }, false); |
| |
| if (chunks.isEmpty()) { |
| throw new ImagingException("PNG: no chunks"); |
| } |
| |
| final List<PngChunk> IHDRs = filterChunks(chunks, ChunkType.IHDR); |
| if (IHDRs.size() != 1) { |
| throw new ImagingException("PNG contains more than one Header"); |
| } |
| |
| final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0); |
| |
| boolean transparent = false; |
| |
| final List<PngChunk> tRNSs = filterChunks(chunks, ChunkType.tRNS); |
| if (!tRNSs.isEmpty()) { |
| transparent = true; |
| } else { |
| // CE - Fix Alpha. |
| transparent = pngChunkIHDR.getPngColorType().hasAlpha(); |
| // END FIX |
| } |
| |
| PngChunkPhys pngChunkpHYs = null; |
| |
| final List<PngChunk> pHYss = filterChunks(chunks, ChunkType.pHYs); |
| if (pHYss.size() > 1) { |
| throw new ImagingException("PNG contains more than one pHYs: " |
| + pHYss.size()); |
| } |
| if (pHYss.size() == 1) { |
| pngChunkpHYs = (PngChunkPhys) pHYss.get(0); |
| } |
| |
| PhysicalScale physicalScale = PhysicalScale.UNDEFINED; |
| |
| final List<PngChunk> sCALs = filterChunks(chunks, ChunkType.sCAL); |
| if (sCALs.size() > 1) { |
| throw new ImagingException("PNG contains more than one sCAL:" |
| + sCALs.size()); |
| } |
| if (sCALs.size() == 1) { |
| final PngChunkScal pngChunkScal = (PngChunkScal) sCALs.get(0); |
| if (pngChunkScal.getUnitSpecifier() == 1) { |
| physicalScale = PhysicalScale.createFromMeters(pngChunkScal.getUnitsPerPixelXAxis(), |
| pngChunkScal.getUnitsPerPixelYAxis()); |
| } else { |
| physicalScale = PhysicalScale.createFromRadians(pngChunkScal.getUnitsPerPixelXAxis(), |
| pngChunkScal.getUnitsPerPixelYAxis()); |
| } |
| } |
| |
| final List<PngChunk> tEXts = filterChunks(chunks, ChunkType.tEXt); |
| final List<PngChunk> zTXts = filterChunks(chunks, ChunkType.zTXt); |
| final List<PngChunk> iTXts = filterChunks(chunks, ChunkType.iTXt); |
| |
| final int chunkCount = tEXts.size() + zTXts.size() + iTXts.size(); |
| final List<String> comments = Allocator.arrayList(chunkCount); |
| final List<PngText> textChunks = Allocator.arrayList(chunkCount); |
| |
| for (final PngChunk tEXt : tEXts) { |
| final PngChunkText pngChunktEXt = (PngChunkText) tEXt; |
| comments.add(pngChunktEXt.getKeyword() + ": " + pngChunktEXt.getText()); |
| textChunks.add(pngChunktEXt.getContents()); |
| } |
| for (final PngChunk zTXt : zTXts) { |
| final PngChunkZtxt pngChunkzTXt = (PngChunkZtxt) zTXt; |
| comments.add(pngChunkzTXt.getKeyword() + ": " + pngChunkzTXt.getText()); |
| textChunks.add(pngChunkzTXt.getContents()); |
| } |
| for (final PngChunk iTXt : iTXts) { |
| final PngChunkItxt pngChunkiTXt = (PngChunkItxt) iTXt; |
| comments.add(pngChunkiTXt.getKeyword() + ": " + pngChunkiTXt.getText()); |
| textChunks.add(pngChunkiTXt.getContents()); |
| } |
| |
| final int bitsPerPixel = pngChunkIHDR.getBitDepth() * pngChunkIHDR.getPngColorType().getSamplesPerPixel(); |
| final ImageFormat format = ImageFormats.PNG; |
| final String formatName = "PNG Portable Network Graphics"; |
| final int height = pngChunkIHDR.getHeight(); |
| final String mimeType = "image/png"; |
| final int numberOfImages = 1; |
| final int width = pngChunkIHDR.getWidth(); |
| final boolean progressive = pngChunkIHDR.getInterlaceMethod().isProgressive(); |
| |
| 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.getUnitSpecifier() == 1)) { // meters |
| final double metersPerInch = 0.0254; |
| |
| physicalWidthDpi = (int) Math.round(pngChunkpHYs.getPixelsPerUnitXAxis() * metersPerInch); |
| physicalWidthInch = (float) (width / (pngChunkpHYs.getPixelsPerUnitXAxis() * metersPerInch)); |
| physicalHeightDpi = (int) Math.round(pngChunkpHYs.getPixelsPerUnitYAxis() * metersPerInch); |
| physicalHeightInch = (float) (height / (pngChunkpHYs.getPixelsPerUnitYAxis() * metersPerInch)); |
| } |
| |
| boolean usesPalette = false; |
| |
| final List<PngChunk> PLTEs = filterChunks(chunks, ChunkType.PLTE); |
| if (!PLTEs.isEmpty()) { |
| usesPalette = true; |
| } |
| |
| ImageInfo.ColorType colorType; |
| switch (pngChunkIHDR.getPngColorType()) { |
| case GREYSCALE: |
| case GREYSCALE_WITH_ALPHA: |
| colorType = ImageInfo.ColorType.GRAYSCALE; |
| break; |
| case TRUE_COLOR: |
| case INDEXED_COLOR: |
| case TRUE_COLOR_WITH_ALPHA: |
| colorType = ImageInfo.ColorType.RGB; |
| break; |
| default: |
| throw new ImagingException("Png: Unknown ColorType: " + pngChunkIHDR.getPngColorType()); |
| } |
| |
| final String formatDetails = "Png"; |
| final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.PNG_FILTER; |
| |
| return new PngImageInfo(formatDetails, bitsPerPixel, comments, |
| format, formatName, height, mimeType, numberOfImages, |
| physicalHeightDpi, physicalHeightInch, physicalWidthDpi, |
| physicalWidthInch, width, progressive, transparent, |
| usesPalette, colorType, compressionAlgorithm, textChunks, |
| physicalScale); |
| } |
| |
| @Override |
| public Dimension getImageSize(final ByteSource byteSource, final PngImagingParameters params) |
| throws ImagingException, IOException { |
| final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.IHDR, }, true); |
| |
| if (chunks.isEmpty()) { |
| throw new ImagingException("Png: No chunks"); |
| } |
| |
| if (chunks.size() > 1) { |
| throw new ImagingException("PNG contains more than one Header"); |
| } |
| |
| final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) chunks.get(0); |
| |
| return new Dimension(pngChunkIHDR.getWidth(), pngChunkIHDR.getHeight()); |
| } |
| |
| @Override |
| public ImageMetadata getMetadata(final ByteSource byteSource, final PngImagingParameters params) |
| throws ImagingException, IOException { |
| final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.tEXt, ChunkType.zTXt, ChunkType.iTXt }, false); |
| |
| if (chunks.isEmpty()) { |
| return null; |
| } |
| |
| final GenericImageMetadata result = new GenericImageMetadata(); |
| |
| for (final PngChunk chunk : chunks) { |
| final PngTextChunk textChunk = (PngTextChunk) chunk; |
| |
| result.add(textChunk.getKeyword(), textChunk.getText()); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public String getName() { |
| return "Png-Custom"; |
| } |
| |
| private TransparencyFilter getTransparencyFilter(final PngColorType pngColorType, final PngChunk pngChunktRNS) |
| throws ImagingException, IOException { |
| switch (pngColorType) { |
| case GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale sample. |
| return new TransparencyFilterGrayscale(pngChunktRNS.getBytes()); |
| case TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple. |
| return new TransparencyFilterTrueColor(pngChunktRNS.getBytes()); |
| case INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index; |
| return new TransparencyFilterIndexedColor(pngChunktRNS.getBytes()); |
| case GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a grayscale sample, |
| case TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an R,G,B triple, |
| default: |
| throw new ImagingException("Simple Transparency not compatible with ColorType: " + pngColorType); |
| } |
| } |
| |
| @Override |
| public String getXmpXml(final ByteSource byteSource, final XmpImagingParameters<PngImagingParameters> params) |
| throws ImagingException, IOException { |
| |
| final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.iTXt }, false); |
| |
| if (chunks.isEmpty()) { |
| return null; |
| } |
| |
| final List<PngChunkItxt> xmpChunks = new ArrayList<>(); |
| for (final PngChunk chunk : chunks) { |
| final PngChunkItxt itxtChunk = (PngChunkItxt) chunk; |
| if (!itxtChunk.getKeyword().equals(PngConstants.XMP_KEYWORD)) { |
| continue; |
| } |
| xmpChunks.add(itxtChunk); |
| } |
| |
| if (xmpChunks.isEmpty()) { |
| return null; |
| } |
| if (xmpChunks.size() > 1) { |
| throw new ImagingException( |
| "PNG contains more than one XMP chunk."); |
| } |
| |
| final PngChunkItxt chunk = xmpChunks.get(0); |
| return chunk.getText(); |
| } |
| |
| // TODO: I have been too casual about making inner classes subclass of |
| // BinaryFileParser |
| // I may not have always preserved byte order correctly. |
| |
| public boolean hasChunkType(final ByteSource byteSource, final ChunkType chunkType) |
| throws ImagingException, IOException { |
| try (InputStream is = byteSource.getInputStream()) { |
| readSignature(is); |
| final List<PngChunk> chunks = readChunks(is, new ChunkType[] { chunkType }, true); |
| return !chunks.isEmpty(); |
| } |
| } |
| |
| private boolean keepChunk(final int chunkType, final ChunkType[] chunkTypes) { |
| // System.out.println("keepChunk: "); |
| if (chunkTypes == null) { |
| return true; |
| } |
| |
| for (final ChunkType chunkType2 : chunkTypes) { |
| if (chunkType2.value == chunkType) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private List<PngChunk> readChunks(final ByteSource byteSource, final ChunkType[] chunkTypes, |
| final boolean returnAfterFirst) throws ImagingException, IOException { |
| try (InputStream is = byteSource.getInputStream()) { |
| readSignature(is); |
| return readChunks(is, chunkTypes, returnAfterFirst); |
| } |
| } |
| |
| private List<PngChunk> readChunks(final InputStream is, final ChunkType[] chunkTypes, |
| final boolean returnAfterFirst) throws ImagingException, IOException { |
| final List<PngChunk> result = new ArrayList<>(); |
| |
| while (true) { |
| final int length = read4Bytes("Length", is, "Not a Valid PNG File", getByteOrder()); |
| if (length < 0) { |
| throw new ImagingException("Invalid PNG chunk length: " + length); |
| } |
| final int chunkType = read4Bytes("ChunkType", is, "Not a Valid PNG File", getByteOrder()); |
| |
| if (LOGGER.isLoggable(Level.FINEST)) { |
| 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 (LOGGER.isLoggable(Level.FINEST)) { |
| if (bytes != null) { |
| debugNumber("bytes", bytes.length, 4); |
| } |
| } |
| |
| final int crc = read4Bytes("CRC", is, "Not a Valid PNG File", getByteOrder()); |
| |
| if (keep) { |
| if (chunkType == ChunkType.iCCP.value) { |
| result.add(new PngChunkIccp(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.tEXt.value) { |
| result.add(new PngChunkText(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.zTXt.value) { |
| result.add(new PngChunkZtxt(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.IHDR.value) { |
| result.add(new PngChunkIhdr(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.PLTE.value) { |
| result.add(new PngChunkPlte(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.pHYs.value) { |
| result.add(new PngChunkPhys(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.sCAL.value) { |
| result.add(new PngChunkScal(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.IDAT.value) { |
| result.add(new PngChunkIdat(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.gAMA.value) { |
| result.add(new PngChunkGama(length, chunkType, crc, bytes)); |
| } else if (chunkType == ChunkType.iTXt.value) { |
| result.add(new PngChunkItxt(length, chunkType, crc, bytes)); |
| } else { |
| result.add(new PngChunk(length, chunkType, crc, bytes)); |
| } |
| |
| if (returnAfterFirst) { |
| return result; |
| } |
| } |
| |
| if (chunkType == ChunkType.IEND.value) { |
| break; |
| } |
| |
| } |
| |
| return result; |
| |
| } |
| |
| public void readSignature(final InputStream is) throws ImagingException, |
| IOException { |
| readAndVerifyBytes(is, PngConstants.PNG_SIGNATURE, |
| "Not a Valid PNG Segment: Incorrect Signature"); |
| |
| } |
| |
| @Override |
| public void writeImage(final BufferedImage src, final OutputStream os, final PngImagingParameters params) |
| throws ImagingException, IOException { |
| new PngWriter().writeImage(src, os, params, null); |
| } |
| |
| } |