Seungmin Lee created PDFBOX-6235:
------------------------------------

             Summary: JPEGFactory.createFromImage() throws NullPointerException 
for 4-component (CMYK) BufferedImages
                 Key: PDFBOX-6235
                 URL: https://issues.apache.org/jira/browse/PDFBOX-6235
             Project: PDFBox
          Issue Type: Bug
          Components: PDModel
    Affects Versions: 3.0.7 PDFBox, 3.0.1 PDFBox, 2.0.36
         Environment: PDFBox 3.0.1
Java: Amazon Corretto 21.0.8+9-LTS (aarch64)
OS: macOS (Apple Silicon)
            Reporter: Seungmin Lee


JPEGFactory.createFromImage() fails with a NullPointerException for any 
BufferedImage backed by a 4-component CMYK color space -- for example the 
result of ImageIO.read() on a CMYK JPEG, which is common for print-ready assets.

Reproduced on 2.0.36, 3.0.1 and 3.0.7 (same failure, only the line number 
differs: 396 / 372 / 376). The same unguarded dereference is still present on 
trunk.

h3. Root cause

encodeImageToJPEGStream() dereferences the app0JFIF metadata node without a 
null check:

{code:java}
Element tree = (Element) data.getAsTree("javax_imageio_jpeg_image_1.0");
Element jfif = (Element) tree.getElementsByTagName("app0JFIF").item(0);
String dpiString = Integer.toString(dpi);
jfif.setAttribute("Xdensity", dpiString);   // <-- NPE
jfif.setAttribute("Ydensity", dpiString);
jfif.setAttribute("resUnits", "1");
{code}

The JFIF APP0 segment is only defined for 1-component (grayscale) and 
3-component (YCbCr) JPEGs. When the ImageIO JPEG writer encodes a 4-component 
image it emits an Adobe APP14 marker instead, so the default metadata tree 
contains no "app0JFIF" node and item(0) returns null.

Note that the NPE comes from the metadata of the *re-encoded output*, not from 
the input file -- a CMYK input JPEG that does carry a JFIF APP0 marker fails 
just the same.

This appears to be an oversight rather than intentionally unsupported input: 
getColorSpaceFromAWT() explicitly handles ColorSpace.TYPE_CMYK, so the factory 
otherwise looks intended to accept 4-component images.

h3. Steps to reproduce

1. Create a 4-component CMYK JPEG (no attachment needed):

{noformat}
magick -size 200x200 gradient:red-blue -colorspace CMYK cmyk.jpg
{noformat}

Verify with {{file cmyk.jpg}} -> "JPEG image data, baseline, precision 8, 
200x200, components 4"

2. Run:

{code:java}
BufferedImage src = ImageIO.read(new File("cmyk.jpg"));
// src.getType() == TYPE_CUSTOM (0)
// src.getColorModel().getNumComponents() == 4
// src.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK (9)
try (PDDocument doc = new PDDocument()) {
    JPEGFactory.createFromImage(doc, src);
}
{code}

h3. Actual result

{noformat}
java.lang.NullPointerException: Cannot invoke 
"org.w3c.dom.Element.setAttribute(String, String)" because "jfif" is null
        at 
org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory.encodeImageToJPEGStream(JPEGFactory.java:376)
        at 
org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory.createJPEG(JPEGFactory.java:312)
        at 
org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory.createFromImage(JPEGFactory.java:278)
        at 
org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory.createFromImage(JPEGFactory.java:255)
        at 
org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory.createFromImage(JPEGFactory.java:233)
{noformat}

(stack trace from 3.0.7)

h3. Expected result

Either a PDImageXObject is created with a DeviceCMYK color space, or a 
descriptive exception is thrown stating that 4-component images are not 
supported.

h3. Suggested fix

Guard the dereference. The DPI metadata simply cannot be expressed in a 
non-JFIF stream, so skipping those three attributes when jfif is null seems 
sufficient:

{code:java}
Element jfif = (Element) tree.getElementsByTagName("app0JFIF").item(0);
if (jfif != null)
{
    String dpiString = Integer.toString(dpi);
    jfif.setAttribute("Xdensity", dpiString);
    jfif.setAttribute("Ydensity", dpiString);
    jfif.setAttribute("resUnits", "1"); // 1 = dots/inch
}
{code}

h3. Workaround

When the image does not need resampling, embedding the original bytes with 
JPEGFactory.createFromByteArray() avoids the re-encode entirely and also 
preserves the DeviceCMYK color space. This is not an option when the image must 
be resized.

Possibly related (all older, different symptoms): PDFBOX-2057, PDFBOX-2128, 
PDFBOX-3823.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to