Browse Source

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
tags/fop-2_4
Simon Steiner 5 years ago
parent
commit
eff7c6c6d8

+ 36
- 1
fop-core/src/main/java/org/apache/fop/pdf/AlphaRasterImage.java View 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} */

+ 8
- 1
fop-core/src/main/java/org/apache/fop/pdf/PDFImage.java View 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();


+ 0
- 6
fop-core/src/main/java/org/apache/fop/render/pdf/AbstractImageAdapter.java View 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;

+ 17
- 19
fop-core/src/main/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java View File

@@ -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} */

+ 105
- 0
fop-core/src/test/java/org/apache/fop/pdf/PDFImageXObjectTestCase.java View File

@@ -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());
}
}
}

+ 193
- 0
fop-core/src/test/java/org/apache/fop/render/pdf/ImageRenderedAdapterTestCase.java View File

@@ -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;
}
}

Loading…
Cancel
Save