Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extra output from getpalette() and getcolors() in quantized image #6046

Closed
clarkemw opened this issue Feb 12, 2022 · 6 comments · Fixed by #6060
Closed

Extra output from getpalette() and getcolors() in quantized image #6046

clarkemw opened this issue Feb 12, 2022 · 6 comments · Fixed by #6060
Labels

Comments

@clarkemw
Copy link

clarkemw commented Feb 12, 2022

What did you do?

I am using Image.quantize to reduce an image down to a simple 12 color palette and count the prevalence of each color in quantized image.

What did you expect to happen?

getpalette() should return a 36 integer list reflecting the R,G,B values of the 12 color palette
getcolors() should return a list of 12 tuples with the second index in the tuples ranging from 1-12 referencing the colors in the palette.

What actually happened?

When I call getpalette() the first 36 items reflect my palette but the remaining items up to index 768 are a sequence of 12, 12, 12, 13, 13, 13 etc. I'm guessing the code does this to avoid resizing the array and internally detects these additional items and discards them...so maybe not a big deal.

The real issue is when I call getcolors(). Instead of receiving a list of 12 tuples (with second index in the tuples referencing a palette colors in the range 1-12) I receive a list of 208 tuples which reference palette colors as high as 252. I can't seem to make sense of the output. Am I supposed to just ignore anything that references an out of range palette index?

What are your OS, Python and Pillow versions?

  • OS:
  • Python: 3.9.10
  • Pillow: 9.01
RGB_flat = (2, 62, 255, 255, 124, 0, 26, 201, 56, 232, 0, 11, 139, 43, 226, 159, 72, 0, 241, 76, 193, 163, 163, 163, 255, 196, 0, 0, 215, 255, 0, 0, 0, 255, 255, 255)
img = Image.open('cherry.jpg')
pimg = Image.new('P',(16,16))
pimg.putpalette(RGB_flat)
imgq = img.quantize(palette=pimg)
colors = imgq.getcolors()
palette = imgq.getpalette()

cherry

@radarhere radarhere changed the title Extra Output From getpalette() and getcolors() in Quantized Image Extra output from getpalette() and getcolors() in quantized image Feb 12, 2022
@radarhere
Copy link
Member

I think this probably deserves a PR, but if you would like an immediate solution, try this code out.

from PIL import Image
RGB_flat = (2, 62, 255, 255, 124, 0, 26, 201, 56, 232, 0, 11, 139, 43, 226, 159, 72, 0, 241, 76, 193, 163, 163, 163, 255, 196, 0, 0, 215, 255, 0, 0, 0, 255, 255, 255)
img = Image.open('cherry.jpg')
pimg = Image.new('P', (16,16))
pimg.putpalette(RGB_flat+RGB_flat[-3:]*(255 - len(RGB_flat)//3))
imgq = img.quantize(palette=pimg)
print(imgq.getcolors())
print(imgq.palette.colors)

@clarkemw
Copy link
Author

clarkemw commented Feb 13, 2022

Thanks that fix seems to work. I'm not terribly familiar with interfacing C with Python programs but after staring at the code a bit I think my specific issue might stem from the _putpalette() function in the C code. The palette array gets initialized to 256 colors using a ramp of increasing values:

for (i = 0; i < 256; i++) {
palette->palette[i * 4 + 0] = palette->palette[i * 4 + 1] =
palette->palette[i * 4 + 2] = (UINT8)i;
palette->palette[i * 4 + 3] = 255; /* opaque */
}

Then in _putpalette() the call to unpack appears to only replace items up to the size of the palette:
unpack(self->image->palette->palette, palette, palettesize * 8 / bits);

As a result you get your requested palette plus the remaining ramp items from the original palette initialization (or whatever the palette was before). There are a lot of places where the code assumes a 256 color palette so changing the size would probably break everything...so padding out the palette automatically might be the best fix. There does appear to be logic to skip duplicate colors in other sections of the code which is why the call to getcolors() seems to work with the padding.

I can try to come up with a PR for a fix, but you would prefer to fix this at the Python level or in the C code itself?

@radarhere
Copy link
Member

I expect a C solution would be better than a Python one, as it would have a broader effect.

@radarhere
Copy link
Member

I've created PR #6060 to resolve this, so that your original code works.

@clarkemw
Copy link
Author

Thanks I pulled in your changes from the PR and recompiled. This fixes the confusing outputs I was seeing and even better the quantization is now acting in a much more predictable fashion. I was always puzzled at some of the color choices it was making before. I appreciate your help in resolving this so quickly. Please feel free to close once that PR gets into a release.

@radarhere
Copy link
Member

The next release will be on April 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants