I can display an image on my panel, what I need is to click on 2 spots in the picture and calculate the distance between them. I am having trouble with the event handler and how to use it similarly to a scanner in Java. For example, if I run the program and click once somewhere in the image, it runs all 3 methods at once which leads to give an error.
root = Tk()
img = ImageTk.PhotoImage(Image.open("target.PNG"))
#img = cv2.imread("target.PNG")
panel = Label(root, image = img)
panel.pack(side = "bottom", fill = "both", expand = "yes")
def leftClick(event):
global x0,y0
x0 = event.x
y0 = event.y
return x0, y0
panel.bind("<Button-1>", leftClick)
def rightClick(event):
global x1,y1
x1 = event.x
y1 = event.y
return x1, y1
panel.bind("<Button-1>", rightClick)
def getDistance(event):
distance = math.sqrt( ((x0-x1)**2)+((y0-y1)**2) )
print(distance)
panel.bind("<Button-1>", getDistance)
root.mainloop()
What I'm looking for is to execute each step once at a time. The final step to calculate the distance can be done outside a method it doesn't really matter. I just need to get the coordinates to work first. Please let me know how I could proceed to solve this.
You Can Try This Two:
Process 1(Uses mouse left click, right click, middle(scroll) click):
The following code takes
(x0, y0) from mouse-left-click
(x1, y1) from mouse-right-click
and then prints distance between them on mouse-middle(scroll)-click
from tkinter import *
from PIL import ImageTk, Image
import math
root = Tk()
img = ImageTk.PhotoImage(Image.open("Logo.png"))
panel = Label(root, image=img)
panel.pack(side="bottom", fill="both", expand="yes")
x0 = 0
y0 = 0
x1 = 0
y1 = 0
def leftClick(event):
global x0, y0
x0 = event.x
y0 = event.y
# return [x0, y0]
panel.bind("<Button-1>", leftClick)
def rightClick(event):
global x1, y1
x1 = event.x
y1 = event.y
# return x1, y1
panel.bind("<Button-3>", rightClick)
def getDistance(event):
global x0, y0, x1, y1
distance = math.sqrt(((x0 - x1)**2)+((y0 - y1)**2))
print(distance)
panel.bind("<Button-2>", getDistance)
root.mainloop()
Process 2(Uses only mouse left click):
The following code takes
(x0, y0) from first mouse-left-click
(x1, y1) from second mouse-left-click
and then prints distance between them on third mouse-left-click
from tkinter import *
from PIL import ImageTk, Image
import math
root = Tk()
img = ImageTk.PhotoImage(Image.open("Logo.png"))
panel = Label(root, image=img)
panel.pack(side="bottom", fill="both", expand="yes")
counter = 0
x0 = 0
x1 = 0
y0 = 0
y1 = 0
def getDistance(event):
global counter, x0, y0, x1, y1
if counter == 0:
x0 = event.x
y0 = event.y
counter += 1
elif counter == 1:
x1 = event.x
y1 = event.y
counter += 1
elif counter == 2:
distance = math.sqrt(((x0 - x1)**2)+((y0 - y1)**2))
print(distance)
counter = 0
panel.bind("<Button-1>", getDistance)
root.mainloop()
Below is a demo for count distance from a start point to a end point, which takes a DRAG operation with mouse left button.
import tkinter as tk
from PIL import ImageTk, Image
import math
start_point_x, start_point_y, end_point_x, end_point_y = 0, 0, 0, 0
def mouse_left_down_detection(event):
global start_point_x, start_point_y
start_point_x = event.x
start_point_y = event.y
def mouse_left_release_detection(event):
global end_point_x, end_point_y
end_point_x = event.x
end_point_y = event.y
print(start_point_x, start_point_y, end_point_x, end_point_y)
print(get_instance(start_point_x, start_point_y, end_point_x, end_point_y))
def get_instance(x1, y1, x2, y2):
return math.sqrt((pow(abs(x2-x1), abs(x2-x1))+pow(abs(y2-y1), abs(y2-y1))))
image_path = "andy.jpg"
root = tk.Tk()
img = ImageTk.PhotoImage(Image.open(image_path))
panel = tk.Label(root, image=img)
# Bind event mouse left down
panel.bind("<Button-1>", mouse_left_down_detection)
# Bind event mouse left release and calculate distance
panel.bind("<ButtonRelease-1>", mouse_left_release_detection)
panel.pack(side="bottom", fill="both", expand="yes")
root.mainloop()
Related
I have a tool to capture screenshots but this tool only captures screenshots when you hold button-1 and move to the right side, when you hold button-1 and move it to the left side or up, it does not capture the screenshot, what is the reason and how can I fix it? I thought about it, but I think it's a mathematical problem, I'm waiting for your help.
import tkinter as tk
from PIL import Image, ImageTk, ImageGrab, ImageEnhance
root = tk.Tk()
root.resizable(0, 0)
def show_image(image):
win = tk.Toplevel()
win.image = ImageTk.PhotoImage(image)
tk.Label(win, image=win.image).pack()
win.grab_set()
win.wait_window(win)
def area_sel():
x1 = y1 = x2 = y2 = 0
roi_image = None
def on_mouse_down(event):
nonlocal x1, y1
x1, y1 = event.x, event.y
canvas.create_rectangle(x1, y1, x1, y1, outline='red', tag='roi')
def button_release(event):
print("ok")
win.destroy()
def on_mouse_move(event):
nonlocal roi_image, x2, y2
x2, y2 = event.x, event.y
canvas.delete('roi-image') # remove old overlay image
canvas.update()
roi_image = image.crop((x1, y1, x2, y2)) # get the image of selected region
canvas.image = ImageTk.PhotoImage(roi_image)
canvas.create_image(x1, y1, image=canvas.image, tag=('roi-image'), anchor='nw')
canvas.coords('roi', x1, y1, x2, y2)
# make sure the select rectangle is on top of the overlay image
canvas.lift('roi')
root.withdraw() # hide the root window
image = ImageGrab.grab() # grab the fullscreen as select region background
bgimage = ImageEnhance.Brightness(image).enhance(0.3) # darken the capture image
# create a fullscreen window to perform the select region action
win = tk.Toplevel()
win.attributes('-fullscreen', 1)
win.attributes('-topmost', 1)
canvas = tk.Canvas(win, highlightthickness=0)
canvas.pack(fill='both', expand=1)
tkimage = ImageTk.PhotoImage(bgimage)
canvas.create_image(0, 0, image=tkimage, anchor='nw', tag='images')
# bind the mouse events for selecting region
win.bind('<ButtonPress-1>', on_mouse_down)
win.bind('<ButtonRelease>', button_release)
win.bind('<B1-Motion>', on_mouse_move)
# use Esc key to abort the capture
win.bind('<Escape>', lambda e: win.destroy())
# make the capture window modal
win.focus_force()
win.grab_set()
win.wait_window(win)
root.deiconify() # restore root window
# show the capture image
if roi_image:
show_image(roi_image)
tk.Button(root, text='select area', width=30, command=area_sel).pack()
root.mainloop()
You need to keep x1 <= x2 and y1 <= y2 when cropping image and creating the roi rectangle:
def normalize(x1, y1, x2, y2):
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
return x1, y1, x2, y2
def on_mouse_move(event):
nonlocal roi_image, x2, y2
x2, y2 = event.x, event.y
rect = normalize(x1, y1, x2, y2)
canvas.delete('roi-image') # remove old overlay image
roi_image = image.crop(rect) # get the image of selected region
canvas.image = ImageTk.PhotoImage(roi_image)
canvas.create_image(rect[:2], image=canvas.image, tag=('roi-image'), anchor='nw')
canvas.coords('roi', rect)
# make sure the select rectangle is on top of the overlay image
canvas.lift('roi')
I want to create a rectangle with a checkbutton on a canvas, and then having sliders to adjust the height and width of this rectangle, but it seems that the rectangle I create does not carry over to the other functions, as I just create the triangle, and when adjusting the height and width, nothing happens.
from tkinter import *
def rect():
rectangle = canvas.create_rectangle(20,50, 40, 10, fill="green")
def width(e, rectangle):
x0, y0, x1, y1 = canvas.coords(rectangle) # get the coords of rect
x1 = float(e) # calc new coords
canvas.coords(rectangle, x0, y0, x1, y1) # set new coords
def height(h, rectangle):
x0, y0, x1, y1 = canvas.coords(rectangle)
y1 = float(h) + 40
canvas.coords(rectangle, x0,y0,x1,y1)
root = Tk()
frame = Frame(root)
frame.pack()
create_rect = Checkbutton(frame, text='rect', variable=IntVar, command = rect)
create_rect.pack()
slider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="blue",command = width)
slider.pack()
slider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="green",command = height)
slider.pack()
canvas = Canvas(root,height=500,width=360)
canvas.pack()
root.mainloop()
In order to access rectangle outside rect(), you can make it a global variable. Below is the modified code:
from tkinter import *
rectangle = None # initialize rectangle
def rect():
# tell Python rectangle is a global variable
global rectangle
if cbvar.get() == 1:
# create the rectangle if checkbutton is checked
if rectangle is None:
rectangle = canvas.create_rectangle(20, 10, 20+wslider.get(), 10+hslider.get(), fill="green")
else:
# destroy the rectangle if checkbutton is not checked
if rectangle:
canvas.delete(rectangle)
rectangle = None
def width(e):
# make sure rectangle exists
if rectangle:
x0, y0, x1, y1 = canvas.coords(rectangle) # get the coords of rect
x1 = x0 + float(e) # calc new coords
canvas.coords(rectangle, x0, y0, x1, y1) # set new coords
def height(h):
# make sure rectangle exists
if rectangle:
x0, y0, x1, y1 = canvas.coords(rectangle)
y1 = y0 + float(h)
canvas.coords(rectangle, x0, y0, x1, y1)
root = Tk()
frame = Frame(root)
frame.pack()
cbvar = IntVar()
create_rect = Checkbutton(frame, text='rect', variable=cbvar, command=rect)
create_rect.pack()
wslider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="blue", command=width)
wslider.pack()
hslider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="green", command=height)
hslider.pack()
canvas = Canvas(root, height=500, width=360)
canvas.pack()
root.mainloop()
Thanks to lots of help in Drag and drop the object in Tkinter UI, I could manage to draw three square that are draggable.
Now I am trying to draw 3 lines between each squares and I cannot find way to enable it. What I tried is following :
from tkinter import *
window = Tk()
window.state('zoomed')
window.configure(bg = 'white')
def drag(event):
new_x = event.x_root - window.winfo_rootx()
new_y = event.y_root - window.winfo_rooty()
event.widget.place(x=new_x, y=new_y,anchor=CENTER)
card = Canvas(window, width=10, height=10, bg='red1')
card.place(x=300, y=600,anchor=CENTER)
card.bind("<B1-Motion>", drag)
another_card = Canvas(window, width=10, height=10, bg='red2')
another_card.place(x=600, y=600,anchor=CENTER)
another_card.bind("<B1-Motion>", drag)
third_card = Canvas(window, width=10, height=10, bg='red3')
third_card.place(x=600, y=600,anchor=CENTER)
third_card.bind("<B1-Motion>", drag)
def line(x1, y1, x2, y2):
print(x1, y1, x2, y2)
Canvas.create_line(x1, y1, x2, y2, fill="green")
coor_1 = canvas.coords(card)
coor_2 = canvas.coords(another_card)
line(coor_1[0],coor_1[1],coor_1[0],coor_2[1])
window.mainloop()
It didn't work and I don't think it will work since this code does not catch the change occurred by dragging object, But I cannot guess how to code since I do not understand how the event function works completely. How should I make a code for it ?
for the purpose of self-study:
import tkinter as tk
window = tk.Tk()
window.state('zoomed')
class DragAndDropArea(tk.Canvas):
def __init__(self,master, **kwargs):
tk.Canvas.__init__(self,master, **kwargs)
self.active = None
card_I = self.draw_card(300,600, 100,100, 'red1')
card_II = self.draw_card(600,600, 100,100, 'red2')
card_III = self.draw_card(400,400, 100,100, 'red3')
self.bind_tention(card_I,card_III)
self.bind_tention(card_I,card_II)
self.bind_tention(card_III,card_II)
self.bind('<ButtonPress-1>', self.get_item)
self.bind('<B1-Motion>',self.move_active)
self.bind('<ButtonRelease-1>', self.set_none)
def set_none(self,event):
self.active = None
def get_item(self,event):
try:
item = self.find_withtag('current')
self.active = item[0]
except IndexError:
print('no item was clicked')
def move_active(self,event):
if self.active != None:
coords = self.coords(self.active)
width = coords[2] - coords[0] #x2-x1
height= coords[1] - coords[3] #y1-y2
position = coords[0],coords[1]#x1,y1
x1 = event.x - width/2
y1 = event.y - height/2
x2 = event.x + width/2
y2 = event.y + height/2
self.coords(self.active, x1,y1, x2,y2)
try:
self.update_tention(self.active)
except IndexError:
print('no tentions found')
def update_tention(self, tag):
tentions = self.find_withtag(f'card {tag}')
for tention in tentions:
bounded_cards = self.gettags(tention)
card = bounded_cards[0].split()[-1]
card2= bounded_cards[1].split()[-1]
x1,y1 = self.get_mid_point(card)
x2,y2 = self.get_mid_point(card2)
self.coords(tention, x1,y1, x2,y2)
self.lower(tention)
def draw_card(self, x,y, width,height, color):
x1,y1 = x,y
x2,y2 = x+width,y+height
reference = self.create_rectangle(x1,y1,x2,y2,
fill = color)
return reference
def bind_tention(self, card, another_card):
x1,y1 = self.get_mid_point(card)
x2,y2 = self.get_mid_point(another_card)
tag_I = f'card {card}'
tag_II= f'card {another_card}'
reference = self.create_line(x1,y1,x2,y2, fill='green',
tags=(tag_I,tag_II))
self.lower(reference)
def get_mid_point(self, card):
coords = self.coords(card)
width = coords[2] - coords[0] #x2-x1
height= coords[1] - coords[3] #y1-y2
position = coords[0],coords[1]#x1,y1
mid_x = position[0] + width/2
mid_y = position[1] - height/2
return mid_x,mid_y
area = DragAndDropArea(window, bg='white')
area.pack(fill='both',expand=1)
window.mainloop()
I am trying to model a simple solar system in Tkinter using circles and moving them around in canvas. However, I am stuck trying to find a way to animate them. I looked around and found the movefunction coupled with after to create an animation loop. I tried fidgeting with the parameters to vary the y offset and create movement in a curved path, but I failed while trying to do this recursively or with a while loop. Here is the code I have so far:
import tkinter
class celestial:
def __init__(self, x0, y0, x1, y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
sol_obj = celestial(200, 250, 250, 200)
sx0 = getattr(sol_obj, 'x0')
sy0 = getattr(sol_obj, 'y0')
sx1 = getattr(sol_obj, 'x1')
sy1 = getattr(sol_obj, 'y1')
coord_sol = sx0, sy0, sx1, sy1
top = tkinter.Tk()
c = tkinter.Canvas(top, bg='black', height=500, width=500)
c.pack()
sol = c.create_oval(coord_sol, fill='black', outline='white')
top.mainloop()
Here's something that shows one way to do what you want using the tkinter after method to update both the position of the object and the associated canvas oval object. It uses a generator function to compute coordinates along a circular path representing the orbit of one of the Celestial instances (named planet_obj1).
import math
try:
import tkinter as tk
except ImportError:
import Tkinter as tk # Python 2
DELAY = 100
CIRCULAR_PATH_INCR = 10
sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))
class Celestial(object):
# Constants
COS_0, COS_180 = cos(0), cos(180)
SIN_90, SIN_270 = sin(90), sin(270)
def __init__(self, x, y, radius):
self.x, self.y = x, y
self.radius = radius
def bounds(self):
""" Return coords of rectangle surrounding circlular object. """
return (self.x + self.radius*self.COS_0, self.y + self.radius*self.SIN_270,
self.x + self.radius*self.COS_180, self.y + self.radius*self.SIN_90)
def circular_path(x, y, radius, delta_ang, start_ang=0):
""" Endlessly generate coords of a circular path every delta angle degrees. """
ang = start_ang % 360
while True:
yield x + radius*cos(ang), y + radius*sin(ang)
ang = (ang+delta_ang) % 360
def update_position(canvas, id, celestial_obj, path_iter):
celestial_obj.x, celestial_obj.y = next(path_iter) # iterate path and set new position
# update the position of the corresponding canvas obj
x0, y0, x1, y1 = canvas.coords(id) # coordinates of canvas oval object
oldx, oldy = (x0+x1) // 2, (y0+y1) // 2 # current center point
dx, dy = celestial_obj.x - oldx, celestial_obj.y - oldy # amount of movement
canvas.move(id, dx, dy) # move canvas oval object that much
# repeat after delay
canvas.after(DELAY, update_position, canvas, id, celestial_obj, path_iter)
top = tk.Tk()
top.title('Circular Path')
canvas = tk.Canvas(top, bg='black', height=500, width=500)
canvas.pack()
sol_obj = Celestial(250, 250, 25)
planet_obj1 = Celestial(250+100, 250, 15)
sol = canvas.create_oval(sol_obj.bounds(), fill='yellow', width=0)
planet1 = canvas.create_oval(planet_obj1.bounds(), fill='blue', width=0)
orbital_radius = math.hypot(sol_obj.x - planet_obj1.x, sol_obj.y - planet_obj1.y)
path_iter = circular_path(sol_obj.x, sol_obj.y, orbital_radius, CIRCULAR_PATH_INCR)
next(path_iter) # prime generator
top.after(DELAY, update_position, canvas, planet1, planet_obj1, path_iter)
top.mainloop()
Here's what it looks like running:
I am trying to model a simple solar system in Tkinter using circles and moving them around in canvas. However, I am stuck trying to find a way to animate them. I looked around and found the movefunction coupled with after to create an animation loop. I tried fidgeting with the parameters to vary the y offset and create movement in a curved path, but I failed while trying to do this recursively or with a while loop. Here is the code I have so far:
import tkinter
class celestial:
def __init__(self, x0, y0, x1, y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
sol_obj = celestial(200, 250, 250, 200)
sx0 = getattr(sol_obj, 'x0')
sy0 = getattr(sol_obj, 'y0')
sx1 = getattr(sol_obj, 'x1')
sy1 = getattr(sol_obj, 'y1')
coord_sol = sx0, sy0, sx1, sy1
top = tkinter.Tk()
c = tkinter.Canvas(top, bg='black', height=500, width=500)
c.pack()
sol = c.create_oval(coord_sol, fill='black', outline='white')
top.mainloop()
Here's something that shows one way to do what you want using the tkinter after method to update both the position of the object and the associated canvas oval object. It uses a generator function to compute coordinates along a circular path representing the orbit of one of the Celestial instances (named planet_obj1).
import math
try:
import tkinter as tk
except ImportError:
import Tkinter as tk # Python 2
DELAY = 100
CIRCULAR_PATH_INCR = 10
sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))
class Celestial(object):
# Constants
COS_0, COS_180 = cos(0), cos(180)
SIN_90, SIN_270 = sin(90), sin(270)
def __init__(self, x, y, radius):
self.x, self.y = x, y
self.radius = radius
def bounds(self):
""" Return coords of rectangle surrounding circlular object. """
return (self.x + self.radius*self.COS_0, self.y + self.radius*self.SIN_270,
self.x + self.radius*self.COS_180, self.y + self.radius*self.SIN_90)
def circular_path(x, y, radius, delta_ang, start_ang=0):
""" Endlessly generate coords of a circular path every delta angle degrees. """
ang = start_ang % 360
while True:
yield x + radius*cos(ang), y + radius*sin(ang)
ang = (ang+delta_ang) % 360
def update_position(canvas, id, celestial_obj, path_iter):
celestial_obj.x, celestial_obj.y = next(path_iter) # iterate path and set new position
# update the position of the corresponding canvas obj
x0, y0, x1, y1 = canvas.coords(id) # coordinates of canvas oval object
oldx, oldy = (x0+x1) // 2, (y0+y1) // 2 # current center point
dx, dy = celestial_obj.x - oldx, celestial_obj.y - oldy # amount of movement
canvas.move(id, dx, dy) # move canvas oval object that much
# repeat after delay
canvas.after(DELAY, update_position, canvas, id, celestial_obj, path_iter)
top = tk.Tk()
top.title('Circular Path')
canvas = tk.Canvas(top, bg='black', height=500, width=500)
canvas.pack()
sol_obj = Celestial(250, 250, 25)
planet_obj1 = Celestial(250+100, 250, 15)
sol = canvas.create_oval(sol_obj.bounds(), fill='yellow', width=0)
planet1 = canvas.create_oval(planet_obj1.bounds(), fill='blue', width=0)
orbital_radius = math.hypot(sol_obj.x - planet_obj1.x, sol_obj.y - planet_obj1.y)
path_iter = circular_path(sol_obj.x, sol_obj.y, orbital_radius, CIRCULAR_PATH_INCR)
next(path_iter) # prime generator
top.after(DELAY, update_position, canvas, planet1, planet_obj1, path_iter)
top.mainloop()
Here's what it looks like running: