FOP-3211: Add option for native embed of compressed images in AFP by João André Gonçalves
diff --git a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java
index 987102a..ee41d66 100644
--- a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java
+++ b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java
@@ -199,7 +199,7 @@
public IncludeObject createInclude(String includeName, AFPDataObjectInfo dataObjectInfo) {
IncludeObject includeObj = factory.createInclude(includeName);
- if (dataObjectInfo instanceof AFPImageObjectInfo) {
+ if (dataObjectInfo.isUseIocaImages() && dataObjectInfo instanceof AFPImageObjectInfo) {
// IOCA image object
includeObj.setObjectType(IncludeObject.TYPE_IMAGE);
} else if (dataObjectInfo instanceof AFPGraphicsObjectInfo) {
diff --git a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java
index dc828a5..55c0cd5 100644
--- a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java
+++ b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java
@@ -59,6 +59,11 @@
/** controls the mapping of the image data into the image area */
private byte mappingOption = MappingOptionTriplet.SCALE_TO_FILL;
+ /**
+ * decides wether we use ioca images or object containers
+ */
+ private boolean useIocaImages = true;
+
public static final byte DEFAULT_MAPPING_OPTION = 0x00;
/**
@@ -282,6 +287,14 @@
return mappingOption;
}
+ public void setUseIocaImages(boolean useIocaImages) {
+ this.useIocaImages = useIocaImages;
+ }
+
+ public boolean isUseIocaImages() {
+ return useIocaImages;
+ }
+
/** {@inheritDoc} */
public String toString() {
return "AFPDataObjectInfo{"
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 017fc07..62303d9 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
@@ -57,6 +57,8 @@
/** image encoding quality setting (0.0f..1.0f) */
private float bitmapEncodingQuality;
+ private boolean useIocaImages = true;
+
/** color image handler */
private transient ColorConverter colorConverter;
@@ -330,6 +332,24 @@
}
/**
+ * Gets the ioca image setting
+ *
+ * @return true by default
+ */
+ public boolean isUseIocaImages() {
+ return this.useIocaImages;
+ }
+
+ /**
+ * Sets the tag that decides if we use ioca images or object containers
+ *
+ * @param useIocaImages true by default
+ */
+ public void setUseIocaImages(boolean useIocaImages) {
+ this.useIocaImages = useIocaImages;
+ }
+
+ /**
* Sets the output/device resolution
*
* @param resolution
diff --git a/fop-core/src/main/java/org/apache/fop/afp/AFPResourceManager.java b/fop-core/src/main/java/org/apache/fop/afp/AFPResourceManager.java
index 459f0e9..c96c300 100644
--- a/fop-core/src/main/java/org/apache/fop/afp/AFPResourceManager.java
+++ b/fop-core/src/main/java/org/apache/fop/afp/AFPResourceManager.java
@@ -172,7 +172,7 @@
Registry.ObjectType objectType = null;
// new resource so create
- if (dataObjectInfo instanceof AFPImageObjectInfo) {
+ if (dataObjectInfo.isUseIocaImages() && dataObjectInfo instanceof AFPImageObjectInfo) {
AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)dataObjectInfo;
namedObj = dataObjectFactory.createImage(imageObjectInfo);
} else if (dataObjectInfo instanceof AFPGraphicsObjectInfo) {
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 2a2b880..b1bb770 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
@@ -491,6 +491,13 @@
this.paintingState.setBitmapEncodingQuality(quality);
}
+ /**
+ * {@inheritDoc}
+ */
+ public void setUseIocaImages(boolean useIocaImages) {
+ this.paintingState.setUseIocaImages(useIocaImages);
+ }
+
/** {@inheritDoc} */
public void setShadingMode(AFPShadingMode shadingMode) {
this.shadingMode = shadingMode;
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 25bfd63..4111d09 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
@@ -301,6 +301,8 @@
}
}
+ imageObjectInfo.setUseIocaImages(paintingState.isUseIocaImages());
+
//TODO To reduce AFP file size, investigate using a compression scheme.
//Currently, all image data is uncompressed.
ColorModel cm = renderedImage.getColorModel();
@@ -357,6 +359,10 @@
paintingState.getResolution(),
baos);
imageObjectInfo.setCompression(ImageContent.COMPID_JPEG);
+
+ if (!paintingState.isUseIocaImages()) {
+ imageObjectInfo.setMimeType("image/jpeg");
+ }
} catch (IOException ioe) {
//Some JPEG codecs cannot encode CMYK
helper.encode(baos);
@@ -380,7 +386,10 @@
(functionSet.equals(FunctionSet.FS11) || functionSet.equals(FunctionSet.FS45))
&& paintingState.getWrapPSeg()
);
- imageObjectInfo.setMimeType(functionSet.getMimeType());
+ if (imageObjectInfo.getMimeType() == null) {
+ imageObjectInfo.setMimeType(functionSet.getMimeType());
+ }
+
imageObjectInfo.setData(imageData);
return imageObjectInfo;
}
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 db8162d..fe1978a 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
@@ -60,6 +60,7 @@
import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_WRAP_PSEG;
import static org.apache.fop.render.afp.AFPRendererOption.JPEG_ALLOW_JPEG_EMBEDDING;
import static org.apache.fop.render.afp.AFPRendererOption.JPEG_BITMAP_ENCODING_QUALITY;
+import static org.apache.fop.render.afp.AFPRendererOption.JPEG_USE_IOCA_IMAGES;
import static org.apache.fop.render.afp.AFPRendererOption.LINE_WIDTH_CORRECTION;
import static org.apache.fop.render.afp.AFPRendererOption.RENDERER_RESOLUTION;
import static org.apache.fop.render.afp.AFPRendererOption.RESOURCE_GROUP_URI;
@@ -188,6 +189,10 @@
return getParam(JPEG_BITMAP_ENCODING_QUALITY, Float.class);
}
+ public Boolean isUseIocaImages() {
+ return getParam(JPEG_USE_IOCA_IMAGES, Boolean.class);
+ }
+
public Float getLineWidthCorrection() {
return getParam(LINE_WIDTH_CORRECTION, Float.class);
}
@@ -338,10 +343,12 @@
Configuration jpegConfig = imagesCfg.getChild(IMAGES_JPEG.getName());
float bitmapEncodingQuality = 1.0f;
boolean allowJpegEmbedding = false;
+ boolean useIocaImages = true;
if (jpegConfig != null) {
allowJpegEmbedding = jpegConfig.getAttributeAsBoolean(
JPEG_ALLOW_JPEG_EMBEDDING.getName(),
false);
+ useIocaImages = jpegConfig.getAttributeAsBoolean(JPEG_USE_IOCA_IMAGES.getName(), true);
String bitmapEncodingQualityStr = jpegConfig.getAttribute(
JPEG_BITMAP_ENCODING_QUALITY.getName(), null);
if (bitmapEncodingQualityStr != null) {
@@ -354,6 +361,7 @@
}
setParam(JPEG_BITMAP_ENCODING_QUALITY, bitmapEncodingQuality);
setParam(JPEG_ALLOW_JPEG_EMBEDDING, allowJpegEmbedding);
+ setParam(JPEG_USE_IOCA_IMAGES, useIocaImages);
}
private void createResourceGroupFile() throws FOPException {
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 99a015f..803a5c0 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
@@ -108,6 +108,9 @@
if (config.getBitmapEncodingQuality() != null) {
documentHandler.setBitmapEncodingQuality(config.getBitmapEncodingQuality());
}
+ if (config.isUseIocaImages() != null) {
+ documentHandler.setUseIocaImages(config.isUseIocaImages());
+ }
if (config.getLineWidthCorrection() != null) {
documentHandler.setLineWidthCorrection(config.getLineWidthCorrection());
}
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 cdd6fba..d0721c2 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
@@ -38,6 +38,7 @@
IMAGES_WRAP_PSEG("pseg", Boolean.class),
JPEG_ALLOW_JPEG_EMBEDDING("allow-embedding", Boolean.class),
JPEG_BITMAP_ENCODING_QUALITY("bitmap-encoding-quality", Float.class),
+ JPEG_USE_IOCA_IMAGES("use-ioca-images", Boolean.class),
RENDERER_RESOLUTION("renderer-resolution", Integer.class),
RESOURCE_GROUP_URI("resource-group-file", URI.class),
SHADING("shading", AFPShadingMode.class),
diff --git a/fop-core/src/test/java/org/apache/fop/afp/AFPImageHandlerRenderedImageTestCase.java b/fop-core/src/test/java/org/apache/fop/afp/AFPImageHandlerRenderedImageTestCase.java
new file mode 100644
index 0000000..a1fc9f3
--- /dev/null
+++ b/fop-core/src/test/java/org/apache/fop/afp/AFPImageHandlerRenderedImageTestCase.java
@@ -0,0 +1,154 @@
+/*
+ * 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.afp;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.ImageSize;
+import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
+
+import org.apache.fop.apps.io.InternalResourceResolver;
+import org.apache.fop.apps.io.ResourceResolverFactory;
+
+import org.apache.fop.render.afp.AFPImageHandlerRenderedImage;
+import org.apache.fop.render.afp.AFPParser;
+import org.apache.fop.render.afp.AFPRenderingContext;
+
+/**
+ * A test class for testing AFP events.
+ */
+public class AFPImageHandlerRenderedImageTestCase {
+
+ class MyAFPResourceManager extends AFPResourceManager {
+ AFPDataObjectInfo dataObjectInfo;
+
+ MyAFPResourceManager() {
+ super(null);
+ }
+
+ public void createObject(AFPDataObjectInfo dataObjectInfo) {
+ this.dataObjectInfo = dataObjectInfo;
+ }
+ }
+
+ @Test
+ public void testAfpUsesObjectContainerUseIocaImagesFalse() throws IOException {
+ runAfpImageTest(false,
+ "Must use an object container when use IOCA images is false",
+ "BEGIN RESOURCE_GROUP RG000001\n"
+ + "BEGIN NAME_RESOURCE RES00001 Triplets: "
+ + "OBJECT_FUNCTION_SET_SPECIFICATION,OBJECT_CLASSIFICATION,\n"
+ + "BEGIN OBJECT_CONTAINER OC000001 Triplets: 0x01,0x00,0x00,\n"
+ + "DATA OBJECT_CONTAINER\n"
+ + "DATA OBJECT_CONTAINER\n"
+ + "END OBJECT_CONTAINER OC000001\n"
+ + "END NAME_RESOURCE RES00001\n");
+ }
+
+ @Test
+ public void testAfpUsesImageByDefault() throws IOException {
+ runAfpImageTest(true, "Must use an IOCA image structure",
+ "BEGIN RESOURCE_GROUP RG000001\n"
+ + "BEGIN NAME_RESOURCE RES00001 Triplets: OBJECT_FUNCTION_SET_SPECIFICATION,\n"
+ + "BEGIN IMAGE IMG00001\n"
+ + "BEGIN OBJECT_ENVIRONMENT_GROUP OEG00001\n"
+ + "DESCRIPTOR OBJECT_AREA Triplets: DESCRIPTOR_POSITION,MEASUREMENT_UNITS,OBJECT_AREA_SIZE,\n"
+ + "POSITION OBJECT_AREA\n"
+ + "MAP IMAGE Triplets: MAPPING_OPTION,\n"
+ + "DESCRIPTOR IMAGE\n"
+ + "END OBJECT_ENVIRONMENT_GROUP OEG00001\n"
+ + "DATA IMAGE\n"
+ + "DATA IMAGE\n"
+ + "END IMAGE IMG00001\n"
+ + "END NAME_RESOURCE RES00001\n");
+ }
+
+ private void runAfpImageTest(boolean useIocaImages, String assertionMessage, String afpContent) throws IOException {
+ InternalResourceResolver rr =
+ ResourceResolverFactory.createDefaultInternalResourceResolver(new File(".").toURI());
+ AFPResourceManager afpResourceManager = new AFPResourceManager(rr);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ AFPPaintingState paintingState = new AFPPaintingState();
+ assertTrue("Use IOCA images must be true by default", paintingState.isUseIocaImages());
+ paintingState.setUseIocaImages(useIocaImages);
+
+ DataStream ds = afpResourceManager.createDataStream(null, bos);
+ ds.startPage(0, 0, 0, 0, 0);
+
+ handleImage(BufferedImage.TYPE_INT_ARGB, afpResourceManager, paintingState);
+
+ StringBuilder sb = new StringBuilder();
+ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+ new AFPParser(false).read(bis, sb);
+
+ assertEquals(assertionMessage, afpContent, sb.toString());
+ }
+
+ @Test
+ public void checkMimeTypeTrueUseIocaImages() throws IOException {
+ MyAFPResourceManager afpResourceManager = new MyAFPResourceManager();
+
+ AFPPaintingState paintingState = new AFPPaintingState();
+ paintingState.setUseIocaImages(true);
+ paintingState.setBitsPerPixel(24);
+
+ handleImage(BufferedImage.TYPE_BYTE_GRAY, afpResourceManager, paintingState);
+ assertEquals("Must not use image/jpeg as it will be set as an ioca image",
+ "image/x-afp+fs11", afpResourceManager.dataObjectInfo.getMimeType());
+ }
+
+ @Test
+ public void checkMimeTypeFalseUseIocaImages() throws IOException {
+ MyAFPResourceManager afpResourceManager = new MyAFPResourceManager();
+
+ AFPPaintingState paintingState = new AFPPaintingState();
+ paintingState.setBitsPerPixel(8);
+
+ paintingState.setUseIocaImages(false);
+ handleImage(BufferedImage.TYPE_BYTE_GRAY, afpResourceManager, paintingState);
+ assertEquals("Must use image/jpeg and the image will be stored using an object container",
+ "image/jpeg", afpResourceManager.dataObjectInfo.getMimeType());
+ }
+
+ private void handleImage(int type, AFPResourceManager afpResourceManager, AFPPaintingState paintingState)
+ throws IOException {
+ BufferedImage img = new BufferedImage(100, 100, type);
+ ImageInfo info = new ImageInfo("a", null);
+ info.setSize(new ImageSize(100, 100, 72));
+ ImageRendered imageRendered = new ImageRendered(info, img, null);
+ AFPImageHandlerRenderedImage imageHandlerRenderedImage = new AFPImageHandlerRenderedImage();
+ AFPRenderingContext afpRenderingContext = new AFPRenderingContext(null, afpResourceManager,
+ paintingState, null, null);
+ imageHandlerRenderedImage.handleImage(afpRenderingContext, imageRendered, new Rectangle());
+ }
+}
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 1793499..1309711 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
@@ -43,6 +43,7 @@
import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_WRAP_PSEG;
import static org.apache.fop.render.afp.AFPRendererOption.JPEG_ALLOW_JPEG_EMBEDDING;
import static org.apache.fop.render.afp.AFPRendererOption.JPEG_BITMAP_ENCODING_QUALITY;
+import static org.apache.fop.render.afp.AFPRendererOption.JPEG_USE_IOCA_IMAGES;
import static org.apache.fop.render.afp.AFPRendererOption.LINE_WIDTH_CORRECTION;
import static org.apache.fop.render.afp.AFPRendererOption.RENDERER_RESOLUTION;
import static org.apache.fop.render.afp.AFPRendererOption.RESOURCE_GROUP_URI;
@@ -140,6 +141,11 @@
return this;
}
+ public ImagesBuilder setUseIocaImages(boolean useIocaImages) {
+ getJpeg().setAttribute(JPEG_USE_IOCA_IMAGES.getName(), String.valueOf(useIocaImages));
+ return this;
+ }
+
public ImagesBuilder setDitheringQuality(String value) {
return setAttribute(IMAGES_DITHERING_QUALITY, 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 fad1b07..640fee5 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
@@ -26,8 +26,10 @@
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import org.apache.fop.afp.AFPConstants;
import org.apache.fop.apps.AFPRendererConfBuilder;
@@ -171,6 +173,14 @@
}
@Test
+ public void testUseIocaImages() throws Exception {
+ parseConfig();
+ assertTrue(conf.isUseIocaImages());
+ parseConfig(createRenderer().startImages().setUseIocaImages(false).endImages());
+ assertFalse(conf.isUseIocaImages());
+ }
+
+ @Test
public void testFS45() throws Exception {
parseConfig();
assertEquals(false, conf.isFs45());
diff --git a/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java b/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java
index a473265..e02e746 100644
--- a/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java
+++ b/fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java
@@ -212,6 +212,14 @@
}
@Test
+ public void testUseIocaImages() throws Exception {
+ parseConfig(createBuilder().startImages()
+ .setUseIocaImages(false)
+ .endImages());
+ verify(getDocHandler()).setUseIocaImages(false);
+ }
+
+ @Test
public void testCanEmbedJpeg() throws Exception {
parseConfig(createBuilder().startImages()
.setAllowJpegEmbedding(true)