Python Tkinter save canvas as image using PIL - python

I have this code which lets the user draw on the canvas and save it as a jpeg file.
As mentioned in this post, I tried to draw in parallel on the canvas and in memory using the PIL so that I can save it as a jpeg instead of postscript. It seemed to be working until I found out that some of the images I saved with PIL are not the same as what was drawn on the canvas.
I assume canvas.create_line and draw.line from the PIL image draw module function similarly and should give similar output.
Below is what went wrong:
For example, when I draw a "T" it seems alright (Left is my drawing, right is the saved image).
But when I draw an "A" , the output image seems a bit weird.
This is my current code:
import Tkinter as tk
import Image,ImageDraw
class ImageGenerator:
def __init__(self,parent,posx,posy,*kwargs):
self.parent = parent
self.posx = posx
self.posy = posy
self.sizex = 200
self.sizey = 200
self.b1 = "up"
self.xold = None
self.yold = None
self.coords= []
self.drawing_area=tk.Canvas(self.parent,width=self.sizex,height=self.sizey)
self.drawing_area.place(x=self.posx,y=self.posy)
self.drawing_area.bind("<Motion>", self.motion)
self.drawing_area.bind("<ButtonPress-1>", self.b1down)
self.drawing_area.bind("<ButtonRelease-1>", self.b1up)
self.button=tk.Button(self.parent,text="Done!",width=10,bg='white',command=self.save)
self.button.place(x=self.sizex/7,y=self.sizey+20)
self.button1=tk.Button(self.parent,text="Clear!",width=10,bg='white',command=self.clear)
self.button1.place(x=(self.sizex/7)+80,y=self.sizey+20)
self.image=Image.new("RGB",(200,200),(255,255,255))
self.draw=ImageDraw.Draw(self.image)
def save(self):
print self.coords
self.draw.line(self.coords,(0,128,0),width=3)
filename = "temp.jpg"
self.image.save(filename)
def clear(self):
self.drawing_area.delete("all")
self.coords=[]
def b1down(self,event):
self.b1 = "down"
def b1up(self,event):
self.b1 = "up"
self.xold = None
self.yold = None
def motion(self,event):
if self.b1 == "down":
if self.xold is not None and self.yold is not None:
event.widget.create_line(self.xold,self.yold,event.x,event.y,smooth='true',width=3,fill='blue')
self.coords.append((self.xold,self.yold))
self.xold = event.x
self.yold = event.y
if __name__ == "__main__":
root=tk.Tk()
root.wm_geometry("%dx%d+%d+%d" % (400, 400, 10, 10))
root.config(bg='white')
ImageGenerator(root,10,10)
root.mainloop()
Where did I go wrong and what should I do to save the exact same picture that is drawn on the canvas as a jpeg image?

I managed to solved my problem.In fact i was just being dumb. Even though canvas.create_line and draw.line have the similar function, i didn't use the same exact data to draw out both images. after making changes, this is my working code.
import Tkinter as tk
import Image,ImageDraw
class ImageGenerator:
def __init__(self,parent,posx,posy,*kwargs):
self.parent = parent
self.posx = posx
self.posy = posy
self.sizex = 200
self.sizey = 200
self.b1 = "up"
self.xold = None
self.yold = None
self.drawing_area=tk.Canvas(self.parent,width=self.sizex,height=self.sizey)
self.drawing_area.place(x=self.posx,y=self.posy)
self.drawing_area.bind("<Motion>", self.motion)
self.drawing_area.bind("<ButtonPress-1>", self.b1down)
self.drawing_area.bind("<ButtonRelease-1>", self.b1up)
self.button=tk.Button(self.parent,text="Done!",width=10,bg='white',command=self.save)
self.button.place(x=self.sizex/7,y=self.sizey+20)
self.button1=tk.Button(self.parent,text="Clear!",width=10,bg='white',command=self.clear)
self.button1.place(x=(self.sizex/7)+80,y=self.sizey+20)
self.image=Image.new("RGB",(200,200),(255,255,255))
self.draw=ImageDraw.Draw(self.image)
def save(self):
filename = "temp.jpg"
self.image.save(filename)
def clear(self):
self.drawing_area.delete("all")
self.image=Image.new("RGB",(200,200),(255,255,255))
self.draw=ImageDraw.Draw(self.image)
def b1down(self,event):
self.b1 = "down"
def b1up(self,event):
self.b1 = "up"
self.xold = None
self.yold = None
def motion(self,event):
if self.b1 == "down":
if self.xold is not None and self.yold is not None:
event.widget.create_line(self.xold,self.yold,event.x,event.y,smooth='true',width=3,fill='blue')
self.draw.line(((self.xold,self.yold),(event.x,event.y)),(0,128,0),width=3)
self.xold = event.x
self.yold = event.y
if __name__ == "__main__":
root=tk.Tk()
root.wm_geometry("%dx%d+%d+%d" % (400, 400, 10, 10))
root.config(bg='white')
ImageGenerator(root,10,10)
root.mainloop()

make a litte change about mouse event
"""
画板,鼠标移动速度太快可以造成不连续的点
tkinter 8.6.11
https://stackoverflow.com/questions/17915440/python-tkinter-save-canvas-as-image-using-pil
"""
import tkinter as tk
from PIL import Image, ImageDraw
class ImageGenerator:
def __init__(self, parent, posx, posy, *kwargs):
self.parent = parent
self.posx = posx
self.posy = posy
self.sizex = 280
self.sizey = 280
self.penColor = "white" # 画笔的颜色 (255, 255, 255)
self.backColor = "black" # 画布背景色 (0, 0, 0)
self.penWidth = 10 # 笔刷的宽度
self.drawing_area = tk.Canvas(
self.parent, width=self.sizex, height=self.sizey, bg=self.backColor
)
self.drawing_area.place(x=self.posx, y=self.posy)
self.drawing_area.bind("<B1-Motion>", self.motion)
self.button = tk.Button(
self.parent, text="Done", width=10, bg="white", command=self.save
)
self.button.place(x=self.sizex / 7, y=self.sizey + 20)
self.button1 = tk.Button(
self.parent, text="Clear", width=10, bg="white", command=self.clear
)
self.button1.place(x=(self.sizex / 7) + 80, y=self.sizey + 20)
self.image = Image.new("RGB", (self.sizex, self.sizey), (0, 0, 0))
self.draw = ImageDraw.Draw(self.image)
def save(self):
filename = "temp.jpg"
self.image.save(filename)
def clear(self):
"""将画板和image清空"""
self.drawing_area.delete("all")
self.image = Image.new("RGB", (self.sizex, self.sizey), (0, 0, 0))
self.draw = ImageDraw.Draw(self.image)
def motion(self, event):
"""在画板和image上同时绘制"""
self.drawing_area.create_oval(
event.x,
event.y,
event.x + self.penWidth,
event.y + self.penWidth,
fill=self.penColor,
outline=self.penColor,
) # 在画布上画
self.draw.ellipse(
(
(event.x, event.y),
(event.x + self.penWidth, event.y + self.penWidth),
),
fill=self.penColor,
outline=self.penColor,
width=self.penWidth,
) # 在生成的图上画,point、line都太细
if __name__ == "__main__":
root = tk.Tk()
root.wm_geometry("%dx%d+%d+%d" % (300, 350, 10, 10))
root.config(bg="white")
ImageGenerator(root, 10, 10)
root.mainloop()

Related

tkinter askcolor - can't receive data while choosing color

i'm building an online paint with tkinter, and use the function "askcolor" to pick a color. when the color palette is open, i can't receive any data that comes from the server, which causes paintings that someone did on one computer not viewing on the computer of the person who was choosing color. i need a solution that will let me receive data while the color palette is on.
class Paint(object):
DEFAULT_PEN_SIZE = 5.0
DEFAULT_COLOR = 'black'
def __init__(self):
self.root = Tk()
self.root.config(cursor="#curs.cur")
self.root.geometry("1300x731")
self.root.resizable(width=False, height=False)
self.c = Canvas(self.root, bg='white', width=1300, height=731)
self.c.grid(row=0, columnspan=5)
self.color = "black"
self.toolbar_img = Image.open("toolbar.png")
self.zoom = 1
self.pixels_x, self.pixels_y = tuple([int(self.zoom * x) for x in self.toolbar_img.size])
self.toolbar_photo = ImageTk.PhotoImage(self.toolbar_img.resize((self.pixels_x, self.pixels_y)))
self.toolbar = Label(self.root, image=self.toolbar_photo).place(relx=0.5, rely=0.03, anchor='center')
self.pen_button = Button(self.root, text='pen', command=self.use_pen)
self.pen_button.place(relx=0.1, rely=0.035, anchor='center')
self.brush_button = Button(self.root, text='back', command=self.use_brush)
self.brush_button.place(relx=0.3, rely=0.035, anchor='center')
self.brush_button = Button(self.root, text='brush', command=self.use_brush)
self.brush_button.place(relx=0.45, rely=0.035, anchor='center')
self.color_button = Button(self.root, text='color', command=self.choose_color)
self.color_button.place(relx=0.6, rely=0.035, anchor='center')
self.eraser_button = Button(self.root, text='eraser', command=self.use_eraser)
self.eraser_button.place(relx=0.75, rely=0.035, anchor='center')
self.choose_size_button = Scale(self.root, from_=1, to=99, orient=HORIZONTAL)
self.choose_size_button.place(relx=0.9, rely=0.035, anchor='center')
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.setup()
self.run()
self.root.mainloop()
def on_closing(self):
msg = "quit,"
my_socket.send(msg.encode())
my_socket.close()
self.root.destroy()
exit()
def setup(self):
self.old_x = None
self.old_y = None
self.old_x2 = None
self.old_y2 = None
self.line_width = self.choose_size_button.get()
self.color = self.DEFAULT_COLOR
self.eraser_on = False
self.active_button = self.pen_button
self.c.bind('<B1-Motion>', self.paint)
self.c.bind('<ButtonRelease-1>', self.reset)
def use_pen(self):
self.activate_button(self.pen_button)
def use_brush(self):
self.activate_button(self.brush_button)
def choose_color(self):
self.eraser_on = False
self.color = askcolor(color=self.color)[1]
def use_eraser(self):
self.activate_button(self.eraser_button, eraser_mode=True)
def activate_button(self, some_button, eraser_mode=False):
self.active_button.config(relief=RAISED)
some_button.config(relief=SUNKEN)
self.active_button = some_button
self.eraser_on = eraser_mode
def paint(self, event):
self.line_width = self.choose_size_button.get()
paint_color = 'white' if self.eraser_on else self.color
if self.old_x and self.old_y:
self.c.create_line(self.old_x, self.old_y, event.x, event.y,
width=self.line_width, fill=paint_color,
capstyle=ROUND, smooth=TRUE, splinesteps=36)
self.old_x = event.x
self.old_y = event.y
data_list = [str(self.old_x), str(self.old_y), paint_color, str(self.line_width), current_room]
datasend = pickle.dumps(data_list, -1)
my_socket.sendall(datasend)
def paint_server(self, x, y, color, width):
self.line_width = self.choose_size_button.get()
paint_color = 'white' if self.eraser_on else self.color
if self.old_x2 and self.old_y2:
self.c.create_line(self.old_x2, self.old_y2, x, y,
width=width, fill=color,
capstyle=ROUND, smooth=TRUE, splinesteps=36)
self.old_x2 = x
self.old_y2 = y
def reset(self, event):
self.old_x, self.old_y = None, None
self.old_x2, self.old_x2 = None, None
time.sleep(0.1)
data=["reset", current_room]
datasnd = pickle.dumps(data, -1)
my_socket.send(datasnd)
def reset_server(self):
self.old_x, self.old_y = None, None
self.old_x2, self.old_x2 = None, None
def run(self):
while True:
rlist, wlist, xlist = select.select([my_socket], [my_socket], [])
for sock in rlist:
data = sock.recv(1024)
try:
data = pickle.loads(data)
except:
continue
print(data)
if data[0] == "reset":
self.reset_server()
else:
newx = int(data[0])
newy = int(data[1])
newcolor = data[2]
newwidth = int(data[3])
self.paint_server(newx, newy, newcolor, newwidth)
self.root.update()

How to move a shape while retaining original size in tkinter

I am trying to have the rectangle I create spawn at the bottom of my tkinter canvas instead of spawning at the top without affecting its size. I have only found this post and this doesn't help me as it does not describe how to move the shape to the bottom of the canvas
Here's my code:
from tkinter import *
from tkinter.ttk import *
#Temporary
width = 1024
height=768
class GFG:
def __init__(self, master = None):
self.master = master
self.master.configure(bg="black") #Apart from the canvas colour
self.x = 0
self.y = 0
self.canvas = Canvas(master, background="black", highlightthickness=0, width=width, height=height) #Canvas colour
self.rectangle = self.canvas.create_rectangle(5, 5, 25, 25, fill='white')
self.canvas.pack()
self.movement()
def movement(self):
self.canvas.move(self.rectangle, self.x, self.y)
self.canvas.after(100, self.movement)
def left(self, event):
print(event.keysym)
self.x = -5
self.y = 0
def right(self, event):
print(event.keysym)
self.x = 5
self.y = 0
if __name__ == "__main__":
master = Tk()
master.title("Black Hole Settlers")
gfg = GFG(master)
master.bind("<KeyPress-Left>", lambda e: gfg.left(e))
master.bind("<KeyPress-Right>", lambda e: gfg.right(e))
mainloop()
Pastebin
Thanks,
swanserquack

Tkinter/Pillow - How to make (0, 0) in the same place as in Pillow

So I was trying to figure out who my coordinates were messed up when adding text to an image using canvas coordinates , until I noticed that (0, 0) is off:
How would I put the origin of the canvas in the same place as Pillow does(or at least account for this when adding the text)?
class PictureWindow(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.x = self.y = 0
self.rect = None
self.tex = None
self.start_x = None
self.start_y = None
image = Image.open(file)
smaller_image = image.resize((round(image.size[0]/2), round(image.size[1]/2)))
img = ImageTk.PhotoImage(smaller_image)
self.canvas = tk.Canvas(self, width=img.width(), height=img.height())
self.canvas.img = img
self.canvas.create_image(0, 0, image=img, anchor=tk.NW)
self.canvas.pack(expand=True)
self.canvas.create_text((0, 0), text="(0, 0)", fill=self._from_rgb((225, 225, 225)))
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.finished = tk.Button(self, text="Add Watermark", command=self.Watermark)
self.finished.pack()
self.go_back = tk.Button(self, text="Go back to change settings", command=self.go_away)
self.go_back.pack()
def _from_rgb(self, rgb):
r, g, b = rgb
return f'#{r:02x}{g:02x}{b:02x}'
def go_away(self):
self.withdraw()
def on_button_press(self, event):
self.start_x = event.x
self.start_y = event.y
self.canvas.delete(self.tex)
self.tex = None
if not self.rect:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, fill=self._from_rgb((249, 0, 0)), stipple='gray12')
def on_move_press(self, event):
self.curX, self.curY = (event.x, event.y)
self.text_x = ((self.start_x + self.curX) / 2)
self.text_y = ((self.start_y + self.curY) / 2)
self.canvas.coords(self.rect, self.start_x, self.start_y, self.curX, self.curY)
def on_button_release(self, event):
font_preview_size = int(font_size)
font_preview_almost = (font_preview_size)
font_preview = int(font_preview_almost)
if not self.tex:
self.tex = self.canvas.create_text((self.text_x, self.text_y), text=watermark_text, font=('Gotham Medium', font_preview), fill=self._from_rgb((color)))
def Watermark(self):
self.font_size_var = font_size
Font_Size = int(self.font_size_var)
A = alpha
R = int(color[0])
B = int(color[1])
G = int(color[2])
img = Image.open(file).convert("RGBA")
img_down = img.resize((int(img.width/2), int(img.height/2)), resample=Image.NEAREST)
img_down.x, img_down.y = img_down.size
txt = Image.new('RGBA', img_down.size, (225,225,225,0))
if custom_font == "yes":
font = ImageFont.truetype(font_file, Font_Size)
draw = ImageDraw.Draw(txt)
text = watermark_text
if custom_font == "no":
font = ImageFont.truetype('arial.ttf', Font_Size)
draw = ImageDraw.Draw(txt)
text = watermark_text
self.final_x = self.canvas.canvasx(self.text_x)
self.final_y = self.canvas.canvasx(self.text_x)
draw.text((self.final_x, self.final_y), text, font=font, fill=(R, G, B, A))
comp = Image.alpha_composite(img_down, txt)
img_up = comp.resize((int(comp.width*2), int(comp.height*2)), resample=Image.NEAREST)
img_up.save(save_file)
img_up.show()
p.s. if anyone knows why when the pillow text alpha is set to 0, the text is never added, please let me know!
I'll just post Bryan Oakley's comment as an answer. Pillow has an anchor option and all I needed to do was set it to mm

Change the position of a frame in Tkinter, Involving Classes and functions

I'm trying to move a frame on my GUI, but I am unable to do so because of my classes. How can I move the frame2 with a
Slider1.SetVal(x=20, y=30)
while keeping my classes.
My Code
class Slider:
def __init__ (self,Name, x, y, Height, Width, KnobHeight, KnobWidth, LowVal, HighVal, BgColor, FgColor):
self.Name = Name
self.x = x
self.y = y
self.Height = Height
self.Width = Width
self.KnobHeight = KnobHeight
self.KnobWidth = KnobWidth
self.LowVal = LowVal
self.HighVal = HighVal
self.BgColor = BgColor
self.FgColor = FgColor
def Display(self):
frame = Frame(self.Name, width=self.Width, height=self.Height, colormap="new", bg=self.BgColor)
frame.place(x=self.x, y=self.y)
frame2 = Frame(self.Name, width=self.KnobWidth, height=self.KnobHeight, colormap="new", bg=self.FgColor)
frame2.place(x=(self.x + self.Width/2 - self.KnobWidth/2), y=(self.Height - self.KnobHeight/2 + self.y))
def SetVal(self):
Main.update()
frame2.place(x=33, y=5)
window1 = Window(Main)
Slider1 = Slider(Main,100,60,200,15,15,35,0,80,"White","Green")
Slider1.Display()
Slider1.SetVal(3,30)
Main.mainloop()

simple Tk application - draw on the click of a button

Can anyone tell me why my 'addLine' method is failing to call when the self.rec button is clicked?
from tkinter import *
from tkinter import ttk
root = Tk()
class Paint:
def __init__(self, parent):
self.parent = parent
self.whiteBoard = Canvas(self.parent)
self.whiteBoard.grid(column=0, row=0, sticky=(N,W,E,S))
self.lastx = 0
self.lasty = 0
self.rec = self.whiteBoard.create_rectangle((10, 10, 30, 30), fill="red")
self.whiteBoard.tag_bind(self.rec, "<Button-1>", self.getClick)
def xy(self, event):
self.lastx, self.lasty = event.x, event.y
print (event.x, " is the x coordinate")
print (event.y, " is the y coordinate")
def addLine(self, event):
canvas.create_line((lastx, lasty, event.x, event.y))
self.lastx, self.lasty = event.x, event.y
def getClick(self, event):
self.whiteBoard.bind("<Button-1>", self.xy)
self.whiteBoard.bind("B1-Motion>", self.addLine)
white = Paint(root)
root.mainloop()
This is all part of an attempt to make a MS paint clone using Tkinter.
First, you are binding to B1-Motion> (note the missing <). But, more importantly, don't do bindings like those. It looks like the getClick method is actually "select the line tool". Then, add the bindings <Button-1> and <B1-Motion> to the canvas itself. When the callbacks are called, you do the actions according to the selected tool.
Here is rough sketch that follows this suggestion (with a RECTANGLE tool as a bonus):
import tkinter
# TOOLS
LINE, RECTANGLE = list(range(2))
class Paint:
def __init__(self, canvas):
self.canvas = canvas
self._tool, self._obj = None, None
self.lastx, self.lasty = None, None
self.canvas.bind('<Button-1>', self.update_xy)
self.canvas.bind('<B1-Motion>', self.draw)
def draw(self, event):
if self._tool is None or self._obj is None:
return
x, y = self.lastx, self.lasty
if self._tool in (LINE, RECTANGLE):
self.canvas.coords(self._obj, (x, y, event.x, event.y))
def update_xy(self, event):
if self._tool is None:
return
x, y = event.x, event.y
if self._tool == LINE:
self._obj = self.canvas.create_line((x, y, x, y))
elif self._tool == RECTANGLE:
self._obj = self.canvas.create_rectangle((x, y, x, y))
self.lastx, self.lasty = x, y
def select_tool(self, tool):
print('Tool', tool)
self._tool = tool
class Tool:
def __init__(self, whiteboard, parent=None):
self.whiteboard = whiteboard
frame = tkinter.Frame(parent)
self._curr_tool = None
for i, (text, t) in enumerate((('L', LINE), ('R', RECTANGLE))):
lbl = tkinter.Label(frame, text=text, width=2, relief='raised')
lbl._tool = t
lbl.bind('<Button-1>', self.update_tool)
lbl.pack(padx=6, pady=6*(i % 2))
frame.pack(side='left', fill='y', expand=True, pady=6)
def update_tool(self, event):
lbl = event.widget
if self._curr_tool:
self._curr_tool['relief'] = 'raised'
lbl['relief'] = 'sunken'
self._curr_tool = lbl
self.whiteboard.select_tool(lbl._tool)
root = tkinter.Tk()
canvas = tkinter.Canvas(highlightbackground='black')
whiteboard = Paint(canvas)
tool = Tool(whiteboard)
canvas.pack(fill='both', expand=True, padx=6, pady=6)
root.mainloop()

Categories

Resources