Fix BMP save of a P image with an empty palette#9746
Open
gaoflow wants to merge 1 commit into
Open
Conversation
Saving a P-mode image whose palette is empty (e.g. Image.new("P", size)
with no putpalette) wrote biClrUsed=0 and no color table. A reader treats
biClrUsed=0 at 8 bits per pixel as 256 entries, so the pixel data offset
points past a color table that was never written and reopening the file
fails with "image file is truncated". PNG, GIF and TIFF all round-trip the
same image.
Write a full 256-entry table when the palette is empty, mirroring the L
mode branch, so the file can be read back. A populated palette is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Saving a
P-mode image whose palette is empty produces a BMP that Pillow cannot reopen:PNG, GIF and TIFF all round-trip the same image; only BMP fails, and it fails to read back its own output.
Cause
In
BmpImagePlugin._save, thePbranch computescolors = len(palette) // 4, which is0for an empty palette. The writer then setsbiClrUsed = 0and writes no color table, with the pixel-data offset at14 + 40 = 54. On read,biClrUsed == 0at 8 bpp is interpreted as1 << 8 = 256entries, so the reader advances the pixel offset by256 * 4bytes past a table that was never written — it reads past EOF and reports the file as truncated.Fix
When the palette is empty, write a full 256-entry table (mirroring the existing
Lmode branch) so the header is self-consistent and the file reads back. An all-zero table is used rather than a grayscale ramp so the image reopens asP(a ramp would be detected as grayscale and downgraded toL). A populated palette is unaffected.Tests
test_save_empty_paletteparametrizes(1, 1),(7, 5),(8, 8),(16, 16), asserting the written header has a non-zerobiClrUsed(pinning the root cause, not just the symptom) and that the image reopens asPwith its pixel indices intact.test_save_empty_palette_round_trips_like_other_formatspins the cross-format invariant that BMP reopens the image like PNG/GIF/TIFF do. Both fail onmain(OSError/biClrUsed == 0) and pass with the fix; the fullTests/test_file_bmp.pysuite stays green (37 passed);ruff format,ruff checkandmypyare clean.