Fix color lookup after reusing a palette entry - #10084
sergioperezcheco wants to merge 4 commits into
Conversation
Signed-off-by: sergioperezcheco <checo520@outlook.com>
Signed-off-by: sergioperezcheco <checo520@outlook.com>
|
I've created sergioperezcheco#1 with a suggestion. |
Signed-off-by: sergioperezcheco <checo520@outlook.com>
|
Thanks for the suggestion. I've applied the release-note wording change in 913bb26. I tested the suggested An incremental update could search the remaining palette entries for a replacement mapping when deleting the cached index. The current cache rebuild handles that case directly, which is why I'd prefer to keep the duplicate-color tests. I haven't merged the suggested branch; happy to discuss an incremental variant that preserves this behavior. |
For my benefit, and anyone else reading this, let me go through the 'RGB' 'duplicate' test case step by step. Step 1. In both versions, the palette starts off as
So I understand that my suggestion doesn't do what you're saying. I don't have a firm stance here, but I'm not sure I understand why it should? After Step 2, In short - maybe you value indexes staying the same, or maybe you value not changing palette entries, but neither are guaranteed behaviour no matter what we decide here. |
Draw red into the reused slot before requesting the surviving gray color. Every palette index is now in use, so losing gray's cached mapping raises ValueError rather than merely choosing another unused index. Check rendered colors as well as indices. Signed-off-by: sergioperezcheco <checo520@outlook.com>
|
You are right that allocating red without drawing it does not establish a rendering failure. I strengthened the duplicate-color cases in f9c20e6: they now draw red into the reused entry before requesting the remaining gray color, then check both rendered pixels. With all palette entries now in use, forgetting the surviving gray mapping raises ValueError rather than just selecting another unused index. This tests successful drawing of an already-present color, not stable indices for unused allocations. All 56 checks on this head are successful. |
Palette entries and their color lookup diverge after reuse
A full palette can reuse an index that is absent from the image histogram.
ImagePalette.getcolor()updates the bytes at that index but leaves the replaced color incolors. A subsequent request for that color returns the reused index, so public drawing operations silently use the wrong color.This reproduces on current
mainthroughputpixel():Keep the lookup consistent with the palette bytes
Invalidate the color lookup only when replacing an existing palette entry, then register the new color after updating the bytes. Rebuilding also preserves duplicate colors at their remaining indices; deleting the old color alone would lose that information. Appending a new entry retains the existing incremental lookup path.
The regressions cover RGB/RGBA palettes, replacement of unique and duplicate colors, repeated requests for the newly allocated color, and a public
putpixel()reproduction with a PNG save/reopen check. The existingtest_new_colorexpected a cache entry for black after its unused slot had been replaced; it now checks that the stale entry is absent while retaining its rendered-pixel assertion. A release note describes the behavior change.Validation
Built the checkout and its native extensions locally with Python 3.11.15 on macOS arm64. All five new regression cases fail against the unchanged implementation.
After the fix:
python3 selftest.py: 59 passed.python3 -m pytest Tests/test_imagepalette.py Tests/test_imageops.py Tests/test_imagedraw.py Tests/test_image_putpalette.py Tests/test_image.py Tests/test_file_png.py Tests/test_file_gif.py -q: 725 passed, 2 skipped, 1 xfailed. The skips are missing IPython and WebP support; the xfail is the existingtest_line_h_s1_w2.ImagePalette.py, Sphinx-lint on the release notes, andgit diff --checkpass. Black ran with--fastbecause the project's target includes Python 3.15 while the local interpreter is 3.11; mypy and pytest ran separately.The full test suite and full pre-commit suite were not run. Prepared with AI assistance.