0

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....

just_learning
  • 413
  • 2
  • 11
  • 24
  • 1
    I wonder if you get the `DecompressionBomb` warning for using 90000000 pixel image – Delrius Euphoria Jul 12 '22 at 12:14
  • Yes, I used to get, but you put some commands in PIL and it fixes it.... – just_learning Jul 12 '22 at 12:27
  • 1
    Ah well, it doesnt fix. It ignores it, maybe. – Delrius Euphoria Jul 12 '22 at 12:40
  • 1
    Anyway, I am not sure of what you are asking. But what I think, when you click on the canvas, you want to get the coordinate of that click on the image, like if i click on the center of the image, instead of showing the width_of_canvas/2 and height_of_canvas/2 you want to see width_of_image/2 and height_of_image/2 as the coords. – Delrius Euphoria Jul 12 '22 at 12:42
  • Yes, otherwise we couldn't work with ```geotiff``` images... I mean they are extremely large both in pixels and size..... – just_learning Jul 12 '22 at 12:43
  • The image is displayed on a canvas. The above methods, *see* the canvas and return the coordinates of the the canvas and not the image's coordinates, when I click the mouse pointer. – just_learning Jul 12 '22 at 13:01
  • 1
    That would require some calculations based on canvas image's anchor position, I believe – Delrius Euphoria Jul 12 '22 at 13:07
  • 1
    If the top-left corner of the image is at (0, 0), then you can use `canvas.canvasx(event.x)` and `canvas.canvasy(event.y)` to get the real coordinates in the image. – acw1668 Jul 12 '22 at 13:24
  • 1
    If all you want is the coordinates of an item on a canvas, you can use `canvas.coords(item)` where item is the '`tagOrId`' of the image. – OmegaO333 Jul 12 '22 at 19:18

2 Answers2

1

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.

Explanation

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 e.x and e.y 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 e.x and e.y work with the image coordinate at the center.

Delrius Euphoria
  • 14,910
  • 3
  • 15
  • 46
  • ```File "stackoverflow_tkinter.py", line 11, in callback 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 TypeError: canvas_to_image_cords() got an unexpected keyword argument 'image'``` – just_learning Jul 13 '22 at 13:58
  • 1
    @just_learning Seems like there was a small part I missed while editing, fixed it right now – Delrius Euphoria Jul 13 '22 at 14:05
  • Ok, one last comment: How do I combine this: ```root = Tk() root['bg'] = 'black'``` with this: ```window = tk.Tk()``` ? I see a conflict in my code there... – just_learning Jul 13 '22 at 17:09
  • 1
    @just_learning Umm, you will have to change my imports to be `import tkinter as tk` and then `window = tk.Tk() window['bg'] = 'black'`. It is literally the same thing, just variable name changes and difference in how we import `tkinter` – Delrius Euphoria Jul 14 '22 at 05:30
  • I am trying these changes to your code in order to cover larger area and not having gaps on the Left, Right, Down of the canvas. But it does not work with my changes...Do you see something strange? ```img = ImageTk.PhotoImage(Image.open('test_code/UAV_image.tif')) print("img = ",img) canvas = Canvas(window, width=img.width(), height=img.height(),borderwidth=0, highlightthickness=0) canvas.pack(expand=True) img_tag = canvas.create_image(0, 0, image=img, anchor=tk.NW)``` Thanks... – just_learning Jul 15 '22 at 12:05
  • 1
    @just_learning I don't. Its hard to judge with just a single piece of code. I'd recommend you ask a new question. But from what I can see, do you need your canvas to be THAT big? Right now the canvas is _supposed_ to be as big as the image. – Delrius Euphoria Jul 15 '22 at 14:46
  • I have found stackoverflow link: https://stackoverflow.com/questions/33665542/tkinter-how-to-scroll-an-entire-canvas-using-arrow-keys How can I combine your code with this in order to get the image's coordinates and not the canvas' ? So, imagine that I leave the mouse without moving it in the same position on my desk, and I move the image using Right, Left, Up, Down. The coordinates each time I press the mouse button should be different since the image is moving via the keys. – just_learning Jul 16 '22 at 13:40
  • 1
    @just_learning As I mentioned before, it is better to ask a new question. Any more content on this answer not given in the question before, is just going to make the answer unreasonably bigger. So if you ask a new question, I will try my best to answer it – Delrius Euphoria Jul 16 '22 at 13:42
1

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
acw1668
  • 40,144
  • 5
  • 22
  • 34