Python Forum
how do i find the canvas location of an object?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
how do i find the canvas location of an object?
#1
while i try to get the canvas or even the bbox coordinates of an object i am either getting None returned or the object is returning the memory location of the object and not the location on the canvas,

from tkinter import *
from PIL import ImageTk,Image
root = Tk()
canvas=Canvas(root,width=400,height=400)
canvas.pack()

class Image:
    def __init__(self):
        self.img = PhotoImage(file='./8bitship.png')
        self.id =  canvas.create_image(120,0,anchor=NW,image=self.img)

mship = Image()

mship_loc = canvas.bbox(mship)
print(mship_loc)
print(mship)
Reply
#2
A new record for most things wrong in 16 lines. I'm guessing this is the result of a lot of frustrated thrashing.

To start with, you cannot create a class named Image if you want to use a class named Image from the PIL library. You should never see two lines like this in any Python file.
from PIL import ImageTk,Image
..
class Image:
By defining a class named "Image" you have made it that you cannot use the Image class from PIL library. Even in this case where you don't need the PIL.Image class it is a bad idea to name classes after commonly used classes or modules in Python.

So mship is not an Image. Well, it is an Image, but it is your Image, not the kind of Image that you can add to a label or a canvas. In fact there are very few things that your Image can do. All it has is a couple of attributes. No methods are defined.

Even if mship was a PIL.Image or a tkinter.PhotoImage, you could not get it's bounding box. Image or PhotoImage is like "red". It is paint. You can paint an image as part of a button, or a label or you can paint an image on a canvas. But an image is not a thing that can be moved around or located. You cannot pack() and image. Image/PhotoImage is not a widget or a canvas object. It is just an interesting paint pattern.

The image doesn't become anything all that interesting until you use it. This code creates a canvas object that looks like your image. canvas.create_image() returns a handle to the newly created object. An ID that you can use to manipulate the object.
self.id =  canvas.create_image(120,0,anchor=NW,image=self.img)
Using the ID you can move the image around on the canvas. You can also use the ID to get the coordinates of the image or get the image's, I mean object's, bounding box.
import tkinter as tk

root = tk.Tk()
canvas = tk.Canvas(root)

img = tk.PhotoImage(file='ttt_x.png')
id = canvas.create_image(0, 0, image=img)

print(canvas.bbox(id))
print(canvas.coords(id))
print(canvas.bbox(img)
Output:
(-40, -40, 40, 40) [0.0, 0.0] None
Notice that bbox(id) returns a rectangle. bbox(img) returns None. This is because "img" is not a canvas object, id is. "img" just tells the canvas how to paint the object referenced by id.
Reply
#3
I did not take into account the name of the class for this example, honestly I didn’t realize I couldn’t name my classes after commonly used methods but it makes sense the same way we can’t save files named the same way, but let’s say the class was named Ship like in my other script. Could I then use the canvas.bbox.id() to get the coordinates from the class object on the canvas?
Or should I ditch the class object all together?
Reply
#4
I saw this in a book to promote coding with children bubble_blaster
It's a tkinter game that creates a ship using a canvas object,
bubbles -> canvas ovals and a bullet canvas line.
Reply
#5
A class would be nice. At a minimum it keeps a reference to the image around without adding lots of extra image variables. You could add some methods to make it more useful. Here's one of your early posts modified to use an image class with collision detection.
import tkinter as tk
 
class CanvasImage():  
    def __init__(self, canvas, x, y, file):
        self.canvas = canvas
        self.img = tk.PhotoImage(file=file)
        self.id = canvas.create_image(x, y, image=self.img)

    def bounds(self):
        return self.canvas.bbox(self.id)

    def coords(self):
        return self.canvas.coords(self.id)

    def move(self, dx, dy):
        self.canvas.move(self.id, dx, dy)

    def moveto(self, x, y):
        self.canvas.moveTo(self.id, x, y)

    def collide(self, object):
        a = self.bounds()
        b = object.bounds()
        if b[0] > a[2] or b[2] < a[0] or b[1] > a[3] or b[3] < a[1]:
            return False
        return True

def move_ship(ship, dx, dy):
    ship.move(dx, dy)
    if ship.collide(planet):
        quit()

root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()

ship = CanvasImage(canvas, 40, 40, 'ttt_x.png')
planet = CanvasImage(canvas, 200, 200, 'ttt_o.png')

root.bind("<Right>", lambda e: move_ship(ship, 5, 0))
root.bind("<Left>",  lambda e: move_ship(ship, -5, 0))
root.bind("<Up>",    lambda e: move_ship(ship, 0, -5))
root.bind("<Down>",  lambda e: move_ship(ship, 0 , 5))
Reply
#6
i know you are ready to get rid of me for a while but this collide method what is return false return True doing? i pretty much understand the rest ive been studying AABB collision for a few days now. i guess this has turned into a game.
Reply
#7
You're right, that is an ugly function. My logic was testing if a and b did not collide and returned the appropriate boolean value. It is sloppy coding using True and False instead returning the result of the comparison. To fix that I need to invert the logic. I could use not.
def collide(self, object):
    a = self.bounds()
    b = object.bounds()
    return not(b[0] > a[2] or b[2] < a[0] or b[1] > a[3] or b[3] < a[1])
That looks a little awkward, but it is better than before.

The bounding box is (left, top, right, bottom). The function does not make it clear that a[0] is a left. In Python it is easy to extract values from a collection. How does this function look if I replace indexing with more readable variable names.
def collide(self, object):
    aleft, atop, aright, abot = self.bounds()
    bleft, btop, bright, bbot = object.bounds()
    return not(bleft > aright or bright < aleft or btop > abot or bbot < atop)
That looks better, but there is still that ungainly not(). Lets get rid of that by inverting the logic.

bleft <= aright and bright >= aleft and btop <= abot and bbot >= atop

If I allow the ships to just touch without calling that a collision, I can shorten the expression a bit (drop the "="). I think I'll also change the order in the comparison to make it read better.
def collide(self, object):
    aleft, atop, aright, abot = self.bounds()
    bleft, btop, bright, bbot = object.bounds()
    return aleft < bright and atop < bbot and bleft < aright and btop < abot
I like that much better.
Reply
#8
No, the indexing is fine, I just wanted to make sure that the return false return True were working in that manner , I am teaching myself python and there is a lot I still don’t understand but thank you for your help so far.
Reply
#9
Indexing is not fine. Whenever you can make code easier to read without adverse side effects you should do so. a[0] is meaningless to the casual reader, or you when you come back and look at this code a year from now. aleft from ship.bounds is self documenting. Ideally it should be ship_left_bounds, but that makes the collision detection expression really long and I don't think it adds much to understanding.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] Resizing image inside Canvas (with Canvas' resize) Gupi 2 18,308 Jun-04-2019, 05:05 AM
Last Post: Gupi
  [Tkinter] Problem loading an image from directory into a Canvas object tiotony 3 1,858 Sep-02-2018, 06:47 PM
Last Post: woooee

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020