Skip to content

ImagePalette's palette argument expects different alignment since 8.3.0 #5595

Closed

Description

@krukai

It seems that PIL.ImagePalette.ImagePalette's palette argument no longer expects a palette aligned by channels, but one color after the other, i.e. RGBRGBRGB rather than RRRGGGBBB as previously.

I assume this is an unintentional breaking change as it is neither specifically mentioned in the patch notes, nor was the documentation updated. I could not find any PR or bug report on this, either (unless I missed it - I am sorry if that's the case).

If this is a bug, I hope it can actually become a feature as I personally prefer the new ordering.

Example:

import PIL.Image
import PIL.ImagePalette

p = [0, 0, 0,
     255, 0, 0,
     0, 255, 0]

palette = PIL.ImagePalette.ImagePalette(mode='RGB', palette=p, size=len(p))

image = PIL.Image.new('P', (100, 100), 1)  # fill image with second color in the palette
image.putpalette(palette)

image.show()  # Gives a blue image on 8.2.0 and a red image since 8.3.0

assert image.getpalette()[:len(p)] == p, 'This assert succeeds in 8.3.0'

Cheers!

  • OS: Arch Linux
  • Python: 3.9
  • Pillow: 8.3.0+

Activity

  1. radarhere commented on Jul 9, 2021

    @radarhere
    Member

    Testing, I find this is the result of 1ee30de from #5552

    With that particular change, I was interested in fixing a bug where the palette could break just because it was marked dirty. See this new test that was added there. It failed previously.

    im = Image.open("Tests/images/hopper.gif")
    original = im.copy()
    im.palette.dirty = 1
    assert_image_equal(im.convert("RGB"), original.convert("RGB"))

    But you can see in something like 4d36fee, from earlier in #5552, that my intention was for RGBRGBRGB.

    I've created PR #5599 to update the documentation. Let me know if there is anything else missing.

  2. krukai commented on Jul 9, 2021

    @krukai
    Author

    Thank you for the quick reply! As far as I am aware, that is all.

    But is it really a good idea to force a breaking change? I will now have to re-arrange my input palette depending on the pillow version installed, and I doubt I am the only one depending pillow.
    As I said, I would love this new ordering to become a feature, but backwards compatibility should be honored, shouldn't it?

    Cheers!

  3. radarhere commented on Jul 10, 2021

    @radarhere
    Member

    Pillow does value backwards compatibility, quite highly.

    If this issue had been raised before 8.3.0, then this would have had a good chance of being addressed.
    If this had even been raised before 8.3.1, then maybe.
    If this issue had something like the 25 responses to 8.3.0 bug of #5571 that caused us to release 8.3.1, again, let's try and keep Pillow working for users, maybe we'll put together 8.3.2.
    However, if we're talking about reverting the behaviour in 8.4.0, I'm not convinced that switching back after 3 months of different behaviour is helpful.

    As to why the change happened in the first place, here is my personal feeling - ImagePalette has been broken.

    Allow me to start with your code and go from there.

    from PIL import Image, ImagePalette
    
    p = [0, 0, 0,
         255, 0, 0,
         0, 255, 0]
    palette = ImagePalette.ImagePalette(mode='RGB', palette=p, size=len(p))
    
    image = Image.new('P', (100, 100), 1)
    image.putpalette(palette)
    
    # As you've said, 8.3.0 switches from RRGGBB to RGBRGB.
    print(image.getpalette()[:len(p)])
    # 8.2.0: [0, 255, 0, 0, 0, 255, 0, 0, 0]
    # 8.3.0: [0, 0, 0, 255, 0, 0, 0, 255, 0]
    
    # Unintuitive behaviour: putpalette with an array wasn't the same as ImagePalette
    image.putpalette(p)
    print(image.getpalette()[:len(p)])
    # 8.2.0: [0, 0, 0, 255, 0, 0, 0, 255, 0]  # Different. Actually, it's RGBRGB! So I could make the argument that changing the behaviour makes Pillow more consistent.
    # 8.3.0: [0, 0, 0, 255, 0, 0, 0, 255, 0]  # The same
    
    # Bug: Marking a palette as dirty after passing an array to putpalette changed the palette
    image.load()
    image.palette.dirty = 1
    print(image.getpalette()[:len(p)])
    # 8.2.0: [0, 255, 0, 0, 0, 255, 0, 0, 0]  # Different
    # 8.3.0: [0, 0, 0, 255, 0, 0, 0, 255, 0]  # The same
    
    # Bug: getcolor() didn't correctly return color indexes
    print(image.palette.getcolor((0, 0, 0)))
    # 8.2.0: IndexError: bytearray index out of range
    # 8.3.0: 0
    
    # Bug: getcolor() didn't correctly create new colors
    print(image.palette.getcolor((255, 255, 255)))
    # 8.2.0: IndexError: bytearray index out of range
    # 8.3.0: 3

    It sounds like maybe you're building a library with Pillow as a dependency, and you don't know what version of Pillow your users will have installed? If so, then perhaps using putpalette with an array directly will solve that problem for you, as it behaves the same way in 8.2.0 and 8.3.0.

  4. krukai commented on Jul 11, 2021

    @krukai
    Author

    Thank you very much for the detailed explanation and insight! I fully agree now that this is an inconsistency, first and foremost.
    Frankly, I just chose the first way I came across to to reach my goal, and passing the array directly to putpalette() will work fine for me.
    Thank you again for looking into this and working on pillow in general.

    Cheers!

    Edit: I figure once the documentation change is merged for good, this can be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions