aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Steiner <ssteiner@apache.org>2024-07-24 13:08:11 +0100
committerSimon Steiner <ssteiner@apache.org>2024-07-24 13:08:11 +0100
commit1ce1125e8560afa59b629ba1e9217ceac13f2792 (patch)
tree1612af6f1b4fd0c211af9a83ac2e2f414bec22fe
parentf9404c6f4a07e15a45f00db456f55259844a960c (diff)
downloadxmlgraphics-fop-1ce1125e8560afa59b629ba1e9217ceac13f2792.tar.gz
xmlgraphics-fop-1ce1125e8560afa59b629ba1e9217ceac13f2792.zip
FOP-3194: Add image mask option for AFP
-rw-r--r--fop-core/src/main/java/org/apache/fop/afp/AFPPaintingState.java8
-rw-r--r--fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java5
-rw-r--r--fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java10
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java4
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java34
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfig.java6
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererConfigurator.java3
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/afp/AFPRendererOption.java1
-rw-r--r--fop-core/src/test/java/org/apache/fop/afp/AFPGraphics2DTestCase.java61
-rw-r--r--fop-core/src/test/java/org/apache/fop/apps/AFPRendererConfBuilder.java5
-rw-r--r--fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfigParserTestCase.java8
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());