OiO.lk Blog python Pillow – How to Apply Color Overlay on Grayscale Sprite for Softer Results
python

Pillow – How to Apply Color Overlay on Grayscale Sprite for Softer Results


I’m trying to color a grayscale seed sprite to achieve a natural-looking result, but I’m running into issues. I have a base grayscale sprite, and I’m adding a color overlay to it. However, when I apply the overlay, the colors appear overly bright. I’d like to replicate the look in my reference image, where the colors are well-blended and subtle. How can I adjust the overlay or blending method to achieve a softer, more realistic effect?

(i add background on second image to make it easier to see since the color is white)

This is what i want to achive


This is my current progress


First image color #AEAEAEFF #ED1C24FF, and the second image color #23446FFF #C6AF32FF

Here’s my current approach:

  • I start with a grayscale base sprite, crop it and, and apply a color overlay.
  • I multiply the RGB overlay values by each pixel’s grayscale brightness, using this code:
def add_color_overlay(image: Image.Image, hex_color: str) -> Image:
    """Add a color overlay to the image based on pixel brightness, using an RGBA hex color."""
    hex_color = hex_color.lstrip('#')  # Remove the hash symbol if present
    overlay_r = int(hex_color[0:2], 16)
    overlay_g = int(hex_color[2:4], 16)
    overlay_b = int(hex_color[4:6], 16)

    blended_data = []
    for r, g, b, a in image.getdata():
        if a != 0:  # Only blend non-transparent pixels
            # Calculate grayscale brightness as an average of RGB channels
            grayscale_value = (r + g + b) // 3

            # Blend overlay color with the grayscale intensity
            blend_r = int(overlay_r * (grayscale_value / 255))
            blend_g = int(overlay_g * (grayscale_value / 255))
            blend_b = int(overlay_b * (grayscale_value / 255))

            # Append the blended color with original alpha
            blended_data.append((blend_r, blend_g, blend_b, a))
        else:
            # Keep transparent pixels as they are
            blended_data.append((0, 0, 0, 0))

    # Apply the blended data back to the image
    image.putdata(blended_data)
    return image
  • After colorizing the base and overlay images, I overlay the colored layer on top of the base, using transparency for blending:
async def get_seed_sprite(base: int, over: int, bg, fg) -> bytes | None:
    file_dir = os.path.join(os.getcwd(), "public", "assets", "rttex", "seed.rttex")
    with open(file_dir, "rb") as f:
        image = Image.open(rttex_unpack(f.read())) # Base Sprite Pattern

        img_base = image.crop((base * 16, 0, base * 16 + 16, 16))
        add_color_overlay(img_base, bg)
        img_over = image.crop((over * 16, 16, over * 16 + 16, 32))
        add_color_overlay(img_over, fg, is_overlay=True)

        img_base.paste(img_over, (0, 0), mask=img_over)
        image_file = BytesIO()
        img_base.save(image_file, format="PNG")
        image_file.seek(0)
    return image_file

How can I adjust my blending technique to achieve a softer, more natural overlay effect? Are there any specific blending modes, adjustments, or techniques that would help to better integrate the color overlay with the grayscale base in Python (preferably using PIL)?



You need to sign in to view this answers

Exit mobile version