diff options
author | Simon Steiner <ssteiner@apache.org> | 2024-07-24 13:08:11 +0100 |
---|---|---|
committer | Simon Steiner <ssteiner@apache.org> | 2024-07-24 13:08:11 +0100 |
commit | 1ce1125e8560afa59b629ba1e9217ceac13f2792 (patch) | |
tree | 1612af6f1b4fd0c211af9a83ac2e2f414bec22fe | |
parent | f9404c6f4a07e15a45f00db456f55259844a960c (diff) | |
download | xmlgraphics-fop-1ce1125e8560afa59b629ba1e9217ceac13f2792.tar.gz xmlgraphics-fop-1ce1125e8560afa59b629ba1e9217ceac13f2792.zip |
FOP-3194: Add image mask option for AFP
11 files changed, 141 insertions, 4 deletions
diff --git a/fop-core/src/main/java/org/apache/fop/afp/AFPPaintingState.java b/fop-core/src/main/java/org/apache/fop/afp/AFPPaintingState.java index 3b1354d91..017fc076d 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/AFPPaintingState.java +++ b/fop-core/src/main/java/org/apache/fop/afp/AFPPaintingState.java @@ -99,6 +99,7 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState /** use FS45 images*/ private boolean fs45; + private boolean maskEnabled; /** the current page */ private transient AFPPagePaintingState pagePaintingState; @@ -443,6 +444,13 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState } + public boolean isMaskEnabled() { + return maskEnabled; + } + + public void setMaskEnabled(boolean maskEnabled) { + this.maskEnabled = maskEnabled; + } /** {@inheritDoc} */ @Override diff --git a/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java b/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java index 617a962ba..6bab81121 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java +++ b/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java @@ -69,7 +69,10 @@ public class Tile extends AbstractStructuredObject { 0x00 // reserved }; final int lengthOffset = 2; - if (ideSize == 24) { + if (ideSize <= 8) { + dataHeader[4] = (byte) 0x01; + writeChunksToStream(data, dataHeader, lengthOffset, MAX_DATA_LEN, os); + } else if (ideSize == 24) { byte[] red = new byte[data.length / 3]; byte[] green = new byte[data.length / 3]; byte[] blue = new byte[data.length / 3]; diff --git a/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java b/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java index 0697e427c..1239b1223 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java +++ b/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java @@ -79,9 +79,13 @@ public class ImageObject extends AbstractDataObject { ImageDataDescriptor imageDataDescriptor = factory.createImageDataDescriptor(dataWidth, dataHeight, dataWidthRes, dataHeightRes); - if (MimeConstants.MIME_AFP_IOCA_FS45.equals(imageObjectInfo.getMimeType())) { - imageDataDescriptor.setFunctionSet(ImageDataDescriptor.FUNCTION_SET_FS45); - if (imageObjectInfo.getBitsPerPixel() == 32) { + boolean hasTransparencyMask = imageObjectInfo.getTransparencyMask() != null; + boolean fs45 = MimeConstants.MIME_AFP_IOCA_FS45.equals(imageObjectInfo.getMimeType()); + if (hasTransparencyMask || fs45) { + if (fs45) { + imageDataDescriptor.setFunctionSet(ImageDataDescriptor.FUNCTION_SET_FS45); + } + if (hasTransparencyMask || imageObjectInfo.getBitsPerPixel() == 32) { Tile tile = factory.createTile(); TilePosition tilePosition = factory.createTilePosition(); TileSize tileSize = factory.createTileSize(dataWidth, dataHeight, dataWidthRes, dataHeightRes); diff --git a/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java index a711282d2..2a2b8803b 100644 --- a/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java +++ b/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java @@ -545,6 +545,10 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler paintingState.setFS45(fs45); } + public void setMaskEnabled(boolean maskEnabled) { + paintingState.setMaskEnabled(maskEnabled); + } + /** {@inheritDoc} */ public boolean getWrapPSeg() { return paintingState.getWrapPSeg(); diff --git a/fop-core/src/main/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java b/fop-core/src/main/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java index 57a5143f6..25bfd635e 100644 --- a/fop-core/src/main/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java +++ b/fop-core/src/main/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java @@ -19,8 +19,10 @@ package org.apache.fop.render.afp; +import java.awt.Color; import java.awt.Dimension; import java.awt.Rectangle; +import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; @@ -131,6 +133,12 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima boolean included = afpContext.getResourceManager().tryIncludeObject(imageObjectInfo); if (!included) { long start = System.currentTimeMillis(); + RenderedImage ri = imageRend.getRenderedImage(); + if (ri.getColorModel().hasAlpha()) { + byte[] maskImage = buildMaskImage((BufferedImage) ri, afpContext.getPaintingState()); + imageObjectInfo.setTransparencyMask(maskImage); + } + //encode only if the same image has not been encoded, yet encoder.encodeImage(imageObjectInfo, paintingState); if (log.isDebugEnabled()) { @@ -143,6 +151,32 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima } } + private byte[] buildMaskImage(BufferedImage image, AFPPaintingState paintingState) { + if (!paintingState.isMaskEnabled()) { + return null; + } + BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); + int white = Color.WHITE.getRGB(); + boolean noalpha = true; + for (int i = 0; i < image.getWidth(); i++) { + for (int j = 0; j < image.getHeight(); j++) { + int alpha = (image.getRGB(i, j) >> 24) & 0xff; + if (alpha != 0) { + mask.setRGB(i, j, white); + } else { + noalpha = false; + } + } + } + if (noalpha) { + return null; + } + RenderedImage renderedImage = + BitmapImageUtil.convertToMonochrome(mask, new Dimension(mask.getWidth(), mask.getHeight()), 1); + DataBufferByte bufferByte = (DataBufferByte) renderedImage.getData().getDataBuffer(); + return bufferByte.getData(); + } + /** {@inheritDoc} */ public boolean isCompatible(RenderingContext targetContext, Image image) { return (image == null || image instanceof ImageRendered) diff --git a/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfig.java b/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfig.java index 4eac4bae3..db8162da9 100644 --- a/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfig.java +++ b/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfig.java @@ -54,6 +54,7 @@ import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_DITHERING_QUALI import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_FS45; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_JPEG; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_MAPPING_OPTION; +import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_MASK_ENABLED; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_MODE; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_NATIVE; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_WRAP_PSEG; @@ -175,6 +176,10 @@ public final class AFPRendererConfig implements RendererConfig { return getParam(IMAGES_FS45, Boolean.class); } + public Boolean isMaskEnabled() { + return getParam(IMAGES_MASK_ENABLED, Boolean.class); + } + public Boolean allowJpegEmbedding() { return getParam(JPEG_ALLOW_JPEG_EMBEDDING, Boolean.class); } @@ -315,6 +320,7 @@ public final class AFPRendererConfig implements RendererConfig { setParam(IMAGES_WRAP_PSEG, imagesCfg.getAttributeAsBoolean(IMAGES_WRAP_PSEG.getName(), false)); setParam(IMAGES_FS45, imagesCfg.getAttributeAsBoolean(IMAGES_FS45.getName(), false)); + setParam(IMAGES_MASK_ENABLED, imagesCfg.getAttributeAsBoolean(IMAGES_MASK_ENABLED.getName(), false)); if ("scale-to-fit".equals(imagesCfg.getAttribute(IMAGES_MAPPING_OPTION.getName(), null))) { setParam(IMAGES_MAPPING_OPTION, MappingOptionTriplet.SCALE_TO_FILL); } else { diff --git a/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfigurator.java b/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfigurator.java index 46320779b..99a015fcc 100644 --- a/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfigurator.java +++ b/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfigurator.java @@ -99,6 +99,9 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator { if (config.isFs45() != null) { documentHandler.setFS45(config.isFs45()); } + if (config.isMaskEnabled() != null) { + documentHandler.setMaskEnabled(config.isMaskEnabled()); + } if (config.allowJpegEmbedding() != null) { documentHandler.canEmbedJpeg(config.allowJpegEmbedding()); } diff --git a/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererOption.java b/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererOption.java index b85ecf20b..cdd6fba73 100644 --- a/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererOption.java +++ b/fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererOption.java @@ -31,6 +31,7 @@ public enum AFPRendererOption implements RendererConfigOption { IMAGES_JPEG("jpeg", null), IMAGES_DITHERING_QUALITY("dithering-quality", Float.class), IMAGES_FS45("fs45", Boolean.class), + IMAGES_MASK_ENABLED("mask-enabled", Boolean.class), IMAGES_MAPPING_OPTION("mapping_option", Byte.class), IMAGES_MODE("mode", Boolean.class), IMAGES_NATIVE("native", Boolean.class), diff --git a/fop-core/src/test/java/org/apache/fop/afp/AFPGraphics2DTestCase.java b/fop-core/src/test/java/org/apache/fop/afp/AFPGraphics2DTestCase.java index d6b14af0f..7768800e6 100644 --- a/fop-core/src/test/java/org/apache/fop/afp/AFPGraphics2DTestCase.java +++ b/fop-core/src/test/java/org/apache/fop/afp/AFPGraphics2DTestCase.java @@ -21,12 +21,17 @@ package org.apache.fop.afp; import java.awt.BasicStroke; import java.awt.Rectangle; +import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; +import javax.imageio.ImageIO; + import org.junit.Assert; import org.junit.Test; @@ -37,6 +42,11 @@ import static org.mockito.Mockito.when; import org.apache.xmlgraphics.java2d.GraphicContext; import org.apache.fop.afp.modca.GraphicsObject; +import org.apache.fop.afp.modca.ObjectAreaDescriptor; +import org.apache.fop.afp.parser.MODCAParser; +import org.apache.fop.afp.parser.UnparsedStructuredField; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.FopFactory; import org.apache.fop.fonts.FontInfo; public class AFPGraphics2DTestCase { @@ -64,6 +74,57 @@ public class AFPGraphics2DTestCase { } @Test + public void testDrawImageMask() throws IOException { + BufferedImage image = ImageIO.read(new File("test/resources/images/fop-logo-color-palette-8bit.png")); + byte[] data = getAFPField("Data Image", image, true); + ByteArrayInputStream bis = new ByteArrayInputStream(data); + bis.skip(56); + Assert.assertEquals(bis.read(), 0x8E); //start mask + bis.skip(20); + byte[] firstchunk = new byte[5272]; + bis.read(firstchunk); + int maskbytes = 0; + for (byte b : firstchunk) { + if (b != 0) { + maskbytes++; + } + } + Assert.assertEquals(maskbytes, 333); + bis.skip(38117 - 57 - 20 - firstchunk.length); + Assert.assertEquals(bis.read(), 0x8F); //end mask + Assert.assertEquals(bis.available(), 302498); + } + + private byte[] getAFPField(String field, BufferedImage image, boolean maskEnabled) throws IOException { + FOUserAgent foUserAgent = FopFactory.newInstance(new File(".").toURI()).newFOUserAgent(); + AFPResourceManager afpResourceManager = new AFPResourceManager(foUserAgent.getResourceResolver()); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataStream dataStream = afpResourceManager.createDataStream(null, byteArrayOutputStream); + dataStream.startDocument(); + dataStream.startPage(1, 1, 0, 1, 1); + AFPPaintingState paintingState = new AFPPaintingState(); + paintingState.setMaskEnabled(maskEnabled); + graphics2D = new AFPGraphics2D(false, paintingState, afpResourceManager, resourceInfo, fontInfo); + graphics2D.setGraphicContext(new GraphicContext()); + GraphicsObject graphicsObject = new GraphicsObject(new Factory(), null); + graphics2D.setGraphicsObject(graphicsObject); + graphicsObject.getObjectEnvironmentGroup().setObjectAreaDescriptor(new ObjectAreaDescriptor(1, 1, 1, 1)); + graphics2D.drawRenderedImage(image, AffineTransform.getTranslateInstance(1000, 1000)); + dataStream.endPage(); + dataStream.endDocument(); + afpResourceManager.writeToStream(); + MODCAParser parser = new MODCAParser(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + UnparsedStructuredField structuredField; + while ((structuredField = parser.readNextStructuredField()) != null) { + if (structuredField.toString().contains(field)) { + bos.write(structuredField.getData()); + } + } + return bos.toByteArray(); + } + + @Test public void testDrawGraphicsFillet() throws IOException { GraphicContext gc = new GraphicContext(); gc.setClip(new Rectangle(0, 0, 100, 100)); diff --git a/fop-core/src/test/java/org/apache/fop/apps/AFPRendererConfBuilder.java b/fop-core/src/test/java/org/apache/fop/apps/AFPRendererConfBuilder.java index 87afac637..17934997a 100644 --- a/fop-core/src/test/java/org/apache/fop/apps/AFPRendererConfBuilder.java +++ b/fop-core/src/test/java/org/apache/fop/apps/AFPRendererConfBuilder.java @@ -37,6 +37,7 @@ import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_DITHERING_QUALI import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_FS45; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_JPEG; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_MAPPING_OPTION; +import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_MASK_ENABLED; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_MODE; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_NATIVE; import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_WRAP_PSEG; @@ -151,6 +152,10 @@ public final class AFPRendererConfBuilder extends RendererConfBuilder { return setAttribute(IMAGES_FS45, value); } + public ImagesBuilder setMaskEnabled(boolean value) { + return setAttribute(IMAGES_MASK_ENABLED, value); + } + public ImagesBuilder setMappingOption(String value) { return setAttribute(IMAGES_MAPPING_OPTION, value); } diff --git a/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfigParserTestCase.java b/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfigParserTestCase.java index a85cfb820..fad1b0767 100644 --- a/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfigParserTestCase.java +++ b/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfigParserTestCase.java @@ -179,6 +179,14 @@ public class AFPRendererConfigParserTestCase } @Test + public void testMaskEnabled() throws Exception { + parseConfig(); + assertEquals(false, conf.isMaskEnabled()); + parseConfig(createRenderer().startImages().setMaskEnabled(true).endImages()); + assertEquals(true, conf.isMaskEnabled()); + } + + @Test public void tesPseg() throws Exception { parseConfig(); assertEquals(false, conf.isWrapPseg()); |