简体   繁体   中英

How to get the image coordinates with tkinter and not the canvas coordinates

So, I am using this code: Tkinter: How to scroll an entire canvas using arrow keys? . And this guy's code: Selecting an area of an image with a mouse and recording the dimensions of the selection and more specifically this:

def get_mouse_posn(event):
    global topy, topx

    topx, topy = event.x, event.y

def update_sel_rect(event):
    global rect_id
    global topy, topx, botx, boty

    botx, boty = event.x, event.y
    canvas.coords(rect_id, topx, topy, botx, boty)  # Update selection rect.

The idea is that I depict a big image, that does not fit in my laptop's screen. It is 10.000 * 9.000 pixels. So by using the arrows: Up, Down, Right, Left from my keyboard I can navigate throughout the image. Everything works fine up to here. But, when I use mouse click I use the guy's code in order to get the pixel coordinates (x,y). The problem is that I get the canvas' coordinates, so even I navigate to the top down of the image, when I place the mouse at the upper left corner it will give me 0,0. The latter are the canvas' coordinates and not the image's coordinates. I searched on google, and I found this suggestion: Coordinates of image pixel on tkinter canvas in OOP which does not bring something new. The rationale is more or less implemented in the first code (link that I posted). So, how can I get the image's coordinates and not the canvas' coordinates? I cannot post minimum runnable code, things are too complex and the whole code contains many lines....

You can actually create a function that will check the anchor position of the image, based on the tag you give it(or the Id) and then give the image coordinate based on the clicked canvas coordinate.

def canvas_to_image_cords(canvas: Canvas, x: int, y: int, image: PhotoImage, tagOrId=''):
    anchor = 'center'
    if tagOrId:
        anchor = canvas.itemcget(tagOrId, 'anchor')
    
    w, h = canvas.winfo_reqwidth(), canvas.winfo_reqheight()
    
    if anchor == 'center':
        img_xpos, img_ypos = image.width()/2, image.height()/2
        start_x, start_y = img_xpos-w/2, img_ypos-h/2
    elif anchor == 'nw':
        start_x, start_y = 0, 0
    # And so on for different anchor positions if you want to make this more modular
    
    req_x, req_y = start_x+x, start_y+y

    return req_x, req_y

The way this would work in 'center' anchor is because image is centered and kept in the canvas, so now you can find out where the image starts from the width/height of the canvas. And then you will have to subtract it from the anchor point. By default, the anchor of a canvas image is center, so you may not have to create it for all the other anchor positions if you leave the anchor option empty.

Usage:

from tkinter import * # Avoid this and use import tkinter as tk
from PIL import Image, ImageTk

root = Tk()
root['bg'] = 'black'


def callback(e):
    print()
    
    x,y = canvas_to_image_cords(canvas=canvas, x=e.x, y=e.y, image=img, tagOrId='img') # Can also pass img_tag as tagOrId
    print(x,y)


def canvas_to_image_cords(canvas: Canvas, x: int, y: int, image: PhotoImage, tagOrId=''):
    anchor = 'center'
    if tagOrId:
        anchor = canvas.itemcget(tagOrId, 'anchor')
    
    w, h = canvas.winfo_reqwidth(), canvas.winfo_reqheight()
    
    if anchor == 'center':
        img_xpos, img_ypos = image.width()/2, image.height()/2
        start_x, start_y = img_xpos-w/2, img_ypos-h/2
    elif anchor == 'nw':
        start_x, start_y = 0, 0

    req_x, req_y = start_x+x, start_y+y

    return req_x, req_y


img = ImageTk.PhotoImage(Image.open('hero-big.png'))  # 10000x9000 pixel image
canvas = Canvas(root, highlightthickness=0)
canvas.pack(padx=20, pady=20)

img_tag = canvas.create_image(0, 0, image=img, tag='img') # By default anchor is center
canvas.bind('<1>', callback)

root.mainloop()

Hopefully the color coded image will explain this better.

解释

Our current image position is at the green circle at the center and we need it to be the top left corner of the canvas(the black & white circle). So we need to push back(subtract) half of canvas's width/height inorder to reach the black & white circle. And then you can add the x,y coordinate you need and continue to get the image position you need.

And why do we need to push back to the top left corner? Because ex and ey starts from top left corner. It is rather easy(IMO) to find the image coordinate at the top left corner of the canvas, than to make ex and ey work with the image coordinate at the center.

Note that (event.x, event.y) is the screen coordinates relative to the top-left corner of the canvas. To get the real canvas coordinates , you can use canvas.canvasx() and canvas.canvasy() :

# get the real coordinates in the canvas
# note that canvasx() and canvasy() return float number
x, y = int(canvas.canvasx(event.x)), int(canvas.canvasy(event.y))

So if the top-left corner of the image is at (img_x, img_y) inside the canvas, then the coordinates in the image will be (x-img_x, y-img_y) .


To apply on your posted code:

def get_mouse_posn(event):
    global topx, topy
    topx, topy = int(canvas.canvasx(event.x)), int(canvas.canvasy(event.y))

def update_sel_rect(event):
    global botx, boty
    botx, boty = int(canvas.canvasx(event.x)), int(canvas.canvasy(event.y))
    canvas.coords(rect_id, topx, topy, botx, boty)

To get the selected region in the image:

def get_selected_region():
    global topx, topy, botx, boty
    # make sure topx < botx and topy < boty
    topx, botx = min(topx, botx), max(topx, botx)
    topy, boty = min(topy, boty), max(topy, boty)
    # top-left corner of image is at (img_x, img_y) inside canvas
    region = (topx-img_x, topy-img_y, botx-img_x, boty-img_y)
    return region

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM