git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1856423 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_4
package org.apache.fop.pdf; | package org.apache.fop.pdf; | ||||
import java.awt.Graphics2D; | |||||
import java.awt.image.BufferedImage; | |||||
import java.awt.image.DataBuffer; | import java.awt.image.DataBuffer; | ||||
import java.awt.image.Raster; | import java.awt.image.Raster; | ||||
import java.awt.image.RenderedImage; | import java.awt.image.RenderedImage; | ||||
* @param image the image (must have an alpha channel) | * @param image the image (must have an alpha channel) | ||||
*/ | */ | ||||
public AlphaRasterImage(String k, RenderedImage image) { | 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} */ | /** {@inheritDoc} */ |
/** | /** | ||||
* Check if this image has a transparent color transparency. | * 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(); | boolean isTransparent(); | ||||
private static Integer getIndexOfFirstTransparentColorInPalette(IndexColorModel icm) { | private static Integer getIndexOfFirstTransparentColorInPalette(IndexColorModel icm) { | ||||
byte[] alphas = new byte[icm.getMapSize()]; | 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.getAlphas(alphas); | ||||
icm.getReds(reds); | |||||
icm.getGreens(greens); | |||||
icm.getBlues(blues); | |||||
for (int i = 0; i < icm.getMapSize(); i++) { | for (int i = 0; i < icm.getMapSize(); i++) { | ||||
if ((alphas[i] & 0xFF) == 0) { | if ((alphas[i] & 0xFF) == 0) { | ||||
return i; | return i; |
/* $Id$ */ | /* $Id$ */ | ||||
package org.apache.fop.render.pdf; | package org.apache.fop.render.pdf; | ||||
import java.awt.Color; | |||||
import java.awt.color.ColorSpace; | import java.awt.color.ColorSpace; | ||||
import java.awt.color.ICC_ColorSpace; | import java.awt.color.ICC_ColorSpace; | ||||
import java.awt.color.ICC_Profile; | import java.awt.color.ICC_Profile; | ||||
import java.awt.image.ColorModel; | import java.awt.image.ColorModel; | ||||
import java.awt.image.IndexColorModel; | import java.awt.image.IndexColorModel; | ||||
import java.awt.image.Raster; | |||||
import java.awt.image.RenderedImage; | import java.awt.image.RenderedImage; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStream; | import java.io.OutputStream; | ||||
import org.apache.commons.logging.Log; | import org.apache.commons.logging.Log; | ||||
import org.apache.commons.logging.LogFactory; | import org.apache.commons.logging.LogFactory; | ||||
import org.apache.xmlgraphics.image.GraphicsUtil; | |||||
import org.apache.xmlgraphics.image.loader.impl.ImageRendered; | import org.apache.xmlgraphics.image.loader.impl.ImageRendered; | ||||
import org.apache.xmlgraphics.ps.ImageEncodingHelper; | import org.apache.xmlgraphics.ps.ImageEncodingHelper; | ||||
//TODO Implement code to combine image with background color if transparency is not | //TODO Implement code to combine image with background color if transparency is not | ||||
//allowed (need BufferedImage support for that) | //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(); | |||||
} | } | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public boolean isTransparent() { | public boolean isTransparent() { | ||||
ColorModel cm = getEffectiveColorModel(); | |||||
if (cm instanceof IndexColorModel) { | |||||
if (cm.getTransparency() == IndexColorModel.TRANSLUCENT) { | |||||
return true; | |||||
} | |||||
} | |||||
return (getImage().getTransparentColor() != null); | return (getImage().getTransparentColor() != null); | ||||
} | } | ||||
ColorModel cm = getEffectiveColorModel(); | ColorModel cm = getEffectiveColorModel(); | ||||
if (cm instanceof IndexColorModel) { | if (cm instanceof IndexColorModel) { | ||||
IndexColorModel icm = (IndexColorModel)cm; | IndexColorModel icm = (IndexColorModel)cm; | ||||
if (cm.getTransparency() == IndexColorModel.TRANSLUCENT) { | |||||
if (cm.getTransparency() == IndexColorModel.TRANSLUCENT | |||||
|| cm.getTransparency() == IndexColorModel.BITMASK) { | |||||
int transPixel = icm.getTransparentPixel(); | 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} */ | /** {@inheritDoc} */ |
/* | |||||
* 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()); | |||||
} | |||||
} | |||||
} |
/* | |||||
* 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; | |||||
} | |||||
} |