Python tkinter get color from canvas - python

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.

Related

Getting canvas coordinates in python -For mac especially

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!

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!

Is there a way to have a draggable ruler in tkinter?

Is it possible?
Just to place it anywhere onto the window and drag it anywhere I want it.
Here is an example of draggable item I am looking to achieve like from HTML (I know, it's got nothing to do with html): How TO - Create a Draggable HTML Element.
Here is an example of what I mean by a ruler. A ruler like this:
It's only for display purposes and not calculating anything..
I'll be using Grid manager in this case.
I'll be happy to see any examples!
Standard module tkinter and ttk doesn't have rulers and I don't know any external module for tkinter which has rulers.
Using Canvas I can create widget which draws lines with numbers.
But it is still primitive widget which doesn't resize, doesn't scroll lines and numbers, doesn't rescale, and doesn't show mouse position.
EDIT: Now rules show mouse position using red lines. But if there is no Canvas then they have to know offset - how far they are from left top corner of window.
import tkinter as tk
class VRuler(tk.Canvas):
'''Vertical Ruler'''
def __init__(self, master, width, height, offset=0):
super().__init__(master, width=width, height=height)
self.offset = offset
step = 10
# start at `step` to skip line for `0`
for y in range(step, height, step):
if y % 50 == 0:
# draw longer line with text
self.create_line(0, y, 13, y, width=2)
self.create_text(20, y, text=str(y), angle=90)
else:
self.create_line(2, y, 7, y)
self.position = self.create_line(0, 0, 50, 0, fill='red', width=2)
def set_mouse_position(self, y):
y -= self.offset
self.coords(self.position, 0, y, 50, y)
class HRuler(tk.Canvas):
'''Horizontal Ruler'''
def __init__(self, master, width, height, offset=0):
super().__init__(master, width=width, height=height)
self.offset = offset
step = 10
# start at `step` to skip line for `0`
for x in range(step, width, step):
if x % 50 == 0:
# draw longer line with text
self.create_line(x, 0, x, 13, width=2)
self.create_text(x, 20, text=str(x))
else:
self.create_line((x, 2), (x, 7))
self.position = self.create_line(0, 0, 0, 50, fill='red', width=2)
def set_mouse_position(self, x):
x -= self.offset
self.coords(self.position, x, 0, x, 50)
def motion(event):
x, y = event.x, event.y
hr.set_mouse_position(x)
vr.set_mouse_position(y)
def click(event):
print(event.x, event.y)
root = tk.Tk()
root['bg'] = 'black'
vr = VRuler(root, 25, 250)#, offset=25)
vr.place(x=0, y=28)
hr = HRuler(root, 250, 25)#, offset=25)
hr.place(x=28, y=0)
c = tk.Canvas(root, width=250, height=250)
c.place(x=28, y=28)
#root.bind('<Motion>', motion) # it needs offset=28 if there is no Canvas
#root.bind('<Button-1>', click)
c.bind('<Motion>', motion)
c.bind('<Button-1>', click)
root.mainloop()

refresh the screen when changing the color of a pixel (Python Image and Tkinter)

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()

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