]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-3194: Add image mask option for AFP
authorSimon Steiner <ssteiner@apache.org>
Wed, 24 Jul 2024 12:08:11 +0000 (13:08 +0100)
committerSimon Steiner <ssteiner@apache.org>
Wed, 24 Jul 2024 12:08:11 +0000 (13:08 +0100)
fop-core/src/main/java/org/apache/fop/afp/AFPPaintingState.java
fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java
fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java
fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java
fop-core/src/main/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java
fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfig.java
fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererOption.java
fop-core/src/test/java/org/apache/fop/afp/AFPGraphics2DTestCase.java
fop-core/src/test/java/org/apache/fop/apps/AFPRendererConfBuilder.java
fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfigParserTestCase.java

index 3b1354d911f3a6ba52c38b8bbf86a2722e518eb1..017fc076d3d9e0a486f56c14ddbcb35320606540 100644 (file)
@@ -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
index 617a962bacf193334c748ba3c76401375480eea1..6bab811211c8b15514613c854646f830b29b6c16 100644 (file)
@@ -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];
index 0697e427c0b305d374295c8c128beb8cdec93011..1239b12234f62895211af5c53df76010ebdb63a6 100644 (file)
@@ -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);
index a711282d23fc478ef73d50cf0398d4f63352d300..2a2b8803b9711177a0d8ea69ae4a903ecd735082 100644 (file)
@@ -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();
index 57a5143f6113e1825b0799f40c725f742a18fc8f..25bfd635e1c11665fbaf65755a8d260306423cda 100644 (file)
 
 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)
index 4eac4bae356e0fe3170388969c4f514afca134b8..db8162da9d498e80f369c65338d9299fd7836030 100644 (file)
@@ -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 {
index 46320779b7ab0d2886505e7957f8485fc83f3738..99a015fcc0d0116e97b8d381e3046270eb3942f8 100644 (file)
@@ -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());
         }
index b85ecf20b473814eb201c176c7653d03aadd34df..cdd6fba73bb00000bc89b7c9c2d5e4cacc13125e 100644 (file)
@@ -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),
index d6b14af0f75efceab149df43fec3e8d001477b15..7768800e6da3b679b2a5b4a79b6df1878920f981 100644 (file)
@@ -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 {
@@ -63,6 +73,57 @@ public class AFPGraphics2DTestCase {
         verify(gObject).setLineWidth(correctedLineWidth);
     }
 
+    @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();
index 87afac6370f5f0aeb7976bbd41d9f048f4083288..17934997aa2282bc9cb0b5a44d7a84600afd4836 100644 (file)
@@ -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);
         }
index a85cfb820035499006f18e108d8bce63046b71f0..fad1b07676e0459a8c74cb5c1821ac5043e831da 100644 (file)
@@ -178,6 +178,14 @@ public class AFPRendererConfigParserTestCase
         assertEquals(true, conf.isFs45());
     }
 
+    @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();