Open ericpauley opened 8 years ago
This also happens regardless of the method
argument.
Pretty sure that this is connected with https://github.com/python-pillow/Pillow/issues/1987#issuecomment-228606543
I found an undefined variable in the sample code, so I'm providing a version here that is fixed, simpler, and also provides easier inspection of the result.
from PIL import Image
i = Image.new('RGB', (1,1), (255, 255, 255))
p = Image.new('P', (1,1))
i2 = i.quantize(palette=p)
print(i2.convert('RGB').load()[0, 0]) # (252, 252, 252)
I've been having this issue. Are there any workarounds in the meantime?
This seems to be caused by lossy caching.
#define ImagingPaletteCache(p, r, g, b) \
p->cache[(r >> 2) + (g >> 2) * 64 + (b >> 2) * 64 * 64]
Any image passed through this palette cache gets reduced from 8bpc to 6bpc with values being rounded down, hence the change from 255 to 252 in the sample.
Relevant functions for anyone who wants to take a look: https://github.com/python-pillow/Pillow/blob/61b9065a25aaeefdca19940448b21b02f1e6766a/src/libImaging/Palette.c#L167 https://github.com/python-pillow/Pillow/blob/61b9065a25aaeefdca19940448b21b02f1e6766a/src/libImaging/Convert.c#L1255
The way the cache works probably needs completely rethinking to fix this.
A simple but far-from-optimal way to "fix" the cache would be extending the ImagingPaletteInstance.cache
member to a tagged union allowing multiple values. Sparse cells can keep their one-value encoding, while the multi-value ones can just use a linear search -- 64 entries to look through at most. [Ideally the whole cache
becomes some proper search tree, but that hurts my head. Ayy, double space use can't be that bad, right?]
(Sorry for the previous message -- I didn't even do a getpalette
to check it out.)
Alright, I tried this idea out in https://github.com/Artoria2e5/Pillow/tree/cache and I hate it. The biggest problem is the small malloc
scattered around -- I initially wanted to put a big, shared "extra palette indices" buffer under ImagingPaletteInstance
, using the negative values of INT16 *cache
to indicate indices to it. The thing is despite knowing the ideal maximum size (something along the lines of 256 8 2 entries), repeated calls to ImagingPaletteCacheUpdate
will produce a lot of "dead" space, blowing it up quickly. And I would really love to not have to write a terrible arena allocator on top of my terrible nearest-neighbor code. (Yes, l could just skip already-assigned entries because there really shouldn't be anything to update in that case -- but no thanks i'm tired.)
[Ah great, now I am reminding myself that repeated calls will never happen What did I do in the last hour?]
Reported in 2016, yet biting my ass all the way in 2024, nice.
Heck, I don't even remember if my code compiles. It's C.
What did you do?
I am quantizing many images to the same palette and colors are often not picked to be ideal values, instead being mapped to close values when an exact match exists in the palette.
What did you expect to happen?
Colors would be matched to the closest value in the palette or an exact match if one exists.
What actually happened?
Colors are mapped to close values. (Specifically white is mapped to #fcfcfc)
What versions of Pillow and Python are you using?
Please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies.