]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Bugzilla #40676: Support use of ImageLoaderRawPNG decoder in order to prevent re...
authorGlenn Adams <gadams@apache.org>
Fri, 15 Jun 2012 02:56:21 +0000 (02:56 +0000)
committerGlenn Adams <gadams@apache.org>
Fri, 15 Jun 2012 02:56:21 +0000 (02:56 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1350455 13f79535-47bb-0310-9956-ffa450edef68

14 files changed:
lib/xmlgraphics-commons-1.5svn.jar
src/java/META-INF/services/org.apache.fop.render.ImageHandler
src/java/org/apache/fop/pdf/BitmapImage.java
src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java
src/java/org/apache/fop/render/pdf/ImageRawPNGAdapter.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java
src/java/org/apache/fop/render/pdf/PDFImageHandlerRawPNG.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/ImageEncoderPNG.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerRawPNG.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerRenderedImage.java
status.xml
test/java/org/apache/fop/render/RawPNGTestUtil.java [new file with mode: 0644]
test/java/org/apache/fop/render/pdf/ImageRawPNGAdapterTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/ps/ImageEncoderPNGTestCase.java [new file with mode: 0644]

index 867ecb6e09be10435472b2469ce799d038768a06..c11f186081188cf819fcc5026a3add1a6d34ecd5 100644 (file)
Binary files a/lib/xmlgraphics-commons-1.5svn.jar and b/lib/xmlgraphics-commons-1.5svn.jar differ
index f9b890c8eb8335d0288b8981f40e87d7af92138f..9066913adbb56fef4acb11a79e94574ba34cd296 100644 (file)
@@ -1,6 +1,7 @@
 org.apache.fop.render.pdf.PDFImageHandlerGraphics2D
 org.apache.fop.render.pdf.PDFImageHandlerRenderedImage
 org.apache.fop.render.pdf.PDFImageHandlerRawJPEG
+org.apache.fop.render.pdf.PDFImageHandlerRawPNG
 org.apache.fop.render.pdf.PDFImageHandlerRawCCITTFax
 org.apache.fop.render.pdf.PDFImageHandlerSVG
 org.apache.fop.render.java2d.Java2DImageHandlerRenderedImage
@@ -11,6 +12,7 @@ org.apache.fop.render.ps.PSImageHandlerRenderedImage
 org.apache.fop.render.ps.PSImageHandlerEPS
 org.apache.fop.render.ps.PSImageHandlerRawCCITTFax
 org.apache.fop.render.ps.PSImageHandlerRawJPEG
+org.apache.fop.render.ps.PSImageHandlerRawPNG
 org.apache.fop.render.ps.PSImageHandlerGraphics2D
 org.apache.fop.render.ps.PSImageHandlerSVG
 org.apache.fop.render.afp.AFPImageHandlerRenderedImage
index 186c00986f408286a5b7eef81989113b7b394af0..0979ddf9a2d87dc38441a402ed9a0c213c7c9b98 100644 (file)
@@ -37,6 +37,7 @@ public class BitmapImage implements PDFImage {
     private PDFColor transparent = null;
     private String key;
     private PDFDocument pdfDoc;
+    private PDFFilter pdfFilter;
 
     /**
      * Create a bitmap image.
@@ -208,9 +209,12 @@ public class BitmapImage implements PDFImage {
      * {@inheritDoc}
      */
     public PDFFilter getPDFFilter() {
-        return null;
+        return pdfFilter;
     }
 
+    public void setPDFFilter(PDFFilter pdfFilter) {
+        this.pdfFilter = pdfFilter;
+    }
 }
 
 
index 59d3930f7afa32d0c294c3ba84424d3087bc2768..ce99b9fefae0e735244162e2af3d162936c5e998 100644 (file)
 package org.apache.fop.render.pdf;
 import java.awt.color.ColorSpace;
 import java.awt.color.ICC_Profile;
+import java.awt.image.IndexColorModel;
 
+import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.xmlgraphics.image.loader.Image;
 import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil;
 
+import org.apache.fop.pdf.PDFArray;
 import org.apache.fop.pdf.PDFColor;
 import org.apache.fop.pdf.PDFConformanceException;
 import org.apache.fop.pdf.PDFDeviceColorSpace;
@@ -50,7 +53,9 @@ public abstract class AbstractImageAdapter implements PDFImage {
     /** the image */
     protected Image image;
 
-    private PDFICCStream pdfICCStream = null;
+    private PDFICCStream pdfICCStream;
+
+    private static final int MAX_HIVAL = 255;
 
     /**
      * Creates a new PDFImage from an Image instance.
@@ -202,6 +207,68 @@ public abstract class AbstractImageAdapter implements PDFImage {
         //nop
     }
 
+    /**
+     * This is to be used by populateXObjectDictionary() when the image is palette based.
+     * @param dict the dictionary to fill in
+     * @param icm the image color model
+     */
+    protected void populateXObjectDictionaryForIndexColorModel(PDFDictionary dict, IndexColorModel icm) {
+        PDFArray indexed = new PDFArray(dict);
+        indexed.add(new PDFName("Indexed"));
+        if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) {
+            log.warn("Indexed color space is not using RGB as base color space."
+                    + " The image may not be handled correctly." + " Base color space: "
+                    + icm.getColorSpace() + " Image: " + image.getInfo());
+        }
+        indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName()));
+        int c = icm.getMapSize();
+        int hival = c - 1;
+        if (hival > MAX_HIVAL) {
+            throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL);
+        }
+        indexed.add(new Integer(hival));
+        int[] palette = new int[c];
+        icm.getRGBs(palette);
+        ByteArrayOutputStream baout = new ByteArrayOutputStream();
+        for (int i = 0; i < c; i++) {
+            // TODO Probably doesn't work for non RGB based color spaces
+            // See log warning above
+            int entry = palette[i];
+            baout.write((entry & 0xFF0000) >> 16);
+            baout.write((entry & 0xFF00) >> 8);
+            baout.write(entry & 0xFF);
+        }
+        indexed.add(baout.toByteArray());
+
+        dict.put("ColorSpace", indexed);
+        dict.put("BitsPerComponent", icm.getPixelSize());
+
+        Integer index = getIndexOfFirstTransparentColorInPalette(icm);
+        if (index != null) {
+            PDFArray mask = new PDFArray(dict);
+            mask.add(index);
+            mask.add(index);
+            dict.put("Mask", mask);
+        }
+    }
+
+    private static Integer getIndexOfFirstTransparentColorInPalette(IndexColorModel icm) {
+        byte[] alphas = new byte[icm.getMapSize()];
+        byte[] reds = new byte[icm.getMapSize()];
+        byte[] greens = new byte[icm.getMapSize()];
+        byte[] blues = new byte[icm.getMapSize()];
+        icm.getAlphas(alphas);
+        icm.getReds(reds);
+        icm.getGreens(greens);
+        icm.getBlues(blues);
+        for (int i = 0; i < icm.getMapSize(); i++) {
+            if ((alphas[i] & 0xFF) == 0) {
+                return new Integer(i);
+            }
+        }
+        return null;
+    }
+
     /**
      * Converts a ColorSpace object to a PDFColorSpace object.
      * @param cs ColorSpace instance
diff --git a/src/java/org/apache/fop/render/pdf/ImageRawPNGAdapter.java b/src/java/org/apache/fop/render/pdf/ImageRawPNGAdapter.java
new file mode 100644 (file)
index 0000000..55264e2
--- /dev/null
@@ -0,0 +1,258 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/* $Id$ */\r
+\r
+// Original author: Matthias Reichenbacher\r
+\r
+package org.apache.fop.render.pdf;\r
+\r
+import java.awt.image.ColorModel;\r
+import java.awt.image.IndexColorModel;\r
+import java.io.DataInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.util.zip.Deflater;\r
+import java.util.zip.DeflaterOutputStream;\r
+import java.util.zip.Inflater;\r
+import java.util.zip.InflaterInputStream;\r
+\r
+import org.apache.commons.io.IOUtils;\r
+import org.apache.commons.io.output.ByteArrayOutputStream;\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+\r
+import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;\r
+import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;\r
+\r
+import org.apache.fop.pdf.BitmapImage;\r
+import org.apache.fop.pdf.FlateFilter;\r
+import org.apache.fop.pdf.PDFColor;\r
+import org.apache.fop.pdf.PDFDeviceColorSpace;\r
+import org.apache.fop.pdf.PDFDictionary;\r
+import org.apache.fop.pdf.PDFDocument;\r
+import org.apache.fop.pdf.PDFFilter;\r
+import org.apache.fop.pdf.PDFFilterException;\r
+import org.apache.fop.pdf.PDFFilterList;\r
+import org.apache.fop.pdf.PDFICCStream;\r
+import org.apache.fop.pdf.PDFReference;\r
+\r
+public class ImageRawPNGAdapter extends AbstractImageAdapter {\r
+\r
+    /** logging instance */\r
+    private static Log log = LogFactory.getLog(ImageRawPNGAdapter.class);\r
+\r
+    private PDFICCStream pdfICCStream;\r
+    private PDFFilter pdfFilter;\r
+    private String maskRef;\r
+    private PDFReference softMask;\r
+    private int numberOfInterleavedComponents;\r
+\r
+    /**\r
+     * Creates a new PDFImage from an Image instance.\r
+     * @param image the image\r
+     * @param key XObject key\r
+     */\r
+    public ImageRawPNGAdapter(ImageRawPNG image, String key) {\r
+        super(image, key);\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public void setup(PDFDocument doc) {\r
+        super.setup(doc);\r
+        ColorModel cm = ((ImageRawPNG) this.image).getColorModel();\r
+        if (cm instanceof IndexColorModel) {\r
+            numberOfInterleavedComponents = 1;\r
+        } else {\r
+            // this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha)\r
+            // numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents();\r
+            numberOfInterleavedComponents = cm.getNumComponents();\r
+        }\r
+\r
+        // set up image compression for non-alpha channel\r
+        FlateFilter flate;\r
+        try {\r
+            flate = new FlateFilter();\r
+            flate.setApplied(true);\r
+            flate.setPredictor(FlateFilter.PREDICTION_PNG_OPT);\r
+            if (numberOfInterleavedComponents < 3) {\r
+                // means palette (1) or gray (1) or gray + alpha (2)\r
+                flate.setColors(1);\r
+            } else {\r
+                // means rgb (3) or rgb + alpha (4)\r
+                flate.setColors(3);\r
+            }\r
+            flate.setColumns(image.getSize().getWidthPx());\r
+            flate.setBitsPerComponent(this.getBitsPerComponent());\r
+        } catch (PDFFilterException e) {\r
+            throw new RuntimeException("FlateFilter configuration error", e);\r
+        }\r
+        this.pdfFilter = flate;\r
+\r
+        // Handle transparency channel if applicable; note that for palette images the transparency is\r
+        // not TRANSLUCENT\r
+        if (cm.hasAlpha() && cm.getTransparency() == ColorModel.TRANSLUCENT) {\r
+            doc.getProfile().verifyTransparencyAllowed(image.getInfo().getOriginalURI());\r
+            // TODO: Implement code to combine image with background color if transparency is not allowed\r
+            // here we need to inflate the PNG pixel data, which includes alpha, separate the alpha channel\r
+            // and then deflate it back again\r
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
+            DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater());\r
+            InputStream in = ((ImageRawStream) image).createInputStream();\r
+            try {\r
+                InflaterInputStream infStream = new InflaterInputStream(in, new Inflater());\r
+                DataInputStream dataStream = new DataInputStream(infStream);\r
+                // offset is the byte offset of the alpha component\r
+                int offset = numberOfInterleavedComponents - 1; // 1 for GA, 3 for RGBA\r
+                int numColumns = image.getSize().getWidthPx();\r
+                int bytesPerRow = numberOfInterleavedComponents * numColumns;\r
+                int filter;\r
+                // read line by line; the first byte holds the filter\r
+                while ((filter = dataStream.read()) != -1) {\r
+                    byte[] bytes = new byte[bytesPerRow];\r
+                    dataStream.readFully(bytes, 0, bytesPerRow);\r
+                    dos.write((byte) filter);\r
+                    for (int j = 0; j < numColumns; j++) {\r
+                        dos.write(bytes, offset, 1);\r
+                        offset += numberOfInterleavedComponents;\r
+                    }\r
+                    offset = numberOfInterleavedComponents - 1;\r
+                }\r
+                dos.close();\r
+            } catch (IOException e) {\r
+                throw new RuntimeException("Error processing transparency channel:", e);\r
+            } finally {\r
+                IOUtils.closeQuietly(in);\r
+            }\r
+            // set up alpha channel compression\r
+            FlateFilter transFlate;\r
+            try {\r
+                transFlate = new FlateFilter();\r
+                transFlate.setApplied(true);\r
+                transFlate.setPredictor(FlateFilter.PREDICTION_PNG_OPT);\r
+                transFlate.setColors(1);\r
+                transFlate.setColumns(image.getSize().getWidthPx());\r
+                transFlate.setBitsPerComponent(this.getBitsPerComponent());\r
+            } catch (PDFFilterException e) {\r
+                throw new RuntimeException("FlateFilter configuration error", e);\r
+            }\r
+            BitmapImage alphaMask = new BitmapImage("Mask:" + this.getKey(), image.getSize().getWidthPx(),\r
+                    image.getSize().getHeightPx(), baos.toByteArray(), null);\r
+            alphaMask.setPDFFilter(transFlate);\r
+            alphaMask.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY));\r
+            softMask = doc.addImage(null, alphaMask).makeReference();\r
+        }\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public PDFDeviceColorSpace getColorSpace() {\r
+        // DeviceGray, DeviceRGB, or DeviceCMYK\r
+        return toPDFColorSpace(image.getColorSpace());\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public int getBitsPerComponent() {\r
+        return ((ImageRawPNG) this.image).getBitDepth();\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public boolean isTransparent() {\r
+        return ((ImageRawPNG) this.image).isTransparent();\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public PDFColor getTransparentColor() {\r
+        return new PDFColor(((ImageRawPNG) this.image).getTransparentColor());\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public String getMask() {\r
+        return maskRef;\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public String getSoftMask() {\r
+        return softMask.toString();\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public PDFReference getSoftMaskReference() {\r
+        return softMask;\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public PDFFilter getPDFFilter() {\r
+        return pdfFilter;\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public void outputContents(OutputStream out) throws IOException {\r
+        InputStream in = ((ImageRawStream) image).createInputStream();\r
+\r
+        try {\r
+            if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) {\r
+                // means we have Gray, RGB, or Palette\r
+                IOUtils.copy(in, out);\r
+            } else {\r
+                // means we have Gray + alpha or RGB + alpha\r
+                // TODO: since we have alpha here do this when the alpha channel is extracted\r
+                int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB\r
+                int numColumns = image.getSize().getWidthPx();\r
+                InflaterInputStream infStream = new InflaterInputStream(in, new Inflater());\r
+                DataInputStream dataStream = new DataInputStream(infStream);\r
+                int offset = 0;\r
+                int bytesPerRow = numberOfInterleavedComponents * numColumns;\r
+                int filter;\r
+                // here we need to inflate the PNG pixel data, which includes alpha, separate the alpha\r
+                // channel and then deflate the RGB channels back again\r
+                DeflaterOutputStream dos = new DeflaterOutputStream(out, new Deflater());\r
+                while ((filter = dataStream.read()) != -1) {\r
+                    byte[] bytes = new byte[bytesPerRow];\r
+                    dataStream.readFully(bytes, 0, bytesPerRow);\r
+                    dos.write((byte) filter);\r
+                    for (int j = 0; j < numColumns; j++) {\r
+                        dos.write(bytes, offset, numBytes);\r
+                        offset += numberOfInterleavedComponents;\r
+                    }\r
+                    offset = 0;\r
+                }\r
+                dos.close();\r
+            }\r
+        } finally {\r
+            IOUtils.closeQuietly(in);\r
+        }\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public PDFICCStream getICCStream() {\r
+        return pdfICCStream;\r
+    }\r
+\r
+    /** {@inheritDoc} */\r
+    public String getFilterHint() {\r
+        return PDFFilterList.PRECOMPRESSED_FILTER;\r
+    }\r
+\r
+    public void populateXObjectDictionary(PDFDictionary dict) {\r
+        ColorModel cm = ((ImageRawPNG) image).getColorModel();\r
+        if (cm instanceof IndexColorModel) {\r
+            IndexColorModel icm = (IndexColorModel) cm;\r
+            super.populateXObjectDictionaryForIndexColorModel(dict, icm);\r
+        }\r
+    }\r
+}\r
index c38a2edaff1a48d309e5093b05949443b0e23ea4..b0b7b79d111c27f8a217888647cb1075cab7f14a 100644 (file)
@@ -27,8 +27,6 @@ import java.awt.image.RenderedImage;
 import java.io.IOException;
 import java.io.OutputStream;
 
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -36,14 +34,12 @@ import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
 import org.apache.xmlgraphics.ps.ImageEncodingHelper;
 
 import org.apache.fop.pdf.AlphaRasterImage;
-import org.apache.fop.pdf.PDFArray;
 import org.apache.fop.pdf.PDFColor;
 import org.apache.fop.pdf.PDFDeviceColorSpace;
 import org.apache.fop.pdf.PDFDictionary;
 import org.apache.fop.pdf.PDFDocument;
 import org.apache.fop.pdf.PDFFilter;
 import org.apache.fop.pdf.PDFFilterList;
-import org.apache.fop.pdf.PDFName;
 import org.apache.fop.pdf.PDFReference;
 
 /**
@@ -162,30 +158,6 @@ public class ImageRenderedAdapter extends AbstractImageAdapter {
         return (getImage().getTransparentColor() != null);
     }
 
-    private static Integer getIndexOfFirstTransparentColorInPalette(RenderedImage image) {
-        ColorModel cm = image.getColorModel();
-        if (cm instanceof IndexColorModel) {
-            IndexColorModel icm = (IndexColorModel)cm;
-            //Identify the transparent color in the palette
-            byte[] alphas = new byte[icm.getMapSize()];
-            byte[] reds = new byte[icm.getMapSize()];
-            byte[] greens = new byte[icm.getMapSize()];
-            byte[] blues = new byte[icm.getMapSize()];
-            icm.getAlphas(alphas);
-            icm.getReds(reds);
-            icm.getGreens(greens);
-            icm.getBlues(blues);
-            for (int i = 0;
-                    i < ((IndexColorModel) cm).getMapSize();
-                    i++) {
-                if ((alphas[i] & 0xFF) == 0) {
-                    return Integer.valueOf(i);
-                }
-            }
-        }
-        return null;
-    }
-
     /** {@inheritDoc} */
     @Override
     public PDFColor getTransparentColor() {
@@ -230,54 +202,13 @@ public class ImageRenderedAdapter extends AbstractImageAdapter {
         }
     }
 
-    private static final int MAX_HIVAL = 255;
-
     /** {@inheritDoc} */
     @Override
     public void populateXObjectDictionary(PDFDictionary dict) {
         ColorModel cm = getEffectiveColorModel();
         if (cm instanceof IndexColorModel) {
-            IndexColorModel icm = (IndexColorModel)cm;
-            PDFArray indexed = new PDFArray(dict);
-            indexed.add(new PDFName("Indexed"));
-
-            if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) {
-                log.warn("Indexed color space is not using RGB as base color space."
-                        + " The image may not be handled correctly."
-                        + " Base color space: " + icm.getColorSpace()
-                        + " Image: " + image.getInfo());
-            }
-            indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName()));
-            int c = icm.getMapSize();
-            int hival = c - 1;
-            if (hival > MAX_HIVAL) {
-                throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL);
-            }
-            indexed.add(Integer.valueOf(hival));
-            int[] palette = new int[c];
-            icm.getRGBs(palette);
-            ByteArrayOutputStream baout = new ByteArrayOutputStream();
-            for (int i = 0; i < c; i++) {
-                //TODO Probably doesn't work for non RGB based color spaces
-                //See log warning above
-                int entry = palette[i];
-                baout.write((entry & 0xFF0000) >> 16);
-                baout.write((entry & 0xFF00) >> 8);
-                baout.write(entry & 0xFF);
-            }
-            indexed.add(baout.toByteArray());
-            IOUtils.closeQuietly(baout);
-
-            dict.put("ColorSpace", indexed);
-            dict.put("BitsPerComponent", icm.getPixelSize());
-
-            Integer index = getIndexOfFirstTransparentColorInPalette(getImage().getRenderedImage());
-            if (index != null) {
-                PDFArray mask = new PDFArray(dict);
-                mask.add(index);
-                mask.add(index);
-                dict.put("Mask", mask);
-            }
+            IndexColorModel icm = (IndexColorModel) cm;
+            super.populateXObjectDictionaryForIndexColorModel(dict, icm);
         }
     }
 
diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawPNG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawPNG.java
new file mode 100644 (file)
index 0000000..3475954
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+// Original author: Matthias Reichenbacher
+
+package org.apache.fop.render.pdf;
+
+import org.apache.xmlgraphics.image.loader.Image;
+import org.apache.xmlgraphics.image.loader.ImageFlavor;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
+
+import org.apache.fop.pdf.PDFImage;
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation which handles CCITT encoded images (CCITT fax group 3/4)
+ * for PDF output.
+ */
+public class PDFImageHandlerRawPNG extends AbstractPDFImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {ImageFlavor.RAW_PNG};
+
+    @Override
+    PDFImage createPDFImage(Image image, String xobjectKey) {
+        return new ImageRawPNGAdapter((ImageRawPNG) image, xobjectKey);
+    }
+
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 100;
+    }
+
+    /** {@inheritDoc} */
+    public Class<ImageRawPNG> getSupportedImageClass() {
+        return ImageRawPNG.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        return (image == null || image instanceof ImageRawPNG)
+                && targetContext instanceof PDFRenderingContext;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/ps/ImageEncoderPNG.java b/src/java/org/apache/fop/render/ps/ImageEncoderPNG.java
new file mode 100644 (file)
index 0000000..bcda90b
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps;
+
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
+import org.apache.xmlgraphics.ps.ImageEncoder;
+
+/**
+ * ImageEncoder implementation for PNG images.
+ */
+public class ImageEncoderPNG implements ImageEncoder {
+    private final ImageRawPNG image;
+    private int numberOfInterleavedComponents;
+
+    /**
+     * Main constructor
+     * @param image the PNG image
+     */
+    public ImageEncoderPNG(ImageRawPNG image) {
+        this.image = image;
+        ColorModel cm = ((ImageRawPNG) this.image).getColorModel();
+        if (cm instanceof IndexColorModel) {
+            numberOfInterleavedComponents = 1;
+        } else {
+            // this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha)
+            // numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents();
+            numberOfInterleavedComponents = cm.getNumComponents();
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void writeTo(OutputStream out) throws IOException {
+        // TODO: refactor this code with equivalent PDF code
+        InputStream in = ((ImageRawStream) image).createInputStream();
+        try {
+            if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) {
+                // means we have Gray, RGB, or Palette
+                IOUtils.copy(in, out);
+            } else {
+                // means we have Gray + alpha or RGB + alpha
+                int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB
+                int numColumns = image.getSize().getWidthPx();
+                InflaterInputStream infStream = new InflaterInputStream(in, new Inflater());
+                DataInputStream dataStream = new DataInputStream(infStream);
+                int offset = 0;
+                int bytesPerRow = numberOfInterleavedComponents * numColumns;
+                int filter;
+                // here we need to inflate the PNG pixel data, which includes alpha, separate the alpha
+                // channel and then deflate the RGB channels back again
+                // TODO: not using the baos below and using the original out instead (as happens in PDF)
+                // would be preferable but that does not work with the rest of the postscript code; this
+                // needs to be revisited
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                DeflaterOutputStream dos = new DeflaterOutputStream(/* out */baos, new Deflater());
+                while ((filter = dataStream.read()) != -1) {
+                    byte[] bytes = new byte[bytesPerRow];
+                    dataStream.readFully(bytes, 0, bytesPerRow);
+                    dos.write((byte) filter);
+                    for (int j = 0; j < numColumns; j++) {
+                        dos.write(bytes, offset, numBytes);
+                        offset += numberOfInterleavedComponents;
+                    }
+                    offset = 0;
+                }
+                dos.close();
+                IOUtils.copy(new ByteArrayInputStream(baos.toByteArray()), out);
+            }
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public String getImplicitFilter() {
+        String filter = "<< /Predictor 15 /Columns " + image.getSize().getWidthPx();
+        filter += " /Colors " + (numberOfInterleavedComponents > 2 ? 3 : 1);
+        filter += " /BitsPerComponent " + image.getBitDepth() + " >> /FlateDecode";
+        return filter;
+    }
+}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerRawPNG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerRawPNG.java
new file mode 100644 (file)
index 0000000..2a283e9
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+import java.io.IOException;
+
+import org.apache.xmlgraphics.image.loader.Image;
+import org.apache.xmlgraphics.image.loader.ImageFlavor;
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
+import org.apache.xmlgraphics.ps.FormGenerator;
+import org.apache.xmlgraphics.ps.ImageEncoder;
+import org.apache.xmlgraphics.ps.ImageFormGenerator;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSImageUtils;
+
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation which handles raw (not decoded) PNG images for PostScript output.
+ */
+public class PSImageHandlerRawPNG implements PSImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {ImageFlavor.RAW_PNG};
+
+    /** {@inheritDoc} */
+    public void handleImage(RenderingContext context, Image image, Rectangle pos) throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext) context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRawPNG png = (ImageRawPNG) image;
+
+        float x = (float) pos.getX() / 1000f;
+        float y = (float) pos.getY() / 1000f;
+        float w = (float) pos.getWidth() / 1000f;
+        float h = (float) pos.getHeight() / 1000f;
+        Rectangle2D targetRect = new Rectangle2D.Float(x, y, w, h);
+
+        ImageEncoder encoder = new ImageEncoderPNG(png);
+        ImageInfo info = image.getInfo();
+        Dimension imgDim = info.getSize().getDimensionPx();
+        String imgDescription = image.getClass().getName();
+        ColorModel cm = png.getColorModel();
+
+        PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen);
+    }
+
+    /** {@inheritDoc} */
+    public void generateForm(RenderingContext context, Image image, PSImageFormResource form)
+            throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext) context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRawPNG png = (ImageRawPNG) image;
+        ImageInfo info = image.getInfo();
+        String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
+
+        ImageEncoder encoder = new ImageEncoderPNG(png);
+        FormGenerator formGen = new ImageFormGenerator(form.getName(), imageDescription, info.getSize()
+                .getDimensionPt(), info.getSize().getDimensionPx(), encoder, png.getColorSpace(),
+                false);
+        formGen.generate(gen);
+    }
+
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 200;
+    }
+
+    /** {@inheritDoc} */
+    public Class<ImageRawPNG> getSupportedImageClass() {
+        return ImageRawPNG.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        if (targetContext instanceof PSRenderingContext) {
+            PSRenderingContext psContext = (PSRenderingContext) targetContext;
+            // The filters required for this implementation need PS level 2 or higher
+            if (psContext.getGenerator().getPSLevel() >= 2) {
+                return (image == null || image instanceof ImageRawPNG);
+            }
+        }
+        return false;
+    }
+
+}
index 5a13c1c8e03b0f329de294aa6e64685e5a613ec0..46597e46d1dd478712beb918f8db1e61d9c8cc74 100644 (file)
 
 package org.apache.fop.render.ps;
 
+import java.awt.Dimension;
 import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
 import java.awt.image.RenderedImage;
 import java.io.IOException;
 
@@ -28,6 +31,8 @@ import org.apache.xmlgraphics.image.loader.ImageFlavor;
 import org.apache.xmlgraphics.image.loader.ImageInfo;
 import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
 import org.apache.xmlgraphics.ps.FormGenerator;
+import org.apache.xmlgraphics.ps.ImageEncoder;
+import org.apache.xmlgraphics.ps.ImageEncodingHelper;
 import org.apache.xmlgraphics.ps.ImageFormGenerator;
 import org.apache.xmlgraphics.ps.PSGenerator;
 import org.apache.xmlgraphics.ps.PSImageUtils;
@@ -47,17 +52,24 @@ public class PSImageHandlerRenderedImage implements PSImageHandler {
     /** {@inheritDoc} */
     public void handleImage(RenderingContext context, Image image, Rectangle pos)
                 throws IOException {
-        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSRenderingContext psContext = (PSRenderingContext) context;
         PSGenerator gen = psContext.getGenerator();
-        ImageRendered imageRend = (ImageRendered)image;
+        ImageRendered imageRend = (ImageRendered) image;
 
-        float x = (float)pos.getX() / 1000f;
-        float y = (float)pos.getY() / 1000f;
-        float w = (float)pos.getWidth() / 1000f;
-        float h = (float)pos.getHeight() / 1000f;
+        float x = (float) pos.getX() / 1000f;
+        float y = (float) pos.getY() / 1000f;
+        float w = (float) pos.getWidth() / 1000f;
+        float h = (float) pos.getHeight() / 1000f;
+        Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h);
 
         RenderedImage ri = imageRend.getRenderedImage();
-        PSImageUtils.renderBitmapImage(ri, x, y, w, h, gen);
+        ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri);
+        Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight());
+        String imgDescription = ri.getClass().getName();
+        ImageEncodingHelper helper = new ImageEncodingHelper(ri);
+        ColorModel cm = helper.getEncodedColorModel();
+
+        PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen);
     }
 
     /** {@inheritDoc} */
index ba895efabbeefa56cbaa0bd7840c289941726b85..f96a9487b684d8cc1819937f40504e1146ed977f 100644 (file)
@@ -63,6 +63,9 @@
       documents. Example: the fix of marks layering will be such a case when it's done.
     -->
     <release version="FOP Trunk" date="TBD">
+      <action context="Images" dev="GA" type="fix" fixes-bug="40676" due-to="Luis Bernardo, Matthias Reischenbacher">
+        Support use of ImageLoaderRawPNG decoder in order to prevent re-encoding of PNG images (and unnecssary output file bloat).
+      </action>
       <action context="Code" dev="GA" type="fix" fixes-bug="53412" due-to="Alexios Giotis">
         Eliminate incorrect use of object identity which produces excessive property cache collisions.
       </action>
diff --git a/test/java/org/apache/fop/render/RawPNGTestUtil.java b/test/java/org/apache/fop/render/RawPNGTestUtil.java
new file mode 100644 (file)
index 0000000..e6660bb
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.apache.xmlgraphics.image.loader.ImageSize;
+
+public final class RawPNGTestUtil {
+
+    private static int NUM_ROWS = 32;
+    private static int NUM_COLUMNS = 32;
+    private static int DPI = 72;
+
+    private RawPNGTestUtil() {
+
+    }
+
+    /**
+     * Builds a PNG IDAT section for a square of a given color and alpha; the filter is fixed.
+     * @param gray the gray color; set to -1 if using RGB
+     * @param red the red color; ignored if gray > -1
+     * @param green the green color; ignored if gray > -1
+     * @param blue the blue color; ignored if gray > -1
+     * @param alpha the alpha color; set to -1 if not present
+     * @return the PNG IDAT byte array
+     * @throws IOException
+     */
+    public static byte[] buildGRGBAData(int gray, int red, int green, int blue, int alpha) throws IOException {
+        // build an image, 32x32, Gray or RGB, with or without alpha, and with filter
+        int filter = 0;
+        int numRows = NUM_ROWS;
+        int numColumns = NUM_COLUMNS;
+        int numComponents = (gray > -1 ? 1 : 3) + (alpha > -1 ? 1 : 0);
+        int numBytesPerRow = numColumns * numComponents + 1; // 1 for filter
+        int numBytes = numRows * numBytesPerRow;
+        byte[] data = new byte[numBytes];
+        for (int r = 0; r < numRows; r++) {
+            data[r * numBytesPerRow] = (byte) filter;
+            for (int c = 0; c < numColumns; c++) {
+                if (numComponents == 1) {
+                    data[r * numBytesPerRow + numComponents * c + 1] = (byte) gray;
+                } else if (numComponents == 2) {
+                    data[r * numBytesPerRow + numComponents * c + 1] = (byte) gray;
+                    data[r * numBytesPerRow + numComponents * c + 2] = (byte) alpha;
+                } else if (numComponents == 3) {
+                    data[r * numBytesPerRow + numComponents * c + 1] = (byte) red;
+                    data[r * numBytesPerRow + numComponents * c + 2] = (byte) green;
+                    data[r * numBytesPerRow + numComponents * c + 3] = (byte) blue;
+                } else if (numComponents == 4) {
+                    data[r * numBytesPerRow + numComponents * c + 1] = (byte) red;
+                    data[r * numBytesPerRow + numComponents * c + 2] = (byte) green;
+                    data[r * numBytesPerRow + numComponents * c + 3] = (byte) blue;
+                    data[r * numBytesPerRow + numComponents * c + 4] = (byte) alpha;
+                }
+            }
+        }
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater());
+        dos.write(data);
+        dos.close();
+        return baos.toByteArray();
+    }
+
+    /**
+     * 
+     * @return a default ImageSize
+     */
+    public static ImageSize getImageSize() {
+        return new ImageSize(NUM_ROWS, NUM_COLUMNS, DPI);
+    }
+}
diff --git a/test/java/org/apache/fop/render/pdf/ImageRawPNGAdapterTestCase.java b/test/java/org/apache/fop/render/pdf/ImageRawPNGAdapterTestCase.java
new file mode 100644 (file)
index 0000000..885821f
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf;
+
+import java.awt.image.ComponentColorModel;
+import java.awt.image.IndexColorModel;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.junit.Test;
+
+import org.apache.xmlgraphics.image.loader.ImageSize;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
+
+import org.apache.fop.pdf.FlateFilter;
+import org.apache.fop.pdf.PDFAMode;
+import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFProfile;
+import org.apache.fop.render.RawPNGTestUtil;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ImageRawPNGAdapterTestCase {
+
+    @Test
+    public void testSetupWithIndexColorModel() {
+        IndexColorModel cm = mock(IndexColorModel.class);
+        ImageRawPNG irpng = mock(ImageRawPNG.class);
+        PDFDocument doc = mock(PDFDocument.class);
+        PDFProfile profile = mock(PDFProfile.class);
+        ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock");
+        ImageSize is = RawPNGTestUtil.getImageSize();
+
+        when(irpng.getColorModel()).thenReturn(cm);
+        // when(cm.hasAlpha()).thenReturn(false);
+        when(doc.getProfile()).thenReturn(profile);
+        when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A);
+        when(irpng.getSize()).thenReturn(is);
+        irpnga.setup(doc);
+        FlateFilter filter = (FlateFilter) irpnga.getPDFFilter();
+        assertEquals(1, filter.getColors());
+    }
+
+    @Test
+    public void testSetupWithComponentColorModel() throws IOException {
+        ComponentColorModel cm = mock(ComponentColorModel.class);
+        ImageRawPNG irpng = mock(ImageRawPNG.class);
+        PDFDocument doc = mock(PDFDocument.class);
+        PDFProfile profile = mock(PDFProfile.class);
+        ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock");
+        ImageSize is = RawPNGTestUtil.getImageSize();
+
+        when(irpng.getColorModel()).thenReturn(cm);
+        when(cm.getNumComponents()).thenReturn(3);
+        // when(cm.hasAlpha()).thenReturn(false);
+        when(doc.getProfile()).thenReturn(profile);
+        when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A);
+        when(irpng.getSize()).thenReturn(is);
+        irpnga.setup(doc);
+        FlateFilter filter = (FlateFilter) irpnga.getPDFFilter();
+        assertEquals(3, filter.getColors());
+    }
+
+    @Test
+    public void testOutputContentsWithRGBPNG() throws IOException {
+        testOutputContentsWithGRGBAPNG(-1, 128, 128, 128, -1);
+    }
+
+    @Test
+    public void testOutputContentsWithRGBAPNG() throws IOException {
+        testOutputContentsWithGRGBAPNG(-1, 128, 128, 128, 128);
+    }
+
+    @Test
+    public void testOutputContentsWithGPNG() throws IOException {
+        testOutputContentsWithGRGBAPNG(128, -1, -1, -1, -1);
+    }
+
+    @Test
+    public void testOutputContentsWithGAPNG() throws IOException {
+        testOutputContentsWithGRGBAPNG(128, -1, -1, -1, 128);
+    }
+
+    private void testOutputContentsWithGRGBAPNG(int gray, int red, int green, int blue, int alpha)
+            throws IOException {
+        int numColorComponents = gray > -1 ? 1 : 3;
+        int numComponents = numColorComponents + (alpha > -1 ? 1 : 0);
+        ComponentColorModel cm = mock(ComponentColorModel.class);
+        ImageRawPNG irpng = mock(ImageRawPNG.class);
+        PDFDocument doc = mock(PDFDocument.class);
+        PDFProfile profile = mock(PDFProfile.class);
+        ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock");
+        ImageSize is = RawPNGTestUtil.getImageSize();
+
+        when(irpng.getColorModel()).thenReturn(cm);
+        when(cm.getNumComponents()).thenReturn(numComponents);
+        // when(cm.hasAlpha()).thenReturn(false);
+        when(doc.getProfile()).thenReturn(profile);
+        when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A);
+        when(irpng.getSize()).thenReturn(is);
+        irpnga.setup(doc);
+        FlateFilter filter = (FlateFilter) irpnga.getPDFFilter();
+        assertEquals(numColorComponents, filter.getColors());
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] data = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, alpha);
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        when(irpng.createInputStream()).thenReturn(bais);
+        irpnga.outputContents(baos);
+        if (alpha > -1) {
+            byte[] expected = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, -1);
+            assertArrayEquals(expected, baos.toByteArray());
+        } else {
+            assertArrayEquals(data, baos.toByteArray());
+        }
+    }
+
+}
diff --git a/test/java/org/apache/fop/render/ps/ImageEncoderPNGTestCase.java b/test/java/org/apache/fop/render/ps/ImageEncoderPNGTestCase.java
new file mode 100644 (file)
index 0000000..458033d
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps;
+
+import java.awt.image.ComponentColorModel;
+import java.awt.image.IndexColorModel;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.junit.Test;
+
+import org.apache.xmlgraphics.image.loader.ImageSize;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
+
+import org.apache.fop.render.RawPNGTestUtil;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ImageEncoderPNGTestCase {
+
+    @Test
+    public void testWriteToWithRGBPNG() throws IOException {
+        testWriteToWithGRGBAPNG(-1, 128, 128, 128, -1);
+    }
+
+    @Test
+    public void testWriteToWithGPNG() throws IOException {
+        testWriteToWithGRGBAPNG(128, -1, -1, -1, -1);
+    }
+
+    @Test
+    public void testWriteToWithRGBAPNG() throws IOException {
+        testWriteToWithGRGBAPNG(-1, 128, 128, 128, 128);
+    }
+
+    @Test
+    public void testWriteToWithGAPNG() throws IOException {
+        testWriteToWithGRGBAPNG(128, -1, -1, -1, 128);
+    }
+
+    private void testWriteToWithGRGBAPNG(int gray, int red, int green, int blue, int alpha)
+            throws IOException {
+        int numComponents = (gray > -1 ? 1 : 3) + (alpha > -1 ? 1 : 0);
+        ImageSize is = RawPNGTestUtil.getImageSize();
+        ComponentColorModel cm = mock(ComponentColorModel.class);
+        when(cm.getNumComponents()).thenReturn(numComponents);
+        ImageRawPNG irpng = mock(ImageRawPNG.class);
+        when(irpng.getColorModel()).thenReturn(cm);
+        when(irpng.getSize()).thenReturn(is);
+        ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] data = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, alpha);
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        when(irpng.createInputStream()).thenReturn(bais);
+        iepng.writeTo(baos);
+        if (alpha > -1) {
+            byte[] expected = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, -1);
+            assertArrayEquals(expected, baos.toByteArray());
+        } else {
+            assertArrayEquals(data, baos.toByteArray());
+        }
+    }
+
+    @Test
+    public void testWriteToWithPalettePNG() throws IOException {
+        ImageSize is = RawPNGTestUtil.getImageSize();
+        IndexColorModel cm = mock(IndexColorModel.class);
+        ImageRawPNG irpng = mock(ImageRawPNG.class);
+        when(irpng.getColorModel()).thenReturn(cm);
+        when(irpng.getSize()).thenReturn(is);
+        ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] data = RawPNGTestUtil.buildGRGBAData(128, -1, -1, -1, -1);
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        when(irpng.createInputStream()).thenReturn(bais);
+        iepng.writeTo(baos);
+        assertArrayEquals(data, baos.toByteArray());
+    }
+
+    @Test
+    public void testGetImplicitFilterWithIndexColorModel() {
+        ImageSize is = RawPNGTestUtil.getImageSize();
+        IndexColorModel cm = mock(IndexColorModel.class);
+        ImageRawPNG irpng = mock(ImageRawPNG.class);
+        when(irpng.getColorModel()).thenReturn(cm);
+        when(irpng.getBitDepth()).thenReturn(8);
+        when(irpng.getSize()).thenReturn(is);
+        ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);
+
+        String expectedFilter = "<< /Predictor 15 /Columns 32 /Colors 1 /BitsPerComponent 8 >> /FlateDecode";
+        assertEquals(expectedFilter, iepng.getImplicitFilter());
+    }
+
+    @Test
+    public void testGetImplicitFilterWithComponentColorModel() {
+        ImageSize is = RawPNGTestUtil.getImageSize();
+        ComponentColorModel cm = mock(ComponentColorModel.class);
+        when(cm.getNumComponents()).thenReturn(3);
+        ImageRawPNG irpng = mock(ImageRawPNG.class);
+        when(irpng.getColorModel()).thenReturn(cm);
+        when(irpng.getBitDepth()).thenReturn(8);
+        when(irpng.getSize()).thenReturn(is);
+        ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);
+
+        String expectedFilter = "<< /Predictor 15 /Columns 32 /Colors 3 /BitsPerComponent 8 >> /FlateDecode";
+        assertEquals(expectedFilter, iepng.getImplicitFilter());
+    }
+
+}