You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2019/10/12 17:55:06 UTC

svn commit: r1868357 - in /pdfbox/branches/2.0/pdfbox/src: main/java/org/apache/pdfbox/pdmodel/graphics/image/ test/java/org/apache/pdfbox/pdmodel/graphics/image/ test/resources/org/apache/pdfbox/pdmodel/graphics/image/

Author: tilman
Date: Sat Oct 12 17:55:05 2019
New Revision: 1868357

URL: http://svn.apache.org/viewvc?rev=1868357&view=rev
Log:
PDFBOX-4341: add a PNG to PDImageXObject converter, by Emmeran Seehuber

Added:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverter.java   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverterTest.java   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_gray.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_rgb.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray_with_gama.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_1bit_alpha.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_2bit_alpha.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_4bit_alpha.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_8bit_alpha.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_gamma.png   (with props)
    pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_romm_16bit.png   (with props)
Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/LosslessFactory.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/LosslessFactory.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/LosslessFactory.java?rev=1868357&r1=1868356&r2=1868357&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/LosslessFactory.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/LosslessFactory.java Sat Oct 12 17:55:05 2019
@@ -228,7 +228,7 @@ public final class LosslessFactory
      * @return the newly created PDImageXObject with the data compressed.
      * @throws IOException 
      */
-    private static PDImageXObject prepareImageXObject(PDDocument document, 
+    static PDImageXObject prepareImageXObject(PDDocument document,
             byte [] byteArray, int width, int height, int bitsPerComponent, 
             PDColorSpace initColorSpace) throws IOException
     {

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java?rev=1868357&r1=1868356&r2=1868357&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java Sat Oct 12 17:55:05 2019
@@ -325,7 +325,9 @@ public final class PDImageXObject extend
      * This is a convenience method that calls {@link JPEGFactory#createFromByteArray},
      * {@link CCITTFactory#createFromFile} or {@link ImageIO#read} combined with
      * {@link LosslessFactory#createFromImage}. (The later can also be used to create a
-     * PDImageXObject from a BufferedImage).
+     * PDImageXObject from a BufferedImage). Since 2.0.18, this call can also create an image
+     * directly from a PNG file without decoding it, which is faster. However the result size
+     * depends on the compression skill of the software that created the PNG file.
      *
      * @param byteArray bytes from an image file.
      * @param document the document that shall use this PDImageXObject.
@@ -355,6 +357,15 @@ public final class PDImageXObject extend
         {
             return JPEGFactory.createFromByteArray(document, byteArray);
         }
+        if (fileType.equals(FileType.PNG))
+        {
+            // Try to directly convert the image without recoding it.
+            PDImageXObject image = PNGConverter.convertPNGImage(document, byteArray);
+            if (image != null)
+            {
+                return image;
+            }
+        }
         if (fileType.equals(FileType.TIFF))
         {
             try
@@ -757,6 +768,8 @@ public final class PDImageXObject extend
     public void setColorSpace(PDColorSpace cs)
     {
         getCOSObject().setItem(COSName.COLORSPACE, cs != null ? cs.getCOSObject() : null);
+        colorSpace = null;
+        cachedImage = null;
     }
 
     @Override

Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverter.java?rev=1868357&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverter.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverter.java Sat Oct 12 17:55:05 2019
@@ -0,0 +1,931 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.image;
+
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterFactory;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDIndexed;
+
+/**
+ * This factory tries to encode a PNG given as byte array into a PDImageXObject
+ * by directly coping the image data into the PDF streams without
+ * decoding/encoding and re-compressing the PNG data.
+ * <p>
+ * If this is for any reason not possible, the factory will return null. You
+ * must then encode the image by loading it and using the LosslessFactory.
+ * <p>
+ * The W3C PNG spec was used to implement this class:
+ * https://www.w3.org/TR/2003/REC-PNG-20031110
+ *
+ * @author Emmeran Seehuber
+ */
+final class PNGConverter
+{
+    private static final Log LOG = LogFactory.getLog(PNGConverter.class);
+
+    private PNGConverter()
+    {
+    }
+
+    /**
+     * Try to convert a PNG into a PDImageXObject. If for any reason the PNG can not
+     * be converted, null is returned.
+     * <p>
+     * This usually means the PNG structure is damaged (CRC error, etc.) or it uses
+     * some features which can not be mapped to PDF.
+     *
+     * @param doc       the document to put the image in
+     * @param imageData the byte data of the PNG
+     * @return null or the PDImageXObject built from the png
+     */
+    static PDImageXObject convertPNGImage(PDDocument doc, byte[] imageData) throws IOException
+    {
+        PNGConverterState state = parsePNGChunks(imageData);
+        if (!checkConverterState(state))
+        {
+            // There is something wrong, we can't convert this PNG
+            return null;
+        }
+
+        return convertPng(doc, state);
+    }
+
+    /**
+     * Convert the image using the state.
+     *
+     * @param doc   the document to put the image in
+     * @param state the parser state containing the PNG chunks.
+     * @return null or the converted image
+     */
+    private static PDImageXObject convertPng(PDDocument doc, PNGConverterState state)
+            throws IOException
+    {
+        Chunk ihdr = state.IHDR;
+        int ihdrStart = ihdr.start;
+        int width = readInt(ihdr.bytes, ihdrStart);
+        int height = readInt(ihdr.bytes, ihdrStart + 4);
+        int bitDepth = ihdr.bytes[ihdrStart + 8] & 0xFF;
+        int colorType = ihdr.bytes[ihdrStart + 9] & 0xFF;
+        int compressionMethod = ihdr.bytes[ihdrStart + 10] & 0xFF;
+        int filterMethod = ihdr.bytes[ihdrStart + 11] & 0xFF;
+        int interlaceMethod = ihdr.bytes[ihdrStart + 12] & 0xFF;
+
+        if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16)
+        {
+            LOG.error(String.format("Invalid bit depth %d.", bitDepth));
+            return null;
+        }
+        if (width <= 0 || height <= 0)
+        {
+            LOG.error(String.format("Invalid image size %d x %d", width, height));
+            return null;
+        }
+        if (compressionMethod != 0)
+        {
+            LOG.error(String.format("Unknown PNG compression method %d.", compressionMethod));
+            return null;
+        }
+        if (filterMethod != 0)
+        {
+            LOG.error(String.format("Unknown PNG filtering method %d.", compressionMethod));
+            return null;
+        }
+        if (interlaceMethod != 0)
+        {
+            LOG.debug(String.format("Can't handle interlace method %d.", interlaceMethod));
+            return null;
+        }
+
+        state.width = width;
+        state.height = height;
+        state.bitsPerComponent = bitDepth;
+
+        switch (colorType)
+        {
+        case 0:
+            // Grayscale
+            LOG.debug("Can't handle grayscale yet.");
+            return null;
+        case 2:
+            // Truecolor
+            if (state.tRNS != null)
+            {
+                LOG.debug("Can't handle images with transparent colors.");
+                return null;
+            }
+            return buildImageObject(doc, state);
+        case 3:
+            // Indexed image
+            return buildIndexImage(doc, state);
+        case 4:
+            // Grayscale with alpha.
+            LOG.debug(
+                    "Can't handle grayscale with alpha, would need to separate alpha from image data");
+            return null;
+        case 6:
+            // Truecolor with alpha.
+            LOG.debug(
+                    "Can't handle truecolor with alpha, would need to separate alpha from image data");
+            return null;
+        default:
+            LOG.error("Unknown PNG color type " + colorType);
+            return null;
+        }
+    }
+
+    /**
+     * Build a indexed image
+     */
+    private static PDImageXObject buildIndexImage(PDDocument doc, PNGConverterState state)
+            throws IOException
+    {
+        Chunk plte = state.PLTE;
+        if (plte == null)
+        {
+            LOG.error("Indexed image without PLTE chunk.");
+            return null;
+        }
+        if (plte.length % 3 != 0)
+        {
+            LOG.error("PLTE table corrupted, last (r,g,b) tuple is not complete.");
+            return null;
+        }
+        if (state.bitsPerComponent > 8)
+        {
+            LOG.debug(String.format("Can only convert indexed images with bit depth <= 8, not %d.",
+                    state.bitsPerComponent));
+            return null;
+        }
+
+        PDImageXObject image = buildImageObject(doc, state);
+        if (image == null)
+        {
+            return null;
+        }
+
+        int highVal = (plte.length / 3) - 1;
+        if (highVal > 255)
+        {
+            LOG.error(String.format("To much colors in PLTE, only 256 allowed, found %d colors.",
+                    highVal + 1));
+            return null;
+        }
+
+        setupIndexedColorSpace(doc, plte, image, highVal);
+
+        if (state.tRNS != null)
+        {
+            image.getCOSObject().setItem(COSName.SMASK,
+                    buildTransparencyMaskFromIndexedData(doc, image, state));
+        }
+
+        return image;
+    }
+
+    private static PDImageXObject buildTransparencyMaskFromIndexedData(PDDocument doc,
+            PDImageXObject image, PNGConverterState state) throws IOException
+    {
+        Filter flateDecode = FilterFactory.INSTANCE.getFilter(COSName.FLATE_DECODE);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        COSDictionary decodeParams = buildDecodeParams(state, PDDeviceGray.INSTANCE);
+        COSDictionary imageDict = new COSDictionary();
+        imageDict.setItem(COSName.FILTER, COSName.FLATE_DECODE);
+        imageDict.setItem(COSName.DECODE_PARMS, decodeParams);
+        flateDecode.decode(getIDATInputStream(state), outputStream, imageDict, 0);
+        int length = image.getWidth() * image.getHeight();
+        byte[] bytes = new byte[length];
+        byte[] transparencyTable = state.tRNS.getData();
+        byte[] decodedIDAT = outputStream.toByteArray();
+        ImageInputStream iis = new MemoryCacheImageInputStream(
+                new ByteArrayInputStream(decodedIDAT));
+        try
+        {
+            int bitsPerComponent = state.bitsPerComponent;
+            int w = 0;
+            int neededBits = bitsPerComponent * state.width;
+            int bitPadding = neededBits % 8;
+            for (int i = 0; i < bytes.length; i++)
+            {
+                int idx = (int) iis.readBits(bitsPerComponent);
+                byte v;
+                if (idx < transparencyTable.length)
+                {
+                    // Inside the table, use the transparency value
+                    v = transparencyTable[idx];
+                }
+                else
+                {
+                    // Outside the table -> transparent value is 0xFF here.
+                    v = (byte) 0xFF;
+                }
+                bytes[i] = v;
+                w++;
+                if (w == state.width)
+                {
+                    w = 0;
+                    iis.readBits(bitPadding);
+                }
+            }
+        }
+        finally
+        {
+            iis.close();
+        }
+        return LosslessFactory
+                .prepareImageXObject(doc, bytes, image.getWidth(), image.getHeight(), 8,
+                        PDDeviceGray.INSTANCE);
+    }
+
+    private static void setupIndexedColorSpace(PDDocument doc, Chunk lookupTable,
+            PDImageXObject image, int highVal) throws IOException
+    {
+        COSArray indexedArray = new COSArray();
+        indexedArray.add(COSName.INDEXED);
+        indexedArray.add(image.getColorSpace());
+        ((COSDictionary) image.getCOSObject().getItem(COSName.DECODE_PARMS))
+                .setItem(COSName.COLORS, COSInteger.ONE);
+
+        indexedArray.add(COSInteger.get(highVal));
+
+        PDStream colorTable = new PDStream(doc);
+        OutputStream colorTableStream = colorTable.createOutputStream(COSName.FLATE_DECODE);
+        try
+        {
+            colorTableStream.write(lookupTable.bytes, lookupTable.start, lookupTable.length);
+        }
+        finally
+        {
+            colorTableStream.close();
+        }
+        indexedArray.add(colorTable);
+
+        PDIndexed indexed = new PDIndexed(indexedArray);
+        image.setColorSpace(indexed);
+    }
+
+    /**
+     * Build the base image object from the IDATs and profile information
+     */
+    private static PDImageXObject buildImageObject(PDDocument document, PNGConverterState state)
+            throws IOException
+    {
+        InputStream encodedByteStream = getIDATInputStream(state);
+
+        PDColorSpace colorSpace = PDDeviceRGB.INSTANCE;
+
+        PDImageXObject imageXObject = new PDImageXObject(document, encodedByteStream,
+                COSName.FLATE_DECODE, state.width, state.height, state.bitsPerComponent,
+                colorSpace);
+
+        COSDictionary decodeParams = buildDecodeParams(state, colorSpace);
+        imageXObject.getCOSObject().setItem(COSName.DECODE_PARMS, decodeParams);
+
+        // We ignore gAMA and cHRM chunks if we have a ICC profile, as the ICC profile
+        // takes preference
+        boolean hasICCColorProfile = state.sRGB != null || state.iCCP != null;
+
+        if (state.gAMA != null && !hasICCColorProfile)
+        {
+            if (state.gAMA.length != 4)
+            {
+                LOG.error("Invalid gAMA chunk length " + state.gAMA.length);
+                return null;
+            }
+            float gamma = readPNGFloat(state.gAMA.bytes, state.gAMA.start);
+            // If the gamma is 2.2 for sRGB everything is fine. Otherwise bail out.
+            // The gamma is stored as 1 / gamma.
+            if (Math.abs(gamma - (1 / 2.2f)) > 0.00001)
+            {
+                LOG.debug(String.format("We can't handle gamma of %f yet.", gamma));
+                return null;
+            }
+        }
+
+        if (state.sRGB != null)
+        {
+            if (state.sRGB.length != 1)
+            {
+                LOG.error(
+                        String.format("sRGB chunk has an invalid length of %d", state.sRGB.length));
+                return null;
+            }
+
+            // Store the specified rendering intent
+            int renderIntent = state.sRGB.bytes[state.sRGB.start];
+            COSName value = mapPNGRenderIntent(renderIntent);
+            imageXObject.getCOSObject().setItem(COSName.INTENT, value);
+        }
+
+        if (state.cHRM != null && !hasICCColorProfile)
+        {
+            if (state.cHRM.length != 32)
+            {
+                LOG.error("Invalid cHRM chunk length " + state.cHRM.length);
+                return null;
+            }
+            LOG.debug("We can not handle cHRM chunks yet.");
+            return null;
+        }
+
+        // If possible we prefer a ICCBased color profile, just because its way faster
+        // to decode ...
+        if (state.iCCP != null || state.sRGB != null)
+        {
+            // We have got a color profile, which we must attach
+            PDICCBased profile = new PDICCBased(document);
+            COSStream cosStream = profile.getPDStream().getCOSObject();
+            cosStream.setInt(COSName.N, colorSpace.getNumberOfComponents());
+            cosStream.setItem(COSName.ALTERNATE, colorSpace.getNumberOfComponents()
+                    == 1 ? COSName.DEVICEGRAY : COSName.DEVICERGB);
+            if (state.iCCP != null)
+            {
+                // We need to skip over the name
+                int iccProfileDataStart = 0;
+                while (iccProfileDataStart < 80 && iccProfileDataStart < state.iCCP.length)
+                {
+                    if (state.iCCP.bytes[state.iCCP.start + iccProfileDataStart] == 0)
+                        break;
+                    iccProfileDataStart++;
+                }
+                if (iccProfileDataStart >= state.iCCP.length)
+                {
+                    LOG.error("Invalid iCCP chunk, to few bytes");
+                    return null;
+                }
+                byte compressionMethod = state.iCCP.bytes[state.iCCP.start + iccProfileDataStart];
+                if (compressionMethod != 0)
+                {
+                    LOG.error(String.format("iCCP chunk: invalid compression method %d",
+                            compressionMethod));
+                    return null;
+                }
+                // Skip over the compression method
+                iccProfileDataStart++;
+
+                OutputStream rawOutputStream = cosStream.createRawOutputStream();
+                try
+                {
+                    rawOutputStream.write(state.iCCP.bytes, state.iCCP.start + iccProfileDataStart,
+                            state.iCCP.length - iccProfileDataStart);
+                }
+                finally
+                {
+                    rawOutputStream.close();
+                }
+            }
+            else
+            {
+                // We tag the image with the sRGB profile
+                ICC_Profile rgbProfile = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
+                OutputStream outputStream = cosStream.createRawOutputStream();
+                try
+                {
+                    outputStream.write(rgbProfile.getData());
+                }
+                finally
+                {
+                    outputStream.close();
+                }
+            }
+
+            imageXObject.setColorSpace(profile);
+        }
+        return imageXObject;
+    }
+
+    private static COSDictionary buildDecodeParams(PNGConverterState state, PDColorSpace colorSpace)
+    {
+        COSDictionary decodeParms = new COSDictionary();
+        decodeParms.setItem(COSName.BITS_PER_COMPONENT, COSInteger.get(state.bitsPerComponent));
+        decodeParms.setItem(COSName.PREDICTOR, COSInteger.get(15));
+        decodeParms.setItem(COSName.COLUMNS, COSInteger.get(state.width));
+        decodeParms.setItem(COSName.COLORS, COSInteger.get(colorSpace.getNumberOfComponents()));
+        return decodeParms;
+    }
+
+    /**
+     * Build an input stream for the IDAT data. May need to concat multiple IDAT
+     * chunks.
+     *
+     * @param state the converter state.
+     * @return a input stream with the IDAT data.
+     */
+    private static InputStream getIDATInputStream(PNGConverterState state)
+    {
+        MultipleInputStream inputStream = new MultipleInputStream();
+        for (Chunk idat : state.IDATs)
+        {
+            inputStream.inputStreams
+                    .add(new ByteArrayInputStream(idat.bytes, idat.start, idat.length));
+        }
+        return inputStream;
+    }
+
+    private static class MultipleInputStream extends InputStream
+    {
+
+        List<InputStream> inputStreams = new ArrayList<InputStream>();
+        int currentStreamIdx;
+        InputStream currentStream;
+
+        private boolean ensureStream()
+        {
+            if (currentStream == null)
+            {
+                if (currentStreamIdx >= inputStreams.size())
+                {
+                    return false;
+                }
+                currentStream = inputStreams.get(currentStreamIdx++);
+            }
+            return true;
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            throw new IllegalStateException("Only bulk reads are expected!");
+        }
+
+        @Override
+        public int available() throws IOException
+        {
+            if (!ensureStream())
+            {
+                return 0;
+            }
+            return 1;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException
+        {
+            if (!ensureStream())
+            {
+                return -1;
+            }
+            int ret = currentStream.read(b, off, len);
+            if (ret == -1)
+            {
+                currentStream = null;
+                return read(b, off, len);
+            }
+            return ret;
+        }
+    }
+
+    /**
+     * Map the renderIntent int to a PDF render intent. See also
+     * https://www.w3.org/TR/2003/REC-PNG-20031110/#11sRGB
+     *
+     * @param renderIntent the PNG render intent
+     * @return the matching PDF Render Intent or null
+     */
+    static COSName mapPNGRenderIntent(int renderIntent)
+    {
+        COSName value;
+        switch (renderIntent)
+        {
+        case 0:
+            value = COSName.PERCEPTUAL;
+            break;
+        case 1:
+            value = COSName.RELATIVE_COLORIMETRIC;
+            break;
+        case 2:
+            value = COSName.SATURATION;
+            break;
+        case 3:
+            value = COSName.ABSOLUTE_COLORIMETRIC;
+            break;
+        default:
+            value = null;
+            break;
+        }
+        return value;
+    }
+
+    /**
+     * Check if the converter state is sane.
+     *
+     * @param state the parsed converter state
+     * @return true if the state seems plausible
+     */
+    static boolean checkConverterState(PNGConverterState state)
+    {
+        if (state == null)
+        {
+            return false;
+        }
+        if (state.IHDR == null || !checkChunkSane(state.IHDR))
+        {
+            LOG.error("Invalid IHDR chunk.");
+            return false;
+        }
+        if (!checkChunkSane(state.PLTE))
+        {
+            LOG.error("Invalid PLTE chunk.");
+            return false;
+        }
+        if (!checkChunkSane(state.iCCP))
+        {
+            LOG.error("Invalid iCCP chunk.");
+            return false;
+        }
+        if (!checkChunkSane(state.tRNS))
+        {
+            LOG.error("Invalid tRNS chunk.");
+            return false;
+        }
+        if (!checkChunkSane(state.sRGB))
+        {
+            LOG.error("Invalid sRGB chunk.");
+            return false;
+        }
+        if (!checkChunkSane(state.cHRM))
+        {
+            LOG.error("Invalid cHRM chunk.");
+            return false;
+        }
+        if (!checkChunkSane(state.gAMA))
+        {
+            LOG.error("Invalid gAMA chunk.");
+            return false;
+        }
+
+        // Check the IDATs
+        if (state.IDATs.size() == 0)
+        {
+            LOG.error("No IDAT chunks.");
+            return false;
+        }
+        for (Chunk idat : state.IDATs)
+        {
+            if (!checkChunkSane(idat))
+            {
+                LOG.error("Invalid IDAT chunk.");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check if the chunk is sane, i.e. CRC matches and offsets and lengths in the
+     * byte array
+     */
+    static boolean checkChunkSane(Chunk chunk)
+    {
+        if (chunk == null)
+        {
+            // If the chunk does not exist, it can not be wrong...
+            return true;
+        }
+
+        if (chunk.start + chunk.length > chunk.bytes.length)
+        {
+            return false;
+        }
+
+        if (chunk.start < 4)
+        {
+            return false;
+        }
+
+        // We must include the chunk type in the CRC calculation
+        int ourCRC = crc(chunk.bytes, chunk.start - 4, chunk.length + 4);
+        if (ourCRC != chunk.crc)
+        {
+            LOG.error(String.format("Invalid CRC %08X on chunk %08X, expected %08X.", ourCRC,
+                    chunk.chunkType, chunk.crc));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Holds the information about a chunks
+     */
+    static final class Chunk
+    {
+        /**
+         * This field holds the whole byte array; In that it's redundant, as all chunks
+         * will have the same byte array. But have this byte array per chunk makes it
+         * easier to validate and pass around. And we won't have that many chunks, so
+         * those 8 bytes for the pointer (on 64-bit systems) don't matter.
+         */
+        byte[] bytes;
+        /**
+         * The chunk type, see the CHUNK_??? constants.
+         */
+        int chunkType;
+        /**
+         * The crc of the chunk data, as stored in the PNG stream.
+         */
+        int crc;
+        /**
+         * The start index of the chunk data within bytes.
+         */
+        int start;
+        /**
+         * The length of the data within the byte array.
+         */
+        int length;
+
+        /**
+         * Get the data of this chunk as a byte array
+         *
+         * @return a byte-array with only the data of the chunk
+         */
+        byte[] getData()
+        {
+            return Arrays.copyOfRange(bytes, start, start + length);
+        }
+    }
+
+    /**
+     * Holds all relevant chunks of the PNG
+     */
+    static final class PNGConverterState
+    {
+        List<Chunk> IDATs = new ArrayList<Chunk>();
+        @SuppressWarnings("SpellCheckingInspection") Chunk IHDR;
+        @SuppressWarnings("SpellCheckingInspection") Chunk PLTE;
+        Chunk iCCP;
+        Chunk tRNS;
+        Chunk sRGB;
+        Chunk gAMA;
+        Chunk cHRM;
+
+        // Parsed header fields
+        int width;
+        int height;
+        int bitsPerComponent;
+    }
+
+    private static int readInt(byte[] data, int offset)
+    {
+        int b1 = (data[offset] & 0xFF) << 24;
+        int b2 = (data[offset + 1] & 0xFF) << 16;
+        int b3 = (data[offset + 2] & 0xFF) << 8;
+        int b4 = (data[offset + 3] & 0xFF);
+        return b1 | b2 | b3 | b4;
+    }
+
+    private static float readPNGFloat(byte[] bytes, int offset)
+    {
+        int v = readInt(bytes, offset);
+        return v / 100000f;
+    }
+
+    // Chunk Type definitions. The bytes in the comments are the bytes in the spec.
+    private static final int CHUNK_IHDR = 0x49484452; // IHDR: 73 72 68 82
+    private static final int CHUNK_IDAT = 0x49444154; // IDAT: 73 68 65 84
+    private static final int CHUNK_PLTE = 0x504C5445; // PLTE: 80 76 84 69
+    private static final int CHUNK_IEND = 0x49454E44; // IEND: 73 69 78 68
+    private static final int CHUNK_TRNS = 0x74524E53; // tRNS: 116 82 78 83
+    private static final int CHUNK_CHRM = 0x6348524D; // cHRM: 99 72 82 77
+    private static final int CHUNK_GAMA = 0x67414D41; // gAMA: 103 65 77 65
+    private static final int CHUNK_ICCP = 0x69434350; // iCCP: 105 67 67 80
+    private static final int CHUNK_SBIT = 0x73424954; // sBIT: 115 66 73 84
+    private static final int CHUNK_SRGB = 0x73524742; // sRGB: 115 82 71 66
+    private static final int CHUNK_TEXT = 0x74455874; // tEXt: 116 69 88 116
+    private static final int CHUNK_ZTXT = 0x7A545874; // zTXt: 122 84 88 116
+    private static final int CHUNK_ITXT = 0x69545874; // iTXt: 105 84 88 116
+    private static final int CHUNK_KBKG = 0x6B424B47; // kBKG: 107 66 75 71
+    private static final int CHUNK_HIST = 0x68495354; // hIST: 104 73 83 84
+    private static final int CHUNK_PHYS = 0x70485973; // pHYs: 112 72 89 115
+    private static final int CHUNK_SPLT = 0x73504C54; // sPLT: 115 80 76 84
+    private static final int CHUNK_TIME = 0x74494D45; // tIME: 116 73 77 69
+
+    /**
+     * Parse the PNG structure into the PNGConverterState. If we can't handle
+     * something, this method will return null.
+     *
+     * @param imageData the byte array with the PNG data
+     * @return null or the converter state with all relevant chunks
+     */
+    private static PNGConverterState parsePNGChunks(byte[] imageData)
+    {
+        if (imageData.length < 20)
+        {
+            LOG.error("ByteArray way to small: " + imageData.length);
+            return null;
+        }
+
+        PNGConverterState state = new PNGConverterState();
+        int ptr = 8;
+        int firstChunkType = readInt(imageData, ptr + 4);
+
+        if (firstChunkType != CHUNK_IHDR)
+        {
+            LOG.error(String.format("First Chunktype was %08X, not IHDR", firstChunkType));
+            return null;
+        }
+
+        while (ptr + 12 <= imageData.length)
+        {
+            int chunkLength = readInt(imageData, ptr);
+            int chunkType = readInt(imageData, ptr + 4);
+            ptr += 8;
+
+            if (ptr + chunkLength + 4 > imageData.length)
+            {
+                LOG.error("Not enough bytes. At offset " + ptr + " are " + chunkLength
+                        + " bytes expected. Overall length is " + imageData.length);
+                return null;
+            }
+
+            Chunk chunk = new Chunk();
+            chunk.chunkType = chunkType;
+            chunk.bytes = imageData;
+            chunk.start = ptr;
+            chunk.length = chunkLength;
+
+            switch (chunkType)
+            {
+            case CHUNK_IHDR:
+                if (state.IHDR != null)
+                {
+                    LOG.error("Two IHDR chunks? There is something wrong.");
+                    return null;
+                }
+                state.IHDR = chunk;
+                break;
+            case CHUNK_IDAT:
+                // The image data itself
+                state.IDATs.add(chunk);
+                break;
+            case CHUNK_PLTE:
+                // For indexed images the palette table
+                if (state.PLTE != null)
+                {
+                    LOG.error("Two PLTE chunks? There is something wrong.");
+                    return null;
+                }
+                state.PLTE = chunk;
+                break;
+            case CHUNK_IEND:
+                // We are done, return the state
+                return state;
+            case CHUNK_TRNS:
+                // For indexed images the alpha transparency table
+                if (state.tRNS != null)
+                {
+                    LOG.error("Two tRNS chunks? There is something wrong.");
+                    return null;
+                }
+                state.tRNS = chunk;
+                break;
+            case CHUNK_GAMA:
+                // Gama
+                state.gAMA = chunk;
+                break;
+            case CHUNK_CHRM:
+                // Chroma
+                state.cHRM = chunk;
+                break;
+            case CHUNK_ICCP:
+                // ICC Profile
+                state.iCCP = chunk;
+                break;
+            case CHUNK_SBIT:
+                LOG.debug("Can't convert PNGs with sBIT chunk.");
+                break;
+            case CHUNK_SRGB:
+                // We use the rendering intent from the chunk
+                state.sRGB = chunk;
+                break;
+            case CHUNK_TEXT:
+            case CHUNK_ZTXT:
+            case CHUNK_ITXT:
+                // We don't care about this text infos / metadata
+                break;
+            case CHUNK_KBKG:
+                // As we can handle transparency we don't need the background color information.
+                break;
+            case CHUNK_HIST:
+                // We don't need the color histogram
+                break;
+            case CHUNK_PHYS:
+                // The PDImageXObject will be placed by the user however he wants,
+                // so we can not enforce the physical dpi information stored here.
+                // We just ignore it.
+                break;
+            case CHUNK_SPLT:
+                // This palette stuff seems editor related, we don't need it.
+                break;
+            case CHUNK_TIME:
+                // We don't need the last image change time either
+                break;
+            default:
+                LOG.debug(String.format("Unknown chunk type %08X, skipping.", chunkType));
+                break;
+            }
+            ptr += chunkLength;
+
+            // Read the CRC
+            chunk.crc = readInt(imageData, ptr);
+            ptr += 4;
+        }
+        LOG.error("No IEND chunk found.");
+        return null;
+    }
+
+    // CRC Reference Implementation, see
+    // https://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix
+    // for details
+
+    /* Table of CRCs of all 8-bit messages. */
+    private static final int[] CRC_TABLE = new int[256];
+
+    static
+    {
+        makeCrcTable();
+    }
+
+    /* Make the table for a fast CRC. */
+    private static void makeCrcTable()
+    {
+        int c;
+
+        for (int n = 0; n < 256; n++)
+        {
+            c = n;
+            for (int k = 0; k < 8; k++)
+            {
+                if ((c & 1) != 0)
+                {
+                    c = 0xEDB88320 ^ (c >>> 1);
+                }
+                else
+                {
+                    c = c >>> 1;
+                }
+            }
+            CRC_TABLE[n] = c;
+        }
+    }
+
+    /*
+     * Update a running CRC with the bytes buf[0..len-1]--the CRC should be
+     * initialized to all 1's, and the transmitted value is the 1's complement of
+     * the final running CRC (see the crc() routine below).
+     */
+    private static int updateCrc(byte[] buf, int offset, int len)
+    {
+        int c = -1;
+        int end = offset + len;
+        for (int n = offset; n < end; n++)
+        {
+            c = CRC_TABLE[(c ^ buf[n]) & 0xff] ^ (c >>> 8);
+        }
+        return c;
+    }
+
+    /* Return the CRC of the bytes buf[offset..(offset+len-1)]. */
+    static int crc(byte[] buf, int offset, int len)
+    {
+        return ~updateCrc(buf, offset, len);
+    }
+}

Propchange: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverterTest.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverterTest.java?rev=1868357&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverterTest.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverterTest.java Sat Oct 12 17:55:05 2019
@@ -0,0 +1,273 @@
+package org.apache.pdfbox.pdmodel.graphics.image;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import javax.imageio.ImageIO;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+
+import static org.apache.pdfbox.pdmodel.graphics.image.ValidateXImage.checkIdent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PNGConverterTest
+{
+
+    @Before
+    public void setup()
+    {
+        //noinspection ResultOfMethodCallIgnored
+        parentDir.mkdirs();
+    }
+
+    /**
+     * This "test" just dumps the list of constants for the PNGConverter CHUNK_??? types, so that
+     * it can just be copy&pasted into the PNGConverter class.
+     */
+    //@Test
+    public void dumpChunkTypes()
+    {
+        final String[] chunkTypes = { "IHDR", "IDAT", "PLTE", "IEND", "tRNS", "cHRM", "gAMA",
+                "iCCP", "sBIT", "sRGB", "tEXt", "zTXt", "iTXt", "kBKG", "hIST", "pHYs", "sPLT",
+                "tIME" };
+
+        for (String chunkType : chunkTypes)
+        {
+            byte[] bytes = chunkType.getBytes();
+            assertEquals(4, bytes.length);
+            System.out.println(String.format("\tprivate static final int CHUNK_" + chunkType
+                            + " = 0x%02X%02X%02X%02X; // %s: %d %d %d %d", (int) bytes[0] & 0xFF,
+                    (int) bytes[1] & 0xFF, (int) bytes[2] & 0xFF, (int) bytes[3] & 0xFF, chunkType,
+                    (int) bytes[0] & 0xFF, (int) bytes[1] & 0xFF, (int) bytes[2] & 0xFF,
+                    (int) bytes[3] & 0xFF));
+        }
+    }
+
+    @Test
+    public void testImageConversionRGB() throws IOException
+    {
+        checkImageConvert("png.png");
+    }
+
+    @Test
+    public void testImageConversionRGBGamma() throws IOException
+    {
+        checkImageConvert("png_rgb_gamma.png");
+    }
+
+    @Test
+    public void testImageConversionRGB16BitICC() throws IOException
+    {
+        checkImageConvert("png_rgb_romm_16bit.png");
+    }
+
+    @Test
+    public void testImageConversionRGBIndexed() throws IOException
+    {
+        checkImageConvert("png_indexed.png");
+    }
+
+    @Test
+    public void testImageConversionRGBIndexedAlpha1Bit() throws IOException
+    {
+        checkImageConvert("png_indexed_1bit_alpha.png");
+    }
+
+    @Test
+    public void testImageConversionRGBIndexedAlpha2Bit() throws IOException
+    {
+        checkImageConvert("png_indexed_2bit_alpha.png");
+    }
+
+    @Test
+    public void testImageConversionRGBIndexedAlpha4Bit() throws IOException
+    {
+        checkImageConvert("png_indexed_4bit_alpha.png");
+    }
+
+    @Test
+    public void testImageConversionRGBIndexedAlpha8Bit() throws IOException
+    {
+        checkImageConvert("png_indexed_8bit_alpha.png");
+    }
+
+    @Test
+    public void testImageConversionRGBAlpha() throws IOException
+    {
+        // We can't handle Alpha RGB
+        checkImageConvertFail("png_alpha_rgb.png");
+    }
+
+    @Test
+    public void testImageConversionGrayAlpha() throws IOException
+    {
+        // We can't handle Alpha RGB
+        checkImageConvertFail("png_alpha_gray.png");
+    }
+
+    @Test
+    public void testImageConversionGray() throws IOException
+    {
+        checkImageConvertFail("png_gray.png");
+    }
+
+    @Test
+    public void testImageConversionGrayGamma() throws IOException
+    {
+        checkImageConvertFail("png_gray_with_gama.png");
+    }
+
+    private final File parentDir = new File("target/test-output/graphics/graphics");
+
+    private void checkImageConvertFail(String name) throws IOException
+    {
+        PDDocument doc = new PDDocument();
+        byte[] imageBytes = IOUtils.toByteArray(PNGConverterTest.class.getResourceAsStream(name));
+        PDImageXObject pdImageXObject = PNGConverter.convertPNGImage(doc, imageBytes);
+        assertNull(pdImageXObject);
+        doc.close();
+    }
+
+    private void checkImageConvert(String name) throws IOException
+    {
+        PDDocument doc = new PDDocument();
+        byte[] imageBytes = IOUtils.toByteArray(PNGConverterTest.class.getResourceAsStream(name));
+        PDImageXObject pdImageXObject = PNGConverter.convertPNGImage(doc, imageBytes);
+        assertNotNull(pdImageXObject);
+        PDPage page = new PDPage();
+        doc.addPage(page);
+        PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+        contentStream.setNonStrokingColor(Color.PINK);
+        contentStream.addRect(0, 0, page.getCropBox().getWidth(), page.getCropBox().getHeight());
+        contentStream.fill();
+
+        contentStream.drawImage(pdImageXObject, 0, 0, pdImageXObject.getWidth(),
+                pdImageXObject.getHeight());
+        contentStream.close();
+        doc.save(new File(parentDir, name + ".pdf"));
+        BufferedImage image = pdImageXObject.getImage();
+        checkIdent(ImageIO.read(new ByteArrayInputStream(imageBytes)), image);
+        doc.close();
+    }
+
+    @Test
+    public void testCheckConverterState()
+    {
+        assertFalse(PNGConverter.checkConverterState(null));
+        PNGConverter.PNGConverterState state = new PNGConverter.PNGConverterState();
+        assertFalse(PNGConverter.checkConverterState(state));
+
+        PNGConverter.Chunk invalidChunk = new PNGConverter.Chunk();
+        invalidChunk.bytes = new byte[0];
+        assertFalse(PNGConverter.checkChunkSane(invalidChunk));
+
+        // Valid Dummy Chunk
+        PNGConverter.Chunk validChunk = new PNGConverter.Chunk();
+        validChunk.bytes = new byte[16];
+        validChunk.start = 4;
+        validChunk.length = 8;
+        validChunk.crc = 2077607535;
+        assertTrue(PNGConverter.checkChunkSane(validChunk));
+
+        state.IHDR = invalidChunk;
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.IDATs = Collections.singletonList(validChunk);
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.IHDR = validChunk;
+        assertTrue(PNGConverter.checkConverterState(state));
+        state.IDATs = new ArrayList<PNGConverter.Chunk>();
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.IDATs = Collections.singletonList(validChunk);
+        assertTrue(PNGConverter.checkConverterState(state));
+
+        state.PLTE = invalidChunk;
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.PLTE = validChunk;
+        assertTrue(PNGConverter.checkConverterState(state));
+
+        state.cHRM = invalidChunk;
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.cHRM = validChunk;
+        assertTrue(PNGConverter.checkConverterState(state));
+
+        state.tRNS = invalidChunk;
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.tRNS = validChunk;
+        assertTrue(PNGConverter.checkConverterState(state));
+
+        state.iCCP = invalidChunk;
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.iCCP = validChunk;
+        assertTrue(PNGConverter.checkConverterState(state));
+
+        state.sRGB = invalidChunk;
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.sRGB = validChunk;
+        assertTrue(PNGConverter.checkConverterState(state));
+
+        state.gAMA = invalidChunk;
+        assertFalse(PNGConverter.checkConverterState(state));
+        state.gAMA = validChunk;
+        assertTrue(PNGConverter.checkConverterState(state));
+
+        state.IDATs = Arrays.asList(validChunk, invalidChunk);
+        assertFalse(PNGConverter.checkConverterState(state));
+    }
+
+    @Test
+    public void testChunkSane()
+    {
+        PNGConverter.Chunk chunk = new PNGConverter.Chunk();
+        assertTrue(PNGConverter.checkChunkSane(null));
+        chunk.bytes = "IHDRsomedummyvaluesDummyValuesAtEnd".getBytes();
+        chunk.length = 19;
+        assertEquals(chunk.bytes.length, 35);
+
+        assertEquals("IHDRsomedummyvalues", new String(chunk.getData()));
+
+        assertFalse(PNGConverter.checkChunkSane(chunk));
+        chunk.start = 4;
+        assertEquals("somedummyvaluesDumm", new String(chunk.getData()));
+        assertFalse(PNGConverter.checkChunkSane(chunk));
+        chunk.crc = -1729802258;
+        assertTrue(PNGConverter.checkChunkSane(chunk));
+        chunk.start = 6;
+        assertFalse(PNGConverter.checkChunkSane(chunk));
+        chunk.length = 60;
+        assertFalse(PNGConverter.checkChunkSane(chunk));
+    }
+
+    @Test
+    public void testCRCImpl()
+    {
+        byte[] b1 = "Hello World!".getBytes();
+        assertEquals(472456355, PNGConverter.crc(b1, 0, b1.length));
+        assertEquals(-632335482, PNGConverter.crc(b1, 2, b1.length - 4));
+    }
+
+    @Test
+    public void testMapPNGRenderIntent()
+    {
+        assertEquals(COSName.PERCEPTUAL, PNGConverter.mapPNGRenderIntent(0));
+        assertEquals(COSName.RELATIVE_COLORIMETRIC, PNGConverter.mapPNGRenderIntent(1));
+        assertEquals(COSName.SATURATION, PNGConverter.mapPNGRenderIntent(2));
+        assertEquals(COSName.ABSOLUTE_COLORIMETRIC, PNGConverter.mapPNGRenderIntent(3));
+        assertNull(PNGConverter.mapPNGRenderIntent(-1));
+        assertNull(PNGConverter.mapPNGRenderIntent(4));
+    }
+}

Propchange: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PNGConverterTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_gray.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_gray.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_gray.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_rgb.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_rgb.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_alpha_rgb.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray_with_gama.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray_with_gama.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_gray_with_gama.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_1bit_alpha.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_1bit_alpha.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_1bit_alpha.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_2bit_alpha.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_2bit_alpha.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_2bit_alpha.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_4bit_alpha.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_4bit_alpha.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_4bit_alpha.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_8bit_alpha.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_8bit_alpha.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_indexed_8bit_alpha.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_gamma.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_gamma.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_gamma.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Added: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_romm_16bit.png
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_romm_16bit.png?rev=1868357&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/branches/2.0/pdfbox/src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/png_rgb_romm_16bit.png
------------------------------------------------------------------------------
    svn:mime-type = image/png