Search code examples
pythonimagetkintertkinter-canvasphotoimage

tkinter - show image overlay on a canvas image with mouse move


I have two images that are the exact same size. One is the base background image and the other image is an altered version of the background image. I am trying to show a window of a static size (such as 100,100) of the altered image over the top of the background image where they line up wherever the mouse is moved. Sort of a moving window overlaying the altered image over the base image. I have been trying to modify some base code I found somewhere here on Stack Overflow, but can't seem to figure out how to modify it to get the desired result. Please suggest an alternative method if you think I am going about this the wrong way, or also if there is a close example somewhere I have missed. I am very new to tkinter. Also, I have no need for the coordinate system shown in my image examples below. Just the image alone is good.

from tkinter import *
from PIL import ImageTk, Image
from scipy.misc import imread

event2canvas = lambda e, c: (c.canvasx(e.x), c.canvasy(e.y))

# function to be called when mouse is moved
def move_window(event):
    # outputting x and y coords to console
    cx, cy = event2canvas(event, canvas)
    print("(%d, %d) / (%d, %d)" % (event.x, event.y, cx, cy))


if __name__ == "__main__":
    root = Tk()

    #setting up a tkinter canvas with scrollbars
    frame = Frame(root, bd=2, relief=SUNKEN)
    frame.grid_rowconfigure(0, weight=1)
    frame.grid_columnconfigure(0, weight=1)
    xscroll = Scrollbar(frame, orient=HORIZONTAL)
    xscroll.grid(row=1, column=0, sticky=E+W)
    yscroll = Scrollbar(frame)
    yscroll.grid(row=0, column=1, sticky=N+S)
    canvas = Canvas(frame, bd=0, xscrollcommand=xscroll.set, yscrollcommand=yscroll.set)
    canvas.grid(row=0, column=0, sticky=N+S+E+W)
    xscroll.config(command=canvas.xview)
    yscroll.config(command=canvas.yview)
    frame.pack(fill=BOTH,expand=1)

    # loading background and altered image into numpy array.
    background_image_data = imread("images/original.png", mode="L")
    foreground_image_data = imread("images/altered.png", mode="L")

    # Here is where the mouse coordinates should be injected to move the window over the background image
    window_data = foreground_image_data[500:600, 500:600]
    background_image_data[500:600, 500:600] = window_data

    img = ImageTk.PhotoImage(Image.fromarray(background_image_data))
    canvas.create_image(0, 0,image=img,anchor="nw")
    canvas.config(scrollregion=canvas.bbox(ALL), width=img.width(), height=img.height())

    test = canvas.bind("<ButtonPress-1>", move_window)

    root.mainloop()

Background Image: enter image description here

Altered Image: enter image description here

Desired Result: enter image description here


Solution

  • You actually did almost all of it. You just need to reposition some of the things and add a few things here and there (I'm not going to do it in a clean way otherwise I have to change more stuff. You can do that yourself later to not have duplicate lines in your code):

    1. Change this part of your code:

      # loading background and altered image into numpy array.
      background_image_data = imread("images/original.png", mode="L")
      foreground_image_data = imread("images/altered.png", mode="L")
      
      # Here is where the mouse coordinates should be injected to move the window over the background image
      window_data = foreground_image_data[500:600, 500:600]
      background_image_data[500:600, 500:600] = window_data
      
      img = ImageTk.PhotoImage(Image.fromarray(background_image_data))
      canvas.create_image(0, 0,image=img,anchor="nw")
      canvas.config(scrollregion=canvas.bbox(ALL), width=img.width(), height=img.height()) 
      

      to this:

      # loading background and altered image into numpy array.
      background_image_data = imread("images/original.png", mode="L")
      foreground_image_data = imread("images/altered.png", mode="L")
      '''Shouldn't change the original background image because
      that way the changes will be permanent'''
      bg_img = background_image_data.copy() 
      # Here is where the mouse coordinates should be injected to move the window over the background image
      x,y,wh = (50,50,100)
      window_data = foreground_image_data[y:y+wh,x:x+wh]
      bg_img[y:y+wh,x:x+wh] = window_data
      img = ImageTk.PhotoImage(Image.fromarray(bg_img))
      canvas.create_image(0, 0,image=img,anchor="nw")
      canvas.config(scrollregion=canvas.bbox(ALL), width=img.width(), height=img.height())
      
    2. Change "<ButtonPress-1>" to "<Motion>" so it would run by change of mouse position. Or change it to "<B1-Motion>" so it runs whenever you are holding down Left-Button and moving the mouse.

    3. Change this:

      def move_window(event):
          cx, cy = event2canvas(event, canvas)
      

      To this:

      def move_window(event):
          global img
          cx, cy = event2canvas(event, canvas)
          x,y,wh = (int(cx),int(cy),100)
          window_data = foreground_image_data[y:y+wh,x:x+wh]
          bg_img = background_image_data.copy()
          bg_img[y:y+wh,x:x+wh] = window_data
          img = ImageTk.PhotoImage(Image.fromarray(bg_img))
          canvas.create_image(0, 0,image=img,anchor="nw")
      

    And you're done! As you can see it's all your own code just moved around, and with a few spices. I haven't worked with canvas very much so I don't know if that last part will keep creating new image on top of the other or replacing an already existing one. So I don't know if it's the best code to use there, but it works.
    You better clean this code up because as you can see a lot of the code from move_window is the same as it was before the mainloop
    Good luck.