Getting canvas coordinates in python -For mac especially - python

from keras.models import load_model
from tkinter import *
import tkinter as tk
import appscript
#import win32gui
from PIL import ImageGrab, Image
import numpy as np
model = load_model('mnist.h5')
def predict_digit(img):
#resize image to 28x28 pixels
img = img.resize((28,28))
#convert rgb to grayscale
img = img.convert('L')
img = np.array(img)
#reshaping to support our model input and normalizing
img = img.reshape(1,28,28,1)
img = img/255.0
#predicting the class
res = model.predict([img])[0]
return np.argmax(res), max(res)
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.x = self.y = 0
# Creating elements
self.canvas = tk.Canvas(self, width=300, height=300, bg = "white", cursor="cross")
self.label = tk.Label(self, text="Draw..", font=("Helvetica", 48))
self.classify_btn = tk.Button(self, text = "Recognise", command = self.classify_handwriting)
self.button_clear = tk.Button(self, text = "Clear", command = self.clear_all)
# Grid structure
self.canvas.grid(row=0, column=0, pady=2, sticky=W, )
self.label.grid(row=0, column=1,pady=2, padx=2)
self.classify_btn.grid(row=1, column=1, pady=2, padx=2)
self.button_clear.grid(row=1, column=0, pady=2)
#self.canvas.bind("<Motion>", self.start_pos)
self.canvas.bind("<B1-Motion>", self.draw_lines)
def clear_all(self):
self.canvas.delete("all")
def classify_handwriting(self):
HWND = self.canvas.winfo_id() # get the handle of the canvas
rect=self.canvas.coords(HWND)
#rect = win32gui.GetWindowRect(HWND) # get the coordinate of the canvas
a,b,c,d = rect
rect=(a+4,b+4,c-4,d-4)
im = ImageGrab.grab(rect)
digit, acc = predict_digit(im)
self.label.configure(text= str(digit)+', '+ str(int(acc*100))+'%')
def draw_lines(self, event):
self.x = event.x
self.y = event.y
r=8
HWND=self.canvas.create_oval(self.x-r, self.y-r, self.x + r, self.y + r, fill='black')
app = App()
mainloop()
Can anyone help me especially with this part :
HWND = self.canvas.winfo_id() # get the handle of the canvas
rect=self.canvas.coords(HWND)
#rect = win32gui.GetWindowRect(HWND) # get the coordinate of the canvas
a,b,c,d = rect
rect=(a+4,b+4,c-4,d-4)
im = ImageGrab.grab(rect)
This is the error that appears :
Exception in Tkinter callback
Traceback (most recent call last):
File "/Users/xxx/opt/anaconda3/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "<ipython-input-4-28fb2cc31a85>", line 52, in classify_handwriting
a,b,c,d = rect
ValueError: not enough values to unpack (expected 4, got 0)
I am trying to create an ocr program to identify handwriting by letting the user draw a number and return the current value with high accuracy .
But the problem faced was with the canvas and how to get the coordinates without using the win32gui module (Since im a mac user) so I'm looking for a solution or an alternative for win32gui method GetWindowRect

self.canvas.coords(HWND) is going to return the coordinates of an object on the canvas with the id or tag of whatever is in HWND. It does not return the coordinates of the canvas itself.
Since HWND doesn't represent an item on the canvas, self.canvas.coords(HWND) returns an empty list. That is why you get the error not enough values to unpack - your code requires four values but the list has zero.
If you are wanting the coordinates of the canvas on the physical screen there are other methods you can use. See the following code:
x, y = (self.canvas.winfo_rootx(), self.canvas.winfo_rooty())
width, height = (self.canvas.winfo_width(), self.canvas.winfo_height())
a, b, c, d = (x, y, x+width, y+height)

Correct! It works fine on MACOS. comment the "import win32gui" line. Modify "classify_handwriting" function as below:
def classify_handwriting(self):
x, y = (self.canvas.winfo_rootx(), self.canvas.winfo_rooty())
width, height = (self.canvas.winfo_width(),
self.canvas.winfo_height())
a, b, c, d = (x, y, x+width, y+height)
im = ImageGrab.grab(bbox=(a,b,c,d))
Hope it works in your local setting!

Related

Resetting image along canvas in Tkinter

