From: Simon Steiner Date: Fri, 11 Oct 2024 07:36:27 +0000 (+0100) Subject: FOP-3211: Add option for native embed of compressed images in AFP by João André Gonçalves X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=53ce1edfc042619e2612e9f21d5dccaba19dbffd;p=xmlgraphics-fop.git 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 987102af7..ee41d66c7 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 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) { 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 dc828a540..55c0cd527 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 @@ 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{" 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 017fc076d..62303d9ea 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 @@ 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 * 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 459f0e9a6..c96c300f0 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 @@ 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) { 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 2a2b8803b..b1bb7701d 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 @@ 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; 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 25bfd635e..4111d0964 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 @@ 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; } 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 db8162da9..fe1978a02 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_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 { 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 99a015fcc..803a5c0d9 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 @@ 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()); } 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 cdd6fba73..d0721c26e 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 @@ 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 index 000000000..a1fc9f39c --- /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 17934997a..130971175 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_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); } 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 fad1b0767..640fee564 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 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(); 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 a47326599..e02e746d5 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 @@ -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()