Author: tilman Date: Tue Jul 15 15:11:37 2025 New Revision: 1927245 URL: http://svn.apache.org/viewvc?rev=1927245&view=rev Log: PDFBOX-6032: add a fallback functional interface, as suggested by Ilgoo Kim
Added: pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CustomFactory.java (with props) Modified: pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObjectTest.java Added: pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CustomFactory.java URL: http://svn.apache.org/viewvc/pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CustomFactory.java?rev=1927245&view=auto ============================================================================== --- pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CustomFactory.java (added) +++ pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CustomFactory.java Tue Jul 15 15:11:37 2025 @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.apache.pdfbox.pdmodel.graphics.image; + +import java.io.IOException; + +import org.apache.pdfbox.pdmodel.PDDocument; + +/** + * A functional interface that allows users to define a custom strategy for converting image data as + * a byte array into a {@link PDImageXObject}. + */ +@FunctionalInterface +public interface CustomFactory +{ + + /** + * Creates a {@link PDImageXObject} from the given image byte array and document context. + * + * @param document the document that shall use this PDImageXObject. + * @param byteArray the image data as a byte array + * @return a PDImageXObject. + * @throws IOException if there is an error when creating the PDImageXObject. + */ + PDImageXObject createFromByteArray(PDDocument document, byte[] byteArray) throws IOException; +} Propchange: pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CustomFactory.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java URL: http://svn.apache.org/viewvc/pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java?rev=1927245&r1=1927244&r2=1927245&view=diff ============================================================================== --- pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java (original) +++ pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java Tue Jul 15 15:11:37 2025 @@ -344,6 +344,27 @@ public final class PDImageXObject extend */ public static PDImageXObject createFromByteArray(PDDocument document, byte[] byteArray, String name) throws IOException { + return createFromByteArray(document, byteArray, name, null); + } + + /** + * Create a PDImageXObject from an image byte array. This overloaded version allows providing + * a custom factory to handle specific image formats, such as BMP and GIF, or to act as a + * fallback strategy when the default converters (e.g., for PNG or TIFF) fail. + * + * @param document the document that shall use this PDImageXObject. + * @param byteArray bytes from an image file. + * @param name name of image file for exception messages, can be null. + * @param customFactory optional factory used to handle BMP, GIF, or fallback cases + * (e.g., for PNG or TIFF). If {@code null}, this method delegates to + * {@link #createFromByteArray(PDDocument, byte[], String)}. + * @return a PDImageXObject. + * @throws IOException if there is an error when reading the file or creating the + * PDImageXObject. + * @throws IllegalArgumentException if the image type is not supported. + */ + public static PDImageXObject createFromByteArray(PDDocument document, byte[] byteArray, String name, CustomFactory customFactory) throws IOException + { FileType fileType = FileTypeDetector.detectFileType(byteArray); if (fileType == null) { @@ -380,6 +401,11 @@ public final class PDImageXObject extend } if (fileType == FileType.BMP || fileType == FileType.GIF || fileType == FileType.PNG) { + if (customFactory != null) + { + return customFactory.createFromByteArray(document, byteArray); + } + ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); BufferedImage bim = ImageIO.read(bais); return LosslessFactory.createFromImage(document, bim); Modified: pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObjectTest.java URL: http://svn.apache.org/viewvc/pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObjectTest.java?rev=1927245&r1=1927244&r2=1927245&view=diff ============================================================================== --- pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObjectTest.java (original) +++ pdfbox/branches/3.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObjectTest.java Tue Jul 15 15:11:37 2025 @@ -17,12 +17,20 @@ package org.apache.pdfbox.pdmodel.graphics.image; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.WritableRaster; + import static org.junit.jupiter.api.Assertions.assertEquals; -import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URISyntaxException; import javax.imageio.ImageIO; import org.apache.pdfbox.io.IOUtils; @@ -124,6 +132,19 @@ class PDImageXObjectTest testCompareCreatedFromByteArrayWithCreatedByLosslessFactory("lzw.tif"); } + /** + * Test of createFromByteArray method with CustomFactory parameter, of class PDImageXObject. + * @throws java.io.IOException + * @throws java.net.URISyntaxException + */ + @Test + void testCreateFromByteArrayWithCustomFactory() throws IOException, URISyntaxException + { + testCompareCreatedFromByteArrayWithCreatedByCustomFactory("gif.gif"); + testCompareCreatedFromByteArrayWithCreatedByCustomFactory("gif-1bit-transparent.gif"); + testCompareCreatedFromByteArrayWithCreatedByCustomFactory("lzw.tif"); + } + private void testCompareCreatedFileByExtensionWithCreatedByLosslessFactory(String filename) throws IOException, URISyntaxException { @@ -314,6 +335,29 @@ class PDImageXObjectTest } } + private void testCompareCreatedFromByteArrayWithCreatedByCustomFactory(String filename) + throws IOException, URISyntaxException + { + try (PDDocument doc = new PDDocument()) + { + File file = new File(PDImageXObjectTest.class.getResource(filename).toURI()); + byte[] byteArray; + try (InputStream in = new FileInputStream(file)) + { + byteArray = IOUtils.toByteArray(in); + } + + CustomFactory customFactory = this::alphaFlattenedJPEGFactory; + + PDImageXObject image = PDImageXObject.createFromByteArray(doc, byteArray, filename, customFactory); + + PDImageXObject expectedImage = alphaFlattenedJPEGFactory(doc, byteArray); + + assertEquals(expectedImage.getSuffix(), image.getSuffix()); + checkIdentARGB(image.getImage(), expectedImage.getImage()); + } + } + private void checkIdentARGB(BufferedImage expectedImage, BufferedImage actualImage) { String errMsg = ""; @@ -333,5 +377,32 @@ class PDImageXObjectTest assertEquals(expectedImage.getRGB(x, y), actualImage.getRGB(x, y), errMsg); } } - } + } + + private PDImageXObject alphaFlattenedJPEGFactory(PDDocument document, byte[] byteArray) throws IOException + { + ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + BufferedImage bim = ImageIO.read(bais); + + if (bim.isAlphaPremultiplied()) { + ColorModel colorModel = bim.getColorModel(); + WritableRaster raster = bim.copyData(null); + bim = new BufferedImage(colorModel, raster, false, null); + } + + BufferedImage flattened = new BufferedImage( + bim.getWidth(), + bim.getHeight(), + BufferedImage.TYPE_INT_RGB + ); + + Graphics2D g = flattened.createGraphics(); + g.setComposite(AlphaComposite.SrcOver); + g.setColor(Color.WHITE); + g.fillRect(0, 0, flattened.getWidth(), flattened.getHeight()); + g.drawImage(bim, 0, 0, null); + g.dispose(); + + return JPEGFactory.createFromImage(document, flattened); + } }