]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-3211: Add option for native embed of compressed images in AFP by João André Gonçalves
authorSimon Steiner <ssteiner@apache.org>
Fri, 11 Oct 2024 07:36:27 +0000 (08:36 +0100)
committerSimon Steiner <ssteiner@apache.org>
Fri, 11 Oct 2024 07:40:05 +0000 (08:40 +0100)
13 files changed:
fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java
fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java
fop-core/src/main/java/org/apache/fop/afp/AFPPaintingState.java
fop-core/src/main/java/org/apache/fop/afp/AFPResourceManager.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/AFPImageHandlerRenderedImageTestCase.java [new file with mode: 0644]
fop-core/src/test/java/org/apache/fop/apps/AFPRendererConfBuilder.java
fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfigParserTestCase.java
fop-core/src/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java

index 987102af7ef4e1e8287dc8f385aa41f70aa6a3ac..ee41d66c78d30220ad14e1485805fbdee4f230bd 100644 (file)
@@ -199,7 +199,7 @@ public class AFPDataObjectFactory {
     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) {
index dc828a540ad9a8bdf5f482dcc2542108299a8cd1..55c0cd5278edd8609a521ad4f79bfec5e40f29da 100644 (file)
@@ -59,6 +59,11 @@ public class AFPDataObjectInfo {
     /** 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 @@ public class AFPDataObjectInfo {
         return mappingOption;
     }
 
+    public void setUseIocaImages(boolean useIocaImages) {
+        this.useIocaImages = useIocaImages;
+    }
+
+    public boolean isUseIocaImages() {
+        return useIocaImages;
+    }
+
     /** {@inheritDoc} */
     public String toString() {
         return "AFPDataObjectInfo{"
index 017fc076d3d9e0a486f56c14ddbcb35320606540..62303d9ea48e027f8156555f98e46229903e9297 100644 (file)
@@ -57,6 +57,8 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState
     /** image encoding quality setting (0.0f..1.0f) */
     private float bitmapEncodingQuality;
 
+    private boolean useIocaImages = true;
+
     /** color image handler */
     private transient ColorConverter colorConverter;
 
@@ -329,6 +331,24 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState
         this.bitmapEncodingQuality = quality;
     }
 
+    /**
+     * 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
      *
index 459f0e9a66920d5bba4c226dd01610add19c1baa..c96c300f06d2fbe7768498d38504a328b8aef550 100644 (file)
@@ -172,7 +172,7 @@ public class AFPResourceManager {
         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) {
index 2a2b8803b9711177a0d8ea69ae4a903ecd735082..b1bb7701d2703ee1a9e26c828582ac814598d617 100644 (file)
@@ -491,6 +491,13 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler
         this.paintingState.setBitmapEncodingQuality(quality);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public void setUseIocaImages(boolean useIocaImages) {
+        this.paintingState.setUseIocaImages(useIocaImages);
+    }
+
     /** {@inheritDoc} */
     public void setShadingMode(AFPShadingMode shadingMode) {
         this.shadingMode = shadingMode;
index 25bfd635e1c11665fbaf65755a8d260306423cda..4111d09641072782884d60adaa51b0cd80ea3521 100644 (file)
@@ -301,6 +301,8 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima
                 }
             }
 
+            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 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima
                                     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 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima
                     (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;
         }
index db8162da9d498e80f369c65338d9299fd7836030..fe1978a025e92d3b724a7a3470c3c7e93aaee8ab 100644 (file)
@@ -60,6 +60,7 @@ import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_NATIVE;
 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 @@ public final class AFPRendererConfig implements RendererConfig {
         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 @@ public final class AFPRendererConfig implements RendererConfig {
             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 @@ public final class AFPRendererConfig implements RendererConfig {
             }
             setParam(JPEG_BITMAP_ENCODING_QUALITY, bitmapEncodingQuality);
             setParam(JPEG_ALLOW_JPEG_EMBEDDING, allowJpegEmbedding);
+            setParam(JPEG_USE_IOCA_IMAGES, useIocaImages);
         }
 
         private void createResourceGroupFile() throws FOPException {
index 99a015fcc0d0116e97b8d381e3046270eb3942f8..803a5c0d9b6ffb4a775881a6ea97a3e719528035 100644 (file)
@@ -108,6 +108,9 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator {
         if (config.getBitmapEncodingQuality() != null) {
             documentHandler.setBitmapEncodingQuality(config.getBitmapEncodingQuality());
         }
+        if (config.isUseIocaImages() != null) {
+            documentHandler.setUseIocaImages(config.isUseIocaImages());
+        }
         if (config.getLineWidthCorrection() != null) {
             documentHandler.setLineWidthCorrection(config.getLineWidthCorrection());
         }
index cdd6fba73bb00000bc89b7c9c2d5e4cacc13125e..d0721c26e09093c5940b910c65908a2d464a53f1 100644 (file)
@@ -38,6 +38,7 @@ public enum AFPRendererOption implements RendererConfigOption {
     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 (file)
index 0000000..a1fc9f3
--- /dev/null
@@ -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());
+    }
+}
index 17934997aa2282bc9cb0b5a44d7a84600afd4836..1309711756ca17888e1f1718f4c66bd4d9fe8b80 100644 (file)
@@ -43,6 +43,7 @@ import static org.apache.fop.render.afp.AFPRendererOption.IMAGES_NATIVE;
 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 @@ public final class AFPRendererConfBuilder extends RendererConfBuilder {
             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);
         }
index fad1b07676e0459a8c74cb5c1821ac5043e831da..640fee564bc54d848ffeb98a4d777c443e90c11e 100644 (file)
@@ -26,8 +26,10 @@ import java.util.Map;
 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;
@@ -170,6 +172,14 @@ public class AFPRendererConfigParserTestCase
         assertEquals(0.5f, conf.getBitmapEncodingQuality(), 0.001f);
     }
 
+    @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();
index a47326599e538502b12091c009819ea904a3d869..e02e746d59d11a14d187a56a3f3d13e33f8e0ac2 100644 (file)
@@ -211,6 +211,14 @@ public class AFPRendererConfiguratorTestCase extends
         verify(getDocHandler()).setBitmapEncodingQuality(0.5f);
     }
 
+    @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()