Search code examples
pythonimage-processingpython-imaging-librarycolor-palette

Change Palette color index in Python


I got this image. The image is PNG, in mode P, palette is mode RGB. I need to stay with 16 colors, as I want the image as 4bpp. And I need to change his palette, making the color pink (255, 192, 203) its first index.

The image palette is:

{(255, 255, 232): 0, (255, 192, 203): 1, (210, 204, 147): 2, (62, 214, 108): 3, (59, 193, 95): 4, (209, 174, 99): 5, (194, 164, 92): 6, (130, 186, 185): 7, (180, 148, 83): 8, (95, 152, 121): 9, (49, 161, 88): 10, (157, 123, 59): 11, (118, 97, 55): 12, (52, 128, 119): 13, (73, 80, 63): 14, (63, 59, 47): 15}

And I want:

{(255, 192, 203): 0, (255, 255, 232): 1, (210, 204, 147): 2, (62, 214, 108): 3, (59, 193, 95): 4, (209, 174, 99): 5, (194, 164, 92): 6, (130, 186, 185): 7, (180, 148, 83): 8, (95, 152, 121): 9, (49, 161, 88): 10, (157, 123, 59): 11, (118, 97, 55): 12, (52, 128, 119): 13, (73, 80, 63): 14, (63, 59, 47): 15}

I made this code for it:

def change_palette(im):
    colors = im.palette.colors #get colors from palette
    if(PINK not in colors): #check there is pink (could be possible that there is not)
        return
    first = list(colors.keys())[list(colors.values()).index(0)] #colors is a dict so I get the key of the first index
    colors[first] = colors[PINK] 
    colors[PINK] = 0 #change the value of the colors
    newcolors = {}
    newcolors[PINK] = colors.pop(PINK)
    for key in colors:
        newcolors[key] = colors[key] #reorder the dict so it is in order
    colors = newcolors
    newcolors = []
    for key in colors: 
        for c in key:
            newcolors.append(c) #make it a list
    im.putpalette(newcolors) #change palette

But it makes the image look like this.

I tried it with other methods, like not using the putpalette and changing only the colors of the palette, but that does not make any change when I save the image. I understand the problem, but I can't seem to find a solution. I want it to look exactly the same, but with the color pink (255, 192, 203) in the first index.


Solution

  • If I understand correctly, you want to keep the image unchanged, and replace the index of the pink color to be 0.

    When we modify the palette, we are switching between the two colors:
    All the pixels with color (255, 255, 232) are switched to pink color (255, 192, 203), and all the pixels with pink color (255, 192, 203) are switched to color (255, 255, 232).

    The reason is that the (index) values of the pixels are not changed.
    After applying the new palette, all the pixels with (index) value 0 turned to be pink, and all the (index) value 1 turned to be (255, 255, 232).

    For fixing that, we have to switch the image data as well (assuming pink color index equals 1):

    • All the pixels with (index) value 0 should be modified to be value 1.
    • All the pixels with (index) value 1 should be modified to be value 0.

    For convenience we may convert the data to NumPy array, and convert back to PIL Image:

    pink_index = colors[PINK]  # Original index of pink color
    
    ... 
    
    indexed = np.array(im)  # Convert to NumPy array to easier access.
    new_indexed = indexed.copy()  # Make a copy of the NumPy array
    new_indexed[indexed == 0] = pink_index  # Replace all original 0 pixels with pink_index
    new_indexed[indexed == pink_index] = 0  # Replace all original pink_index pixels with 0
    new_im = Image.fromarray(new_indexed)  # Convert from NumPy array to Image.
    new_im.putpalette(newcolors)  # Set the palette
    

    Complete code sample:

    from PIL import Image
    import numpy as np
    
    PINK = (255, 192, 203)
    
    def change_palette(im):
        colors = im.palette.colors #get colors from palette
        if(PINK not in colors): #check there is pink (could be possible that there is not)
            return
        first = list(colors.keys())[list(colors.values()).index(0)] #colors is a dict so I get the key of the first index
        pink_index = colors[PINK]
        colors[first] = colors[PINK] 
        colors[PINK] = 0 #change the value of the colors
        newcolors = {}
        newcolors[PINK] = colors.pop(PINK)
        for key in colors:
            newcolors[key] = colors[key] #reorder the dict so it is in order
        colors = newcolors
        newcolors = []
        for key in colors: 
            for c in key:
                newcolors.append(c) #make it a list
        #im.putpalette(newcolors) #change palette
    
        indexed = np.array(im)  # Convert to NumPy array to easier access https://stackoverflow.com/a/33023875/4926757
        new_indexed = indexed.copy()  # Make a copy of the NumPy array
        new_indexed[indexed == 0] = pink_index  # Replace all original 0 pixels with pink_index
        new_indexed[indexed == pink_index] = 0  # Replace all original pink_index pixels with 0
    
        new_im = Image.fromarray(new_indexed)  # Convert from NumPy array to Image https://stackoverflow.com/a/39258561/4926757
        new_im.putpalette(newcolors)  # Set the palette
    
        return new_im
    
    
    img = Image.open('original_image.png')
    
    new_image = change_palette(img)
    
    new_image.save('changed_image.png')
    

    Result:
    enter image description here