Repository navigation
Avoid conflicting JFIF and Adobe markers for RGB JPEGs - #10345
Muguett-DBY wants to merge 7 commits into
Conversation
|
I find the conditions for using RGB very complex. What if instead, when keep_rgb and DPI are used together, we abandon the JFIF header, and store the DPI in EXIF? Muguett-DBY#2 |
|
Thanks, avoiding the conflicting JFIF marker when writing is a much simpler approach. I tried the proposed commit and found a DPI case the 72-DPI test seems to miss: from io import BytesIO
from PIL import Image
out = BytesIO()
Image.new("RGB", (20, 20), "red").save(out, "JPEG", keep_rgb=True, dpi=(96, 96))
out.seek(0)
with Image.open(out) as im:
print(im.getpixel((0, 0)), im.info["dpi"]) # (255, 0, 0), (72, 72)
One trade-off to consider: a writer-only change would prevent new files from having both markers, but files already saved by older Pillow versions would still decode with the wrong colors. What do you think about handling those existing files? Checked with Codex. |
I've created #10353
I think this would be better addressed in #10334
I've pushed a commit for this.
If Pillow saved those images incorrectly, then they have incorrect colours. |
4c942d2 to
94de18b
Compare
|
Thanks, the writer-only approach makes sense. Avoiding this marker combination when saving is preferable to changing how other software's files are decoded. The current branch now reflects your replacement, and I've updated the PR description accordingly. I checked I'll leave non-square DPI to #10334 as you suggest. |
|
Thanks for merging #10353 and updating the branch. While checking the new head, I found a case the square-DPI test misses: from io import BytesIO
from PIL import Image
exif = Image.Exif()
exif[0x011A] = 300
exif[0x011B] = 200
exif[0x0128] = 3 # resolution in centimetres
out = BytesIO()
Image.new("RGB", (12, 12)).save(out, "JPEG", keep_rgb=True, dpi=(96, 96), exif=exif)
with Image.open(BytesIO(out.getvalue())) as im:
print(im.info["dpi"]) # (243.84, 243.84), not (96, 96)The new XResolution is in inches, but the copied ResolutionUnit remains centimetres. Assigning the entire DPI tuple to XResolution also emits a “too many entries” warning and discards its second value. For a new file, that leaves no YResolution for #10334 to read; with supplied EXIF it can leave an older YResolution in place. There is a related change for non-RGB modes: with I checked the exact writer and reader at |
|
Thanks for checking the EXIF specification in #10334. I missed that requirement when I added separate X/Y tags here: the current head writes unequal EXIF values for non-square DPI, and Pillow still reads X for both axes. I've moved this PR back to draft. For RGB JPEGs, JFIF density preserves two axes but reintroduces the conflicting colour markers; EXIF avoids that conflict but specifies matching axes. Would rejecting unequal DPI for |
|
|
||
| exif[0x011A] = dpi[0] | ||
| exif[0x011B] = dpi[1] | ||
| exif[0x0128] = 2 |
There was a problem hiding this comment.
Why not leave the ResolutionUnit unset?
That sounds fine to me. |
Fixes #10344. Draft: the non-square DPI case needs a decision before this is ready.
Saving an RGB JPEG with
keep_rgb=Trueand DPI currently writes both JFIF and Adobe markers; libjpeg then decodes the RGB components as YCbCr. This was found through a Codex-assisted source investigation.The branch builds on @radarhere's writer-only approach, using EXIF for RGB density and retaining JFIF for other modes. It copies supplied EXIF before updating it and keeps the requested inch unit. The reader change in #10353 is included.
There is an unresolved issue in the current head: it writes different EXIF X/Y resolutions when the requested DPI differs. As discussed in #10334, the CIPA EXIF specification calls for those values to match, and Pillow's current reader reports X for both. The change therefore cannot yet preserve non-square DPI correctly. The PR is a draft while that trade-off is resolved.
At
9a20ff03, all 49 GitHub check runs and both external statuses passed. The focused local tests used the exact Python writer and reader with an existing Pillow 13.0 codec; they do not establish correctness for the unresolved case above. Investigation, implementation and checks assisted by Codex.