Search code examples
pythontkinterpython-imaging-library

Overlay effect on image in python Pillow


I am using python to get an overlay effect on an image on mouse click. How can I increase the transparency of the color so the underlying image remains visible. Currently the underlying image hides beneath. I couldn't find an option to control the opacity

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageDraw, ImageTk

class ImageEditor:
    def __init__(self):
        self.root = tk.Tk()
        self.image = None
        self.grid = None
        self.colors = [(255, 0, 0), (255, 255, 0), (0, 0, 255)]  # RGB colors
        self.current_color_index = 0
        self.display_image = None
        self.canvas = tk.Canvas(self.root)
        self.canvas.bind("<Button-1>", self.change_color)
        self.canvas.pack(side='left')

        self.load_btn = tk.Button(self.root, text='Load', command=self.load_image)
        self.load_btn.pack(side='right')

        self.save_btn = tk.Button(self.root, text='Save', command=self.save_image)
        self.save_btn.pack(side='right')
        
        self.root.mainloop()
        
    def load_image(self):
        img_path = filedialog.askopenfilename()
        self.image = Image.open(img_path).convert("RGB")  # Convert to RGB format
        self.grid = Image.new("RGBA", self.image.size, (0, 0, 0, 0))  # Create a transparent overlay image
        self.display_image = ImageTk.PhotoImage(self.image)
        self.canvas.config(width=self.image.width, height=self.image.height)
        self.canvas.create_image(0, 0, image=self.display_image, anchor='nw')

    def change_color(self, event):
        if self.image is None:
            return
        x, y = event.x, event.y
        # Define the region around the mouse click (adjust as needed)
        region = (x - 10, y - 10, x + 10, y + 10)
        draw = ImageDraw.Draw(self.grid)
        
        if self.current_color_index == 3:
            # If it's the fourth click, remove the color (make the region transparent)
            draw.rectangle(region, fill=(0, 0, 0, 0))
        else:
            color = self.colors[self.current_color_index]
            draw.rectangle(region, fill=color)
        
        self.display_image = ImageTk.PhotoImage(Image.alpha_composite(self.image.convert("RGBA"), self.grid))
        self.canvas.create_image(0, 0, image=self.display_image, anchor='nw')
        self.current_color_index = (self.current_color_index + 1) % 4  # Cycle through 4 values



    def save_image(self):
        if self.image is None:
            return
        file_path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG File", "*.png")], initialfile="output")
        if file_path:
            final_image = Image.alpha_composite(self.image.convert("RGBA"), self.grid)
            final_image.save(file_path, "PNG")

if __name__ == "__main__":
    app = ImageEditor()

Solution

  • I think there are several ways of doing this. One way, just using PIL, is to draw your shapes/overlays on a blank canvas and add whatever opacity/alpha you want into a mask layer, then at the end, paste your shapes onto your background using the alpha layer as a mask.

    This is the initial setup:

    #!/usr/bin/env python3
    
    from PIL import Image, ImageDraw, ImageOps
    
    # Make a red-blue-yellow gradient as background
    bg = Image.linear_gradient('L')
    bg = ImageOps.colorize(bg, 'red', 'yellow', 'blue')
    bg.save('DEBUG-bg.png')
    

    Background

    enter image description here

    And here is the drawing of overlays:

    # Make a canvas to draw on and an alpha mask for it
    canvas = Image.new("RGB", (256,256))
    mask   = Image.new('L',   (256,256))
    canvasDraw = ImageDraw.Draw(canvas)
    maskDraw   = ImageDraw.Draw(mask)
    
    # Draw a black rectangle with 64/255 alpha on left side
    canvasDraw.rectangle((10,10,120,250), 'black')
    maskDraw.rectangle((10,10,120,250), 64)
    
    # Draw a white rectangle with 150/255 alpha on right side
    canvasDraw.rectangle((130,10,250,250), 'white')
    maskDraw.rectangle((130,10,250,250), 150)
    
    # Paste shapes onto background with alpha mask
    bg.paste(canvas, mask)
    bg.save('result.png')
    

    Overlaid shapes with transparency

    enter image description here