git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1856423 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_4
@@ -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} */ |
@@ -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(); | |||
@@ -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; |
@@ -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} */ |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |