Pillow icon indicating copy to clipboard operation
Pillow copied to clipboard

Fix assertion observed when adding new colors to an indexed-color TIFF

Open markmentovai opened this issue 1 month ago • 4 comments

Alternative to https://github.com/python-pillow/Pillow/pull/9309

When ImagePalette._new_color_index adds a new color to an indexed-color TIFF, an assertion added in f2302ab716e9e could be tripped. The call into Image.histogram could result in ImageFile.load being called, which could eventually replace ImagePalette._palette with a bytes object, which did not satisfy the assertion that it be only bytearray.

markmentovai avatar Nov 24 '25 18:11 markmentovai

Hi. Good work on finding this. Just to show the code you're talking about, you're absolutely right, I had expected that https://github.com/python-pillow/Pillow/blob/ec40c546d7d38efebcb228745291bd3ba6233196/src/PIL/ImagePalette.py#L116-L120 would naturally mean that this assertion was valid. https://github.com/python-pillow/Pillow/blob/ec40c546d7d38efebcb228745291bd3ba6233196/src/PIL/ImagePalette.py#L168-L169

However, what I failed to realise was that _new_color_index() also might call image.histogram() https://github.com/python-pillow/Pillow/blob/ec40c546d7d38efebcb228745291bd3ba6233196/src/PIL/ImagePalette.py#L133 which loads the image https://github.com/python-pillow/Pillow/blob/ec40c546d7d38efebcb228745291bd3ba6233196/src/PIL/Image.py#L1681 which might set the palette data. https://github.com/python-pillow/Pillow/blob/ec40c546d7d38efebcb228745291bd3ba6233196/src/PIL/Image.py#L901-L903

Looking at the setting of the palette bytes there, the data is being sent from our Python layer to C a few lines earlier, https://github.com/python-pillow/Pillow/blob/ec40c546d7d38efebcb228745291bd3ba6233196/src/PIL/Image.py#L889-L891 and then retrieved again. This has the effect of converting it from a rawmode to one of our standardised modes. However, it is now apparent to me that if the rawmode matches the mode, then the Python data doesn't need to be updated - the data returned from our C layer would be identical. I've created https://github.com/python-pillow/Pillow/pull/9309 with this idea, and that actually allows your test case to pass.

But wait, you say, what if the rawmode is different? Well, it can't be, because getcolor() starts out with https://github.com/python-pillow/Pillow/blob/ec40c546d7d38efebcb228745291bd3ba6233196/src/PIL/ImagePalette.py#L151-L153

So, yes, I think of https://github.com/python-pillow/Pillow/pull/9309 as an alternative solution to your problem, with the added benefit of running less code. Let me know what you think.

radarhere avatar Nov 25 '25 13:11 radarhere

Hey, that’s great! #9309 works for me. I’m happy for you to land that.

I still think it’d be worth landing the test to guard against regressions. No problem if you want to incorporate that into #9309, or I can drop the ImagePalette.py change from this pull request and make it test-only.

markmentovai avatar Nov 25 '25 13:11 markmentovai

I've updated one of our existing tests in my PR. See what you think.

radarhere avatar Nov 26 '25 09:11 radarhere

https://github.com/python-pillow/Pillow/pull/9308#issuecomment-3580393912:

I've updated one of our existing tests in my PR. See what you think.

Great! Comments over there, then. → https://github.com/python-pillow/Pillow/pull/9309

markmentovai avatar Nov 26 '25 15:11 markmentovai