In Tkinter's canvas is it possible to have mouse events ignore transparent parts of an image?

Viewed 239

I've got an image (drawn in Pillow) with a transparent background. I can add this onto a Tkinter canvas via create_image() and bind mouse events to it, e.g. button clicks.

However, Tkinter canvas triggers the mouse events for the entire image including the transparent background.

Is there any way in Tkinter to have mouse events ignore an image's transparent pixels?

Everything else works fine and as expected, however I've not been able to find any option or if it's even possible to have Tkinter ignore mouse events for transparency in images?

Thanks in advance to anyone who knows!

EDIT2: On request, more details. The situation is that I've got a map which serves as the background of the canvas object, on this map I draw routes, and I want those routes to react to mouse events such as clicks.

for route in travel_routes:

    route_map = route.draw_route_map(canvas_width, canvas_height)
    self.CanvasTravelMapRoutes.append(ImageTk.PhotoImage(route_map))
    
    self.CanvasTravelMap.create_image(
        0, 0, image = self.CanvasTravelMapRoutes[-1], anchor = tk.NW, tag = (route.RouteID, 'travel_route') )

self.CanvasTravelMap.tag_bind('travel_route', "<Button-1>", self.travel_canvas_route_click)

In the route class, the draw_route_map method works as follows:

route_map = Image.new("RGBA", (canvas_width, canvas_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(route_map)

draw.line()
draw.ellipse()
etc. etc.

The result is a route_map image laid on top of the canvas, but which is mostly transparent except for the lines and circles drawn to mark the route's course across the map.

All elements of this code function as designed, except that the canvas mouse events also respond to the transparent parts of the route_map images. Obviously, this means any click anywhere on the canvas always selects whichever route_map is on top and not the specific line/mark being clicked on.

1 Answers

So if you wanted to have this kind of collision you could try this:

from tkinter import Tk, Canvas
from PIL import Image, ImageTk
import requests


url = 'http://media-s3-us-east-1.ceros.com/g3-communications/images/2019/01/15/05eea4b9b9ce010d2dd6b0c063d2f5ca/p1-blob.png?imageOpt=1&fit=bounds&width=893'
data = requests.get(url, stream=True).raw
image = Image.open(data)


def on_opaque(event, offset, img):
    print('clicked on bounding box')
    x, y = event.x - offset[0], event.y - offset[1]
    if img.getpixel((x, y)) != 0:
        print('clicked on opaque pixel')


root = Tk()

photo = ImageTk.PhotoImage(image)

canvas = Canvas(root, height=image.height, width=image.width, highlightthickness=0)
canvas.pack(fill='both', expand=True)

offset_x, offset_y = 0, 0
c_img = canvas.create_image(offset_x, offset_y, image=photo, anchor='nw')
canvas.tag_bind(c_img, '<Button-1>', lambda e: on_opaque(e, (offset_x, offset_y), image))

root.mainloop()

I use image from internet, you can use your own, it is just that you can test it immediately. Basically it binds the whole image to button click, but then in the callback it additionally checks if the click was in a position where there is not a transparent background, also be careful with fake transparent background, I then suggest that you set the background of canvas to black and see if the bg behind image is black too.

Related