Skip to content

Commit f81c7c5

Browse files
committed
PDFBOX-6032: add a fallback functional interface, as suggested by Ilgoo Kim
git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1927244 13f79535-47bb-0310-9956-ffa450edef68
1 parent 3e48d22 commit f81c7c5

File tree

3 files changed

+134
-2
lines changed

3 files changed

+134
-2
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.pdfbox.pdmodel.graphics.image;
18+
19+
import java.io.IOException;
20+
21+
import org.apache.pdfbox.pdmodel.PDDocument;
22+
23+
/**
24+
* A functional interface that allows users to define a custom strategy for converting image data as
25+
* a byte array into a {@link PDImageXObject}.
26+
*/
27+
@FunctionalInterface
28+
public interface CustomFactory
29+
{
30+
31+
/**
32+
* Creates a {@link PDImageXObject} from the given image byte array and document context.
33+
*
34+
* @param document the document that shall use this PDImageXObject.
35+
* @param byteArray the image data as a byte array
36+
* @return a PDImageXObject.
37+
* @throws IOException if there is an error when creating the PDImageXObject.
38+
*/
39+
PDImageXObject createFromByteArray(PDDocument document, byte[] byteArray) throws IOException;
40+
}

pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,27 @@ public static PDImageXObject createFromFileByContent(File file, PDDocument doc)
339339
* @throws IllegalArgumentException if the image type is not supported.
340340
*/
341341
public static PDImageXObject createFromByteArray(PDDocument document, byte[] byteArray, String name) throws IOException
342+
{
343+
return createFromByteArray(document, byteArray, name, null);
344+
}
345+
346+
/**
347+
* Create a PDImageXObject from an image byte array. This overloaded version allows providing
348+
* a custom factory to handle specific image formats, such as BMP and GIF, or to act as a
349+
* fallback strategy when the default converters (e.g., for PNG or TIFF) fail.
350+
*
351+
* @param document the document that shall use this PDImageXObject.
352+
* @param byteArray bytes from an image file.
353+
* @param name name of image file for exception messages, can be null.
354+
* @param customFactory optional factory used to handle BMP, GIF, or fallback cases
355+
* (e.g., for PNG or TIFF). If {@code null}, this method delegates to
356+
* {@link #createFromByteArray(PDDocument, byte[], String)}.
357+
* @return a PDImageXObject.
358+
* @throws IOException if there is an error when reading the file or creating the
359+
* PDImageXObject.
360+
* @throws IllegalArgumentException if the image type is not supported.
361+
*/
362+
public static PDImageXObject createFromByteArray(PDDocument document, byte[] byteArray, String name, CustomFactory customFactory) throws IOException
342363
{
343364
FileType fileType = FileTypeDetector.detectFileType(byteArray);
344365
if (fileType == null)
@@ -376,6 +397,11 @@ public static PDImageXObject createFromByteArray(PDDocument document, byte[] byt
376397
}
377398
if (fileType == FileType.BMP || fileType == FileType.GIF || fileType == FileType.PNG)
378399
{
400+
if (customFactory != null)
401+
{
402+
return customFactory.createFromByteArray(document, byteArray);
403+
}
404+
379405
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
380406
BufferedImage bim = ImageIO.read(bais);
381407
return LosslessFactory.createFromImage(document, bim);

pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObjectTest.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616
*/
1717
package org.apache.pdfbox.pdmodel.graphics.image;
1818

19+
import java.awt.AlphaComposite;
20+
import java.awt.Color;
21+
import java.awt.Graphics2D;
22+
import java.awt.image.BufferedImage;
23+
import java.awt.image.ColorModel;
24+
import java.awt.image.WritableRaster;
1925

2026
import static org.junit.jupiter.api.Assertions.assertEquals;
2127

22-
import java.awt.image.BufferedImage;
28+
import java.io.ByteArrayInputStream;
2329
import java.io.File;
2430
import java.io.FileInputStream;
2531
import java.io.IOException;
@@ -125,6 +131,19 @@ void testCreateFromByteArray() throws IOException, URISyntaxException
125131
testCompareCreatedFromByteArrayWithCreatedByLosslessFactory("lzw.tif");
126132
}
127133

134+
/**
135+
* Test of createFromByteArray method with CustomFactory parameter, of class PDImageXObject.
136+
* @throws java.io.IOException
137+
* @throws java.net.URISyntaxException
138+
*/
139+
@Test
140+
void testCreateFromByteArrayWithCustomFactory() throws IOException, URISyntaxException
141+
{
142+
testCompareCreatedFromByteArrayWithCreatedByCustomFactory("gif.gif");
143+
testCompareCreatedFromByteArrayWithCreatedByCustomFactory("gif-1bit-transparent.gif");
144+
testCompareCreatedFromByteArrayWithCreatedByCustomFactory("lzw.tif");
145+
}
146+
128147
private void testCompareCreatedFileByExtensionWithCreatedByLosslessFactory(String filename)
129148
throws IOException, URISyntaxException
130149
{
@@ -318,6 +337,26 @@ private void testCompareCreatedFromByteArrayWithCreatedByJPEGFactory(String file
318337
}
319338
}
320339

340+
private void testCompareCreatedFromByteArrayWithCreatedByCustomFactory(String filename)
341+
throws IOException, URISyntaxException
342+
{
343+
try (PDDocument doc = new PDDocument())
344+
{
345+
File file = new File(PDImageXObjectTest.class.getResource(filename).toURI());
346+
InputStream in = new FileInputStream(file);
347+
byte[] byteArray = in.readAllBytes();
348+
349+
CustomFactory customFactory = this::alphaFlattenedJPEGFactory;
350+
351+
PDImageXObject image = PDImageXObject.createFromByteArray(doc, byteArray, filename, customFactory);
352+
353+
PDImageXObject expectedImage = alphaFlattenedJPEGFactory(doc, byteArray);
354+
355+
assertEquals(expectedImage.getSuffix(), image.getSuffix());
356+
checkIdentARGB(image.getImage(), expectedImage.getImage());
357+
}
358+
}
359+
321360
private void checkIdentARGB(BufferedImage expectedImage, BufferedImage actualImage)
322361
{
323362
String errMsg = "";
@@ -337,5 +376,32 @@ private void checkIdentARGB(BufferedImage expectedImage, BufferedImage actualIma
337376
assertEquals(expectedImage.getRGB(x, y), actualImage.getRGB(x, y), errMsg);
338377
}
339378
}
340-
}
379+
}
380+
381+
private PDImageXObject alphaFlattenedJPEGFactory(PDDocument document, byte[] byteArray) throws IOException
382+
{
383+
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
384+
BufferedImage bim = ImageIO.read(bais);
385+
386+
if (bim.isAlphaPremultiplied()) {
387+
ColorModel colorModel = bim.getColorModel();
388+
WritableRaster raster = bim.copyData(null);
389+
bim = new BufferedImage(colorModel, raster, false, null);
390+
}
391+
392+
BufferedImage flattened = new BufferedImage(
393+
bim.getWidth(),
394+
bim.getHeight(),
395+
BufferedImage.TYPE_INT_RGB
396+
);
397+
398+
Graphics2D g = flattened.createGraphics();
399+
g.setComposite(AlphaComposite.SrcOver);
400+
g.setColor(Color.WHITE);
401+
g.fillRect(0, 0, flattened.getWidth(), flattened.getHeight());
402+
g.drawImage(bim, 0, 0, null);
403+
g.dispose();
404+
405+
return JPEGFactory.createFromImage(document, flattened);
406+
}
341407
}

0 commit comments

Comments
 (0)