I have used the code from this answer to draw and save images. In addition for my specific case, I needed a reset action/button, which would reset the canvas, so that a user can draw a new image and save it. Unfortunately, it doesn't do what I want, as it just overlaps the previous images, even though the canvas was reset. I understand that the image should be reset as well, but I don't know how to implement that.
Here is my modified code:
from PIL import ImageTk, Image, ImageDraw
import PIL
from tkinter import *
import os
import numpy as np
width = 900
height = 900
white = (255, 255, 255)
green = (0, 128, 128)
log_max = 150 # maximum values
stick_step = 10 # sticks step on the axis
path_temp = "C:/Users/<User>/<Folder>"
def save():
global image_number
filename = path_temp + "/" + "template.png"
image1.save(filename)
def activate_paint(e):
global lastx, lasty
cv.bind('<B1-Motion>', paint)
lastx, lasty = e.x, e.y
def paint(e):
global lastx, lasty
x, y = e.x, e.y
myLine = cv.create_line((lastx, lasty, x, y), width=5)
# --- PIL
draw.line((lastx, lasty, x, y), fill='black', width=5)
lastx, lasty = x, y
return myLine
def clear():
cv.delete('all')
draw_sticks()
# initialize Tkinter window
root = Tk()
root.title("Template")
# coordinates during drawing
lastx, lasty = None, None
# rectangular area intended for drawing a log of width and height similar to those of the log images with white background
cv = Canvas(root, width=width, height=height, bg='white')
# case specific axis generation
def draw_sticks():
# adding the line representing axis of a log
horiz_position_of_axis = width
cv.create_line(0, horiz_position_of_axis, width, horiz_position_of_axis, fill="#476042", width=3)
# add log assisting scale for the user
image_step = int(width*stick_step/log_max)
# adding a first stick and value as a text
cv.create_text(10, horiz_position_of_axis-20, font=("Calibri", 7), text="0")
cv.create_line(3, horiz_position_of_axis-10, 3, horiz_position_of_axis, fill="#476042", width=2)
# adding other sticks
for i in range(int(log_max/stick_step)):
stick_x_pos = (image_step*(i+1))
cv.create_line(stick_x_pos, horiz_position_of_axis-10, stick_x_pos, horiz_position_of_axis, fill="#476042", width=2)
if stick_x_pos!=width:
text = str(int(stick_x_pos*log_max/width))
cv.create_text(stick_x_pos, horiz_position_of_axis-20, font=("Calibri", 7), text=text)
else:
cv.create_text(width-10, horiz_position_of_axis-20, font=("Calibri", 7), text="150")
image1 = PIL.Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(image1)
def draw_save():
draw_sticks()
# bind the activate_paint function, which in turn activates paint function
cv.bind('<1>', activate_paint)
cv.pack(expand=YES, fill=BOTH)
# adding "Save" button
btn_save = Button(text="save", command=save)
btn_save.pack()
# reset=Button(text='Reset canvas',command=clear)
# reset.pack(side=LEFT)
root.mainloop()
# read the image as a grayscale array
filename = os.listdir(path_temp)
img = PIL.Image.open(path_temp + "/" + filename[0]).convert('LA')
template_gray = img.convert('L')
return template_gray
if __name__ == "__main__":
draw_save()
I am trying to reach the goal of resetting the canvas and image with the following function:
def clear():
cv.delete('all')
draw_sticks()
To reset the PIL image, you can just draw a white rectangle the size of the image:
def clear():
draw.rectangle((0, 0, width, height), width=0, fill='white')
cv.delete('all')
draw_sticks()

How to draw an image, so that my image is used as a brush

I created this little algorithm that is supposed to draw an image (imagine that my brush is an image) so that when I keep clicking I will draw the image, but as you can see if you test the code, it does not paint.
What it does is just moves the image across the Canvas.
Is there a way for the image to remain on the Canvas?
Here is my code:
from tkinter import *
from PIL import Image, ImageTk
master = Tk()
w = Canvas(master, width=800, height=400)
w.pack(expand = YES, fill = BOTH)
imagen = Image.open('C:/Users/Andres/Desktop/hola.png')
P_img = ImageTk.PhotoImage(imagen)
def paint( event ):
global w, P_img_crop
#I get the mouse coordinates
x, y = ( event.x - 1 ), ( event.y - 1 )
#I open and draw the image
img_crop = Image.open('C:/Users/Andres/Desktop/papa.png')
P_img_crop = ImageTk.PhotoImage(img_crop)
w.create_image((x,y), anchor=NW, image=P_img_crop)
w.bind( "<B1-Motion>", paint )
mainloop()
I GOT IT
I did not know that the image that was drawn on the canvas, should be saved, so what I did is store the images in a matrix, which belongs to the canvas.
Here is the code, just in case ...
from tkinter import *
from PIL import Image, ImageTk
master = Tk()
w = Canvas(master, width=800, height=400)
w.dib = [{} for k in range(10000)]
w.pack(expand = YES, fill = BOTH)
puntero = 0
def paint( event ):
global w, P_img_crop, puntero
#I get the mouse coordinates
x, y = ( event.x - 1 ), ( event.y - 1 )
#I open and draw the image
img_crop = Image.open('C:/Users/Andres/Documents/PROYECTOS INCONCLUSOS/PAINT MATEW PYTHON/hola.png')
w.dib[puntero]['image'] = ImageTk.PhotoImage(img_crop)
w.create_image((x,y), anchor=NW, image=w.dib[puntero]['image'])
puntero += 1
if(puntero >=10000):
puntero = 0
w.bind( "<B1-Motion>", paint )
mainloop()
All you need to do remove the image creation inside the paint() function. Then you will achieve what you want because otherwise it creates the image again and doesn't save a copy behind. In other words, when you move the brush, the previous image is garbage collected.
Code:
from tkinter import *
from PIL import Image, ImageTk
master = Tk()
w = Canvas(master, width=800, height=400)
w.pack(expand = YES, fill = BOTH)
img_crop = Image.open('yes.png')
P_img_crop = ImageTk.PhotoImage(img_crop)
def paint(event):
global P_img_crop
#I get the mouse coordinates
x, y = event.x - 1, event.y - 1
#I open and draw the image
w.create_image(x, y, image = P_img_crop)
w.bind("<B1-Motion>", paint)
master.mainloop()
Hope this helps!

Python tkinter get color from canvas

I have created a simple Tkinter application with a canvas widget like this:
from tkinter import *
root = Tk()
root.geometry("500x500-500+500")
canvas = Canvas(root, width = 400, height = 400, bg = "white")
canvas.pack()
canvas.create_line(0, 0, 200, 100, width = 20, fill = "black")
root.mainloop()
My question is, how can I get the color of the canvas in a specific position? Say for instance I clicked somewhere on the line, how can I get back the color "black" from that?
In other words, if I wanted a function like this,
def getColor(cnvs, event = None):
x = event.x
y = event.y
# somehow gets the color of cnvs at position (x, y) and stores it as color
return color
how would I go about doing that?
You can take a screen shot of the canvas using Pillow.ImageGrab module and get the required pixel color from the snapshot image:
from PIL import ImageGrab
def get_color(cnvs, event):
x, y = cnvs.winfo_rootx()+event.x, cnvs.winfo_rooty()+event.y
# x, y = cnvs.winfo_pointerx(), cnvs.winfo_pointery()
image = ImageGrab.grab((x, y, x+1, y+1)) # 1 pixel image
return image.getpixel((0, 0))
Note that the color returned is in (R, G, B) format.

Tkinter - bouncing icon script

I'm writing a simple bouncing icon program (Python 3.7, Windows 10 x64) to get the feel for Tkinter and canvases. I've posted my code below. My problem with the program is that it clips the edges of the icon (in the direction of motion). If I slow the motion down a bit (by increasing the value in the after method) it no longer clips, but the motion is choppy. Maybe I'm overthinking this, it basically does what I've aimed for. But if this were a game or other project that mattered, how would this be prevented?
from tkinter import *
import os
from PIL import Image, ImageTk
xinc, yinc = 5, 5
def load_image(width, height, imgpath):
loadimg = Image.open(imgpath)
pwid, phi = loadimg.size
pf1, pf2 = 1.0*width/pwid, 1.0*height/phi
pfactor = min([pf1, pf2])
pwidth, pheight = int(pwid*pfactor), int(phi*pfactor)
loaded = loadimg.resize((pwidth, pheight), Image.ANTIALIAS)
loaded = ImageTk.PhotoImage(loaded)
return loaded
def bounce():
global xinc
global yinc
cwid = int(dash.cget('width'))
chi = int(dash.cget('height'))
x = dash.coords(dashposition)[0]
y = dash.coords(dashposition)[1]
if x > cwid-10 or x < 10:
xinc = -xinc
if y > chi-10 or y < 10:
yinc = -yinc
dash.move(dashposition, xinc, yinc)
dash.after(15, bounce)
root = Tk()
root.configure(bg='black')
dash = Canvas(root, bg='black', highlightthickness=0, width=400, height=300)
dash.grid(row=0, column=0, padx=2, pady=2)
imagepath = os.getcwd() + '/img/cloudy.png'
image = load_image(20, 20, imagepath)
x, y = 10, 10
dashposition = dash.create_image(x, y, anchor=CENTER, image=image, tags=('current'))
bounce()
root.mainloop()
cloudy.png
There are two contributing factors to the clipping. The main problem is that load_image(20, 20, imagepath) will only result in a 20x20 object if the original image is square. But your cloud object isn't square. And your border collision calculations will only work if the rescaled cloud object is 20x20. So we need to modify that. The other issue is that you aren't compensating for the Canvas's border. The easy way to do that is to set it to zero with bd=0.
Its usually a good idea during GUI development to use various colors so you know exactly where your widgets are. So that we can more easily see when the cloud hits the Canvas border I've set the root window color to red. I also increased the .after delay, because I just couldn't see what was happening at the speed you set it at. ;) And I made the cloud a bit bigger.
I've made a couple of other minor changes to your code, the main one being that I got rid of that "star" import, which dumps over 100 Tkinter names into your namespace.
Update
I've reduced xinc & yinc to 1, and improved the bounce bounds calculation. (And incorporated jasonharper's suggestion re cwid & chi). I'm no longer seeing any clipping on my machine, and the motion is smoother. I also reduced the Canvas padding to 1 pixel, but that should have no effect on clipping. I just tried it with padx=10, pady=10 and it works as expected.
import tkinter as tk
import os
from PIL import Image, ImageTk
def load_image(width, height, imgpath):
loadimg = Image.open(imgpath)
pwid, phi = loadimg.size
pf1, pf2 = width / pwid, height / phi
pfactor = min(pf1, pf2)
pwidth, pheight = int(pwid * pfactor), int(phi * pfactor)
loaded = loadimg.resize((pwidth, pheight), Image.ANTIALIAS)
loaded = ImageTk.PhotoImage(loaded)
return loaded, pwidth // 2, pheight // 2
xinc = yinc = 1
def bounce():
global xinc, yinc
x, y = dash.coords(dashposition)
if not bx <= x < cwid-bx:
xinc = -xinc
if not by <= y < chi-by:
yinc = -yinc
dash.move(dashposition, xinc, yinc)
dash.after(5, bounce)
root = tk.Tk()
root.configure(bg='red')
dash = tk.Canvas(root, bg='black',
highlightthickness=0, width=800, height=600, bd=0)
dash.grid(row=0, column=0, padx=1, pady=1)
cwid = int(dash.cget('width'))
chi = int(dash.cget('height'))
imagepath = 'cloudy.png'
size = 50
image, bx, by = load_image(size, size, imagepath)
# print(bx, by)
dashposition = dash.create_image(bx * 2, by * 2,
anchor=tk.CENTER, image=image, tags=('current'))
bounce()
root.mainloop()

How to move a Canvas image using Tkinter?

Guys I am working on a code which needs to move an image in python using Tkinter(canvas)
This is creating problems for me. The image is being displayed but it is not moving.
from Tkinter import *
root = Tk()
root.title("Click me!")
def next_image(event):
global toggle_flag
global x, y, photo1
# display photo2, move to right, y stays same
canvas1.create_image(x+10, y, image=photo1)
canvas1.create_image(x+20, y, image=photo1)
canvas1.create_image(x+30, y, image=photo1)
canvas1.create_image(x+40, y, image=photo1)
canvas1.create_image(x+50, y, image=photo1)
canvas1.create_image(x+60, y, image=photo1)
canvas1.create_image(x+70, y, image=photo1)
canvas1.create_image(x+100, y, image=photo1)
image1 = "C:\Python26\Lib\site-packages\pygame\examples\data\ADN_animation.gif" #use some random gif
photo1 = PhotoImage(file=image1)
# make canvas the size of image1/photo1
width1 = photo1.width()
height1 = photo1.height()
canvas1 = Canvas(width=width1, height=height1)
canvas1.pack()
# display photo1, x, y is center (anchor=CENTER is default)
x = (width1)/2.0
y = (height1)/2.0
canvas1.create_image(x, y, image=photo1)
canvas1.bind('<Button-1>', next_image) # bind left mouse click
root.mainloop()
Canvas provides move method. Arguments are item you want to move, relative x offset from the previous position, y offset.
You need to save the return value of the create_image to pass it to the move method.
Also make sure the canvas is expandable (pack(expand=1, fill=BOTH) in the following code)
from Tkinter import *
root = Tk()
def next_image(event):
canvas1.move(item, 10, 0) # <--- Use Canvas.move method.
image1 = r"C:\Python26\Lib\site-packages\pygame\examples\data\ADN_animation.gif"
photo1 = PhotoImage(file=image1)
width1 = photo1.width()
height1 = photo1.height()
canvas1 = Canvas(width=width1, height=height1)
canvas1.pack(expand=1, fill=BOTH) # <--- Make your canvas expandable.
x = (width1)/2.0
y = (height1)/2.0
item = canvas1.create_image(x, y, image=photo1) # <--- Save the return value of the create_* method.
canvas1.bind('<Button-1>', next_image)
root.mainloop()
UPDATE according to the comment
Using after, you can schedule the function to be called after given time.
def next_image(event=None):
canvas1.move(item, 10, 0)
canvas1.after(100, next_image) # Call this function after 100 ms.

Categories

Resources