Skip to content

JPEG saved with keep_rgb=True and dpi reopens with incorrect colors #10344

Open

Description

@Muguett-DBY

What did you do?

Saved the same RGB image to JPEG with keep_rgb=True, once with DPI and once without it, then reopened both files:

from io import BytesIO

from PIL import Image

image = Image.new("RGB", (16, 16), (255, 0, 0))
for dpi in (None, (72, 72)):
    out = BytesIO()
    options = {"keep_rgb": True, "quality": 100, "subsampling": 0}
    if dpi is not None:
        options["dpi"] = dpi
    image.save(out, "JPEG", **options)
    out.seek(0)
    with Image.open(out) as reopened:
        print(dpi, reopened.getpixel((0, 0)))

What did you expect to happen?

Both images should reopen approximately red. Setting DPI should not visibly change the colors.

What actually happened?

On both installations below, the output was:

None (255, 0, 0)
(72, 72) (76, 255, 28)

The DPI version contains both a JFIF APP0 marker and an Adobe APP14 marker with transform 0; the version without DPI contains only the Adobe marker. As a diagnostic, removing the JFIF marker from the generated bytes made Pillow reopen the pixel as (255, 0, 0), but also removed the DPI. I don't know whether the right fix is in the writer, the reader, or both.

FFmpeg 9.0.2 decodes both generated JPEGs as (255, 0, 0) on the same machine. This suggests a difference in decoding the DPI version; I have not isolated the responsible layer.

The generated file has component IDs R, G and B. As a diagnostic, forcing the low-level JPEG decoder input mode to RGB also restores the expected red in both Pillow versions.

Looking at libjpeg-turbo 3.2.0, its default color-space selection gives the JFIF marker precedence over Adobe APP14, selecting YCbCr when both are present. Pillow enables JFIF when both DPI values are positive.

#7679 notes that setting DPI forces JFIF even for non-JFIF color spaces. The JPEG encoder source is identical between the 13.0.0 tag and current main, although I haven't built main to reproduce this there.

What are your OS, Python and Pillow versions?

  • Windows 11, Python 3.15.2, Pillow 13.0.0, libjpeg-turbo 3.2.0.
  • Also reproduced with Python 3.12.14, Pillow 12.3.0, libjpeg-turbo 3.1.4.1.

Reproducer and investigation assisted by Codex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions