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()
Related
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!
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!
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.
I'm using Python Image and Tkinder. I am creating a blank image and I would like every click on it a pixel to turn red, but the image does not refresh.
What is the best way to do this?
import tkinter as tk
from PIL import Image, ImageTk
# PIL accesses images in Cartesian co-ordinates, so it is Image[columns, rows]
img = Image.new( 'RGB', (500,500), "white") # create a new white image
pixels = img.load() # create the pixel map
window = tk.Tk()
canvas = tk.Canvas(window, width=img.size[0], height=img.size[1])
canvas.pack()
image_tk = ImageTk.PhotoImage(img)
canvas.create_image(img.size[0]//2, img.size[1]//2, image=image_tk)
def mouseClick( event):
pixels[event.x, event.y]= (255,0,0) #print pixel red
canvas.bind("<Button-1>", mouseClick)
window.mainloop()
I don't know anything about tkinter but this seems to do what you want - it may be horrible but maybe it will suffice until someone comes along who knows better...
#!/usr/bin/env python3
import tkinter as tk
from PIL import Image, ImageTk
# PIL accesses images in Cartesian co-ordinates, so it is Image[columns, rows]
img = Image.new( 'RGB', (500,500), "white") # create a new white image
window = tk.Tk()
canvas = tk.Canvas(window, width=img.size[0], height=img.size[1])
canvas.pack()
image_tk = ImageTk.PhotoImage(img)
canvas.create_image(img.size[0]//2, img.size[1]//2, image=image_tk)
def mouseClick( event):
x, y = event.x, event.y
print("x: {}, y: {}".format(x,y))
# Update image
img.putpixel((x, y),(255,0,0))
# Update screen
canvas.create_oval(x, y, x, y, width = 0, fill = 'red')
canvas.bind("<Button-1>", mouseClick)
window.mainloop()
Here was the code to draw a line. Is it possible to let the user drag a line so that it forms a curve?
from tkinter import Canvas, Tk
# Image dimensions
w,h = 640,480
# Create canvas
root = Tk()
canvas = Canvas(root, width = w, height = h, bg = 'white')
canvas.pack()
def on_click(event):
""" set starting point of the line """
global x1, y1
x1 = event.x
y1 = event.y
def on_click_release(event):
""" draw the line """
canvas.create_line(x1, y1, event.x, event.y)
def clear_canvas(event):
canvas.delete('all')
canvas.bind("<Button-1>", on_click)
canvas.bind("<ButtonRelease-1>", on_click_release)
root.bind("<Key-c>", clear_canvas)
root.mainloop()
Once again thank youuuuu!!! :)))))
For an arc you need to track the mouse as it moves across the screen, rather than just the start and end points.
The code below will only create arcs from the bottom left, to the bottom right of a rectangle, but you can add any other arcs you want by changing the start angle and the extent angle of the arc.
from tkinter import Canvas, Tk, ARC
# Image dimensions
w,h = 640,480
# Create canvas
root = Tk()
canvas = Canvas(root, width = w, height = h, bg = 'white')
canvas.pack()
# curve points
global points
global temp_arc
points = []
temp_arc = None
def arc():
x = [point[0] for point in points]
y = [point[1] for point in points]
return canvas.create_arc(x[0], y[0], x[-1], y[-1], start = 0, style = ARC, width = 2, extent = 180)
def motion(event):
global temp_arc
points.append([event.x, event.y])
if temp_arc != None:
canvas.delete(temp_arc)
temp_arc = arc()
def on_click_release(event):
arc()
global points
points = []
def clear_canvas(event):
canvas.delete('all')
canvas.bind("<B1-Motion>", motion)
canvas.bind("<ButtonRelease-1>", on_click_release)
root.bind("<Key-c>", clear_canvas)
root.mainloop()