]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-2847: Support palette-based transparency PNG in PDF
authorSimon Steiner <ssteiner@apache.org>
Wed, 27 Mar 2019 16:06:24 +0000 (16:06 +0000)
committerSimon Steiner <ssteiner@apache.org>
Wed, 27 Mar 2019 16:06:24 +0000 (16:06 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1856423 13f79535-47bb-0310-9956-ffa450edef68

fop-core/src/main/java/org/apache/fop/pdf/AlphaRasterImage.java
fop-core/src/main/java/org/apache/fop/pdf/PDFImage.java
fop-core/src/main/java/org/apache/fop/render/pdf/AbstractImageAdapter.java
fop-core/src/main/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java
fop-core/src/test/java/org/apache/fop/pdf/PDFImageXObjectTestCase.java [new file with mode: 0644]
fop-core/src/test/java/org/apache/fop/render/pdf/ImageRenderedAdapterTestCase.java [new file with mode: 0644]

index 250b5daa21832a85862dc873177b391b1c951bb5..3c17de14814b1950619d5730aa2802b3973884fb 100644 (file)
@@ -19,6 +19,8 @@
 
 package org.apache.fop.pdf;
 
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
@@ -68,7 +70,40 @@ public class AlphaRasterImage implements PDFImage {
      * @param image the image (must have an alpha channel)
      */
     public AlphaRasterImage(String k, RenderedImage image) {
-        this(k, GraphicsUtil.getAlphaRaster(image));
+        this(k, getAlphaRaster(image));
+    }
+
+    /**
+     * Extracts the Alpha Raster for the given image.
+     * Also works for {@link java.awt.image.IndexColorModel}.
+     */
+    private static Raster getAlphaRaster(RenderedImage image) {
+        Raster alphaRaster = GraphicsUtil.getAlphaRaster(image);
+
+        /*
+         * {@link GraphicsUtil#getAlphaRaster} calls internally
+         * {@link java.awt.image.BufferedImage#getAlphRaster} which
+         * will return <code>null</code> according to Java API
+         * documentation for {@link java.awt.image.IndexColorModel}.
+         *
+         * In that case we create the raster drawing a hidden
+         * image. That code might be better placed in
+         * {@link java.awt.image.BufferedImage#getAlphRaster}
+         * but since this is a different project and a change
+         * to the interface semantics, it might break consumers.
+         */
+        if (alphaRaster == null) {
+            BufferedImage bufferedImage = (BufferedImage) image;
+            int w = bufferedImage.getWidth();
+            int h = bufferedImage.getHeight();
+            int type = BufferedImage.TYPE_INT_ARGB;
+            BufferedImage bia = new BufferedImage(w, h, type);
+            Graphics2D g = bia.createGraphics();
+            g.drawImage(bufferedImage, 0, 0, null);
+            g.dispose();
+            alphaRaster = GraphicsUtil.getAlphaRaster(bia);
+        }
+        return alphaRaster;
     }
 
     /** {@inheritDoc} */
index 38da60e7b652ae14300ab42af83b20a116c16d2f..328931ca6a09d35c51074c0a7e7b70fc72e4da6e 100644 (file)
@@ -85,7 +85,14 @@ public interface PDFImage {
     /**
      * Check if this image has a transparent color transparency.
      *
-     * @return true if it has transparency
+     * Classes such as {@link PDFImageXObject} rely on this simple
+     * binary model of transparency (e.g. compare to
+     * {@link java.awt.Transparency}) in order to render
+     * color key masking (see PDF Spec 1.7 Chapter 8.9.6.4).
+     * Therefore only return true if image has fully transparent
+     * colors.
+     *
+     * @return true if it has at least one fully transparent color
      */
     boolean isTransparent();
 
index c7e516a63d459891175c10b732cc8b53793f9a17..fb362ac75566414e47a95aaa339c3741a3b62ebe 100644 (file)
@@ -304,13 +304,7 @@ public abstract class AbstractImageAdapter implements PDFImage {
 
     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 i;
index c1eaa07c93525927f777d4c700fe6fb11500b95a..c47f4e5077aaae4b5899cf58d02881366ccebf1b 100644 (file)
 /* $Id$ */
 
 package org.apache.fop.render.pdf;
+import java.awt.Color;
 import java.awt.color.ColorSpace;
 import java.awt.color.ICC_ColorSpace;
 import java.awt.color.ICC_Profile;
 import java.awt.image.ColorModel;
 import java.awt.image.IndexColorModel;
-import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -31,7 +31,6 @@ import java.io.OutputStream;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import org.apache.xmlgraphics.image.GraphicsUtil;
 import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
 import org.apache.xmlgraphics.ps.ImageEncodingHelper;
 
@@ -126,11 +125,8 @@ public class ImageRenderedAdapter extends AbstractImageAdapter {
             //TODO Implement code to combine image with background color if transparency is not
             //allowed (need BufferedImage support for that)
 
-            Raster raster = GraphicsUtil.getAlphaRaster(ri);
-            if (raster != null) {
-                AlphaRasterImage alphaImage = new AlphaRasterImage("Mask:" + getKey(), raster);
-                this.softMask = doc.addImage(null, alphaImage).makeReference();
-            }
+            AlphaRasterImage alphaImage = new AlphaRasterImage("Mask:" + getKey(), ri);
+            this.softMask = doc.addImage(null, alphaImage).makeReference();
         }
     }
 
@@ -154,12 +150,6 @@ public class ImageRenderedAdapter extends AbstractImageAdapter {
     /** {@inheritDoc} */
     @Override
     public boolean isTransparent() {
-        ColorModel cm = getEffectiveColorModel();
-        if (cm instanceof IndexColorModel) {
-            if (cm.getTransparency() == IndexColorModel.TRANSLUCENT) {
-                return true;
-            }
-        }
         return (getImage().getTransparentColor() != null);
     }
 
@@ -169,15 +159,23 @@ public class ImageRenderedAdapter extends AbstractImageAdapter {
         ColorModel cm = getEffectiveColorModel();
         if (cm instanceof IndexColorModel) {
             IndexColorModel icm = (IndexColorModel)cm;
-            if (cm.getTransparency() == IndexColorModel.TRANSLUCENT) {
+            if (cm.getTransparency() == IndexColorModel.TRANSLUCENT
+                    || cm.getTransparency() == IndexColorModel.BITMASK) {
                 int transPixel = icm.getTransparentPixel();
-                return new PDFColor(
-                        icm.getRed(transPixel),
-                        icm.getGreen(transPixel),
-                        icm.getBlue(transPixel));
+                if (transPixel != -1) {
+                    return new PDFColor(
+                            icm.getRed(transPixel),
+                            icm.getGreen(transPixel),
+                            icm.getBlue(transPixel));
+                }
             }
         }
-        return new PDFColor(getImage().getTransparentColor());
+        Color transColor = getImage().getTransparentColor();
+        if (transColor != null) {
+            return new PDFColor(transColor);
+        } else {
+            return null;
+        }
     }
 
     /** {@inheritDoc} */
diff --git a/fop-core/src/test/java/org/apache/fop/pdf/PDFImageXObjectTestCase.java b/fop-core/src/test/java/org/apache/fop/pdf/PDFImageXObjectTestCase.java
new file mode 100644 (file)
index 0000000..0f21b04
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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.pdf;
+
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
+
+import org.apache.fop.render.pdf.ImageRenderedAdapter;
+import org.apache.fop.render.pdf.ImageRenderedAdapterTestCase;
+
+public class PDFImageXObjectTestCase {
+
+    /**
+     * FOP-2847: tests whether images with index color model returns a valid color key mask</p>
+     */
+    @Test
+    public void testPDFImageXObjectHasCorrectMaskForSemiTransparentIndexColorModel() {
+
+        RenderedImage ri = ImageRenderedAdapterTestCase.createRenderedImageWithIndexColorModel(false);
+
+        ImageRendered ir = mock(ImageRendered.class);
+        when(ir.getRenderedImage()).thenReturn(ri);
+        ImageInfo ii = mock(ImageInfo.class);
+        when(ir.getInfo()).thenReturn(ii);
+        ImageRenderedAdapter ira = new ImageRenderedAdapter(ir, "mock");
+
+        PDFDocument doc = ImageRenderedAdapterTestCase.createPDFDocumentFromRenderedImage();
+        ira.setup(doc);
+
+        AbstractPDFStream pdfImageXObject = new PDFImageXObject(0, ira);
+        pdfImageXObject.populateStreamDict(null);
+
+        /*
+         *  Currently FOP may generate a color key mask (/Mask) as well
+         *  as the more flexible (/SMask) both for a single transparent image.
+         *  That seems and actually is redundant, but it may help limited
+         *  PDF viewers to show at least the fully transparent parts (/Mask),
+         *  while omitting the translucent ones (/SMask).
+         *
+         *  If it contains a /Mask, then make sure it has only length 2.
+         *  Length 2 actually means it holds the two bounds (min/max) as
+         *  single 8-bit values see section 8.9.6.4 color key masking
+         *  of PDF Spec 1.7.
+         */
+        assertTrue(ri.getColorModel() instanceof IndexColorModel);
+        Object obj = pdfImageXObject.getDictionary().get("Mask");
+        if (obj != null && obj instanceof PDFArray) {
+            assertEquals(2, ((PDFArray) obj).length());
+        }
+    }
+
+    /**
+     * FOP-2847: tests whether images with index color model return a valid color key mask</p>
+     */
+    @Test
+    public void testPDFImageXObjectHasCorrectMaskForFullyTransparentIndexColorModel() {
+
+        RenderedImage ri = ImageRenderedAdapterTestCase.createRenderedImageWithIndexColorModel(true);
+
+        ImageRendered ir = mock(ImageRendered.class);
+        when(ir.getRenderedImage()).thenReturn(ri);
+        ImageInfo ii = mock(ImageInfo.class);
+        when(ir.getInfo()).thenReturn(ii);
+        ImageRenderedAdapter ira = new ImageRenderedAdapter(ir, "mock");
+
+        PDFDocument doc = ImageRenderedAdapterTestCase.createPDFDocumentFromRenderedImage();
+        ira.setup(doc);
+
+        AbstractPDFStream pdfImageXObject = new PDFImageXObject(0, ira);
+        pdfImageXObject.populateStreamDict(null);
+
+        assertTrue(ri.getColorModel() instanceof IndexColorModel);
+        Object obj = pdfImageXObject.getDictionary().get("Mask");
+        if (obj != null && obj instanceof PDFArray) {
+            assertEquals(2, ((PDFArray) obj).length());
+        }
+    }
+}
diff --git a/fop-core/src/test/java/org/apache/fop/render/pdf/ImageRenderedAdapterTestCase.java b/fop-core/src/test/java/org/apache/fop/render/pdf/ImageRenderedAdapterTestCase.java
new file mode 100644 (file)
index 0000000..8cb5278
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * 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.Color;
+import java.awt.color.ICC_Profile;
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
+
+import org.apache.fop.pdf.PDFAMode;
+import org.apache.fop.pdf.PDFDictionary;
+import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFFactory;
+import org.apache.fop.pdf.PDFICCBasedColorSpace;
+import org.apache.fop.pdf.PDFICCStream;
+import org.apache.fop.pdf.PDFImage;
+import org.apache.fop.pdf.PDFImageXObject;
+import org.apache.fop.pdf.PDFProfile;
+import org.apache.fop.pdf.PDFResourceContext;
+import org.apache.fop.pdf.PDFResources;
+
+public class ImageRenderedAdapterTestCase {
+
+    /**
+     * tests whether ARGB images return a soft mask
+     */
+    @Test
+    public void testSetupWithARGBReturnsSoftMask() {
+        RenderedImage ri = createRenderedImageWithRGBA();
+
+        ImageRendered ir = mock(ImageRendered.class);
+        when(ir.getRenderedImage()).thenReturn(ri);
+        ImageInfo ii = mock(ImageInfo.class);
+        when(ir.getInfo()).thenReturn(ii);
+        ImageRenderedAdapter ira = new ImageRenderedAdapter(ir, "mock");
+
+        PDFDocument doc = createPDFDocumentFromRenderedImage();
+        PDFDictionary dict = new PDFDictionary();
+        ira.setup(doc);
+        ira.populateXObjectDictionary(dict);
+        assertNotNull(ira.getSoftMaskReference());
+    }
+
+    /**
+     * FOP-2847: tests whether images with index color model return a soft mask</p>
+     */
+    @Test
+    public void testSetupWithIndexColorModelSemiTransparentReturnsSoftMask() {
+        RenderedImage ri = createRenderedImageWithIndexColorModel(false);
+
+        ImageRendered ir = mock(ImageRendered.class);
+        when(ir.getRenderedImage()).thenReturn(ri);
+        ImageInfo ii = mock(ImageInfo.class);
+        when(ir.getInfo()).thenReturn(ii);
+        ImageRenderedAdapter ira = new ImageRenderedAdapter(ir, "mock");
+
+        PDFDocument doc = createPDFDocumentFromRenderedImage();
+        PDFDictionary dict = new PDFDictionary();
+        ira.setup(doc);
+        ira.populateXObjectDictionary(dict);
+        assertNotNull(ira.getSoftMaskReference());
+    }
+
+    /**
+     * FOP-2847: tests whether images with index color model return a soft mask</p>
+     */
+    @Test
+    public void testSetupWithIndexColorModelFullyTransparentReturnsSoftMask() {
+        RenderedImage ri = createRenderedImageWithIndexColorModel(true);
+
+        ImageRendered ir = mock(ImageRendered.class);
+        when(ir.getRenderedImage()).thenReturn(ri);
+        ImageInfo ii = mock(ImageInfo.class);
+        when(ir.getInfo()).thenReturn(ii);
+        ImageRenderedAdapter ira = new ImageRenderedAdapter(ir, "mock");
+
+        PDFDocument doc = createPDFDocumentFromRenderedImage();
+        PDFDictionary dict = new PDFDictionary();
+        ira.setup(doc);
+        ira.populateXObjectDictionary(dict);
+        assertNotNull(ira.getSoftMaskReference());
+    }
+
+    /**
+     * Creates a semi transparent 4x4 image in index color space.
+     *
+     * @param fullyTransparent true if image is supposed to have a fully
+     *        transparent color
+     * @return RenderedImage
+     */
+    public static RenderedImage createRenderedImageWithIndexColorModel(boolean fullyTransparent) {
+        /*
+         *  Define an index color model with just four colors. For reasons of
+         *  simplicity colors will be gray.
+         */
+        IndexColorModel cm;
+        if (fullyTransparent) {
+            byte[] i = {(byte)0x00, (byte)0x80, (byte)0xB0, (byte)0xF0};
+            cm = new IndexColorModel(8, 4, i, i, i, i);
+        } else {
+            byte[] i = {(byte)0x10, (byte)0x80, (byte)0xB0, (byte)0xF0};
+            cm = new IndexColorModel(8, 4, i, i, i, i);
+        }
+
+        // create a 4x4 image with just one uniform color
+        BufferedImage ri = new BufferedImage(4, 4, BufferedImage.TYPE_BYTE_INDEXED, cm);
+        for (int x = 0; x < 2; x++) {
+            for (int y = 0; y < 2; y++) {
+                Color c =  new Color(128, 128, 128, 128);
+                ri.setRGB(x, y, c.getRGB());
+            }
+        }
+        return ri;
+    }
+
+    /**
+     * creates a semi transparent 4x4 image in ABGR color space
+     *
+     * @return RenderedImage
+     */
+    static RenderedImage createRenderedImageWithRGBA() {
+        // create a 4x4 image
+        BufferedImage ri = new BufferedImage(4, 4, BufferedImage.TYPE_4BYTE_ABGR);
+        for (int x = 0; x < 2; x++) {
+            for (int y = 0; y < 2; y++) {
+                Color c =  new Color(128, 128, 128, 128);
+                ri.setRGB(x, y, c.getRGB());
+            }
+        }
+        return ri;
+    }
+
+    /**
+     * Create a mocked PDF document from RenderedImage.
+     *
+     * @return
+     */
+    public static PDFDocument createPDFDocumentFromRenderedImage() {
+        // mock PDFDocument
+        PDFDocument doc = mock(PDFDocument.class);
+        PDFResources resources = mock(PDFResources.class);
+        when(doc.getResources()).thenReturn(resources);
+
+        PDFProfile profile = mock(PDFProfile.class);
+        when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_2A);
+
+        PDFImageXObject pio = new PDFImageXObject(0, null);
+        pio.setObjectNumber(0);
+        when(doc.getProfile()).thenReturn(profile);
+        when(doc.addImage(any(PDFResourceContext.class), any(PDFImage.class))).thenReturn(pio);
+
+        // ICC Color info
+        PDFFactory factory = mock(PDFFactory.class);
+        PDFICCStream iccStream = mock(PDFICCStream.class);
+        ICC_Profile iccProfile = mock(ICC_Profile.class);
+        when(iccProfile.getNumComponents()).thenReturn(4);
+        when(iccStream.getICCProfile()).thenReturn(iccProfile);
+        when(factory.makePDFICCStream()).thenReturn(iccStream);
+        PDFICCBasedColorSpace iccbcs = new PDFICCBasedColorSpace(null, iccStream);
+        when(factory.makeICCBasedColorSpace(null, null, iccStream)).thenReturn(iccbcs);
+        when(doc.getFactory()).thenReturn(factory);
+
+        return doc;
+    }
+}