aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Steiner <ssteiner@apache.org>2019-03-27 16:06:24 +0000
committerSimon Steiner <ssteiner@apache.org>2019-03-27 16:06:24 +0000
commiteff7c6c6d80501dcce34df54936f289e5c11703d (patch)
tree001662a5d2e890a85d719d50fa91d2fa61c0d6c7
parent5a40c4dfd11230414a729347f136c1e670b4e9e9 (diff)
downloadxmlgraphics-fop-eff7c6c6d80501dcce34df54936f289e5c11703d.tar.gz
xmlgraphics-fop-eff7c6c6d80501dcce34df54936f289e5c11703d.zip
FOP-2847: Support palette-based transparency PNG in PDF
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1856423 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/AlphaRasterImage.java37
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/PDFImage.java9
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/pdf/AbstractImageAdapter.java6
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java36
-rw-r--r--fop-core/src/test/java/org/apache/fop/pdf/PDFImageXObjectTestCase.java105
-rw-r--r--fop-core/src/test/java/org/apache/fop/render/pdf/ImageRenderedAdapterTestCase.java193
6 files changed, 359 insertions, 27 deletions
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/AlphaRasterImage.java b/fop-core/src/main/java/org/apache/fop/pdf/AlphaRasterImage.java
index 250b5daa2..3c17de148 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/AlphaRasterImage.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/AlphaRasterImage.java
@@ -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} */
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFImage.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFImage.java
index 38da60e7b..328931ca6 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFImage.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFImage.java
@@ -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();
diff --git a/fop-core/src/main/java/org/apache/fop/render/pdf/AbstractImageAdapter.java b/fop-core/src/main/java/org/apache/fop/render/pdf/AbstractImageAdapter.java
index c7e516a63..fb362ac75 100644
--- a/fop-core/src/main/java/org/apache/fop/render/pdf/AbstractImageAdapter.java
+++ b/fop-core/src/main/java/org/apache/fop/render/pdf/AbstractImageAdapter.java
@@ -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;
diff --git a/fop-core/src/main/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java b/fop-core/src/main/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java
index c1eaa07c9..c47f4e507 100644
--- a/fop-core/src/main/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java
+++ b/fop-core/src/main/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java
@@ -18,12 +18,12 @@
/* $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
index 000000000..0f21b0439
--- /dev/null
+++ b/fop-core/src/test/java/org/apache/fop/pdf/PDFImageXObjectTestCase.java
@@ -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
index 000000000..8cb52789d
--- /dev/null
+++ b/fop-core/src/test/java/org/apache/fop/render/pdf/ImageRenderedAdapterTestCase.java
@@ -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;
+ }
+}