Search code examples
pythonpython-imaging-librarygradient

Python Pillow(PIL) Not completely recoloring gradients?


I am having an issue where I'm using Pillow to recolor an image that has a lot of soft gradients but it seems to not completely color the most translucent part of these gradients, with the recolored image having a gradient that is not as smooth. Is there a way to fix this issue? Example Images and current code below.

enter image description here

Original Gradient: 1: https://i.sstatic.net/VFi75.png

enter image description here

Recolored Gradient: 1: https://i.sstatic.net/e5iNa.png

Here is the Original transparent PNG of the image

import random
import Owl_Attributes
from PIL import Image, ImageColor

# I create the image here and convert the color code to RGBA
RGB_im = image_base_accent3.convert("RGBA")

datas = RGB_im.getdata()


newData = []

for item in datas:
    if item[0] == 208 and item[1] == 231 and item[2] == 161:
        newData.append((255, 0, 0, item[3]))
    else:
        newData.append(item)
RGB_im.putdata(newData)

RGB_im.save('Owl_project_pictures\_final_RGB.png')

Solution

  • First, a couple of things to consider:

    • Inspect your images before you start work. Yours has an alpha channel that is pretty much pointless and irrelevant so I would discard that to save space and processing time.

    • Using for loops over Python lists of pixels is slow, inefficient, and error-prone in Python. Try to use built-in functions based on C code, or to use vectorised functions like Numpy.

    On to your image. There are a whole load of shades and gradations of tone in your image and dealing with one separately through if statements is going to be difficult. I would suggest you want to use HSV colourspace instead.

    I think you want the basic result to be a very saturated red with the lightness dictated by the lightness of the original image.

    So, I would make an image with:

    • Hue=0 (see lower part of this diagram), and
    • Saturation=255 (i.e. fully saturated), and
    • Value (i.e. brightness) of the original image.

    In code that might look like this:

    #!/usr/bin/env python3
    
    # ImageMagick command-line "equivalent"
    # magick -size 599x452 xc:black xc:white \( VFi75.png -colorspace gray +level 0,60% \) +combine HSL result.png
        
    from PIL import Image
        
    # Load image and create HSV version
    im  = Image.open('VFi75.png')
    HSV = im.convert('HSV')
    
    # Split into separate channels for processing, discarding Hue and Saturation
    _, _, V = HSV.split()
    
    # Synthesize Hue channel, same size as input image, filled with 0, to make Red
    H = Image.new('L', (im.width, im.height), 0)
    
    # Synthesize Saturation channel, same size as input image, filled with 255, to make fully saturated
    S = Image.new('L', (im.width, im.height), 255)
    
    # Recombine synthesized H, S and V (based on original image brightness) back into a recombined image
    RGB = Image.merge('HSV', (H,S,V)).convert('RGB')
    
    # Save processed result
    RGB.save('result.png')
    

    enter image description here


    If you wanted to make it lime green, you would change the Hue angle like this:

    # Synthesize Hue channel, same size as input image, filled with 120, to make Lime Green
    H = Image.new('L', (im.width, im.height), 120)
    

    enter image description here


    If you wanted to make it less saturated, you would change the saturation like this:

    # Synthesize Saturation channel, same size as input image, filled with 64, to make less saturated
    S = Image.new('L', (im.width, im.height), 64)
    

    enter image description here