How can I convert canvas content to an image? - python

from Tkinter import *
root = Tk()
cv = Canvas(root)
cv.create_rectangle(10,10,50,50)
cv.pack()
root.mainloop()
I want to convert canvas content to a bitmap or other image, and then do other operations, such as rotating or scaling the image, or changing its coordinates.
Bitmaps can improve efficiency to show if I am no longer drawing.
What should I do?

You can either generate a postscript document (to feed into some other tool: ImageMagick, Ghostscript, etc):
from Tkinter import *
root = Tk()
cv = Canvas(root)
cv.create_rectangle(10,10,50,50)
cv.pack()
root.mainloop()
cv.update()
cv.postscript(file="file_name.ps", colormode='color')
root.mainloop()
or draw the same image in parallel on PIL and on Tkinter's canvas (see: Saving a Tkinter Canvas Drawing (Python)). For example (inspired by the same article):
from Tkinter import *
import Image, ImageDraw
width = 400
height = 300
center = height//2
white = (255, 255, 255)
green = (0,128,0)
root = Tk()
# Tkinter create a canvas to draw on
cv = Canvas(root, width=width, height=height, bg='white')
cv.pack()
# PIL create an empty image and draw object to draw on
# memory only, not visible
image1 = Image.new("RGB", (width, height), white)
draw = ImageDraw.Draw(image1)
# do the Tkinter canvas drawings (visible)
cv.create_line([0, center, width, center], fill='green')
# do the PIL image/draw (in memory) drawings
draw.line([0, center, width, center], green)
# PIL image can be saved as .png .jpg .gif or .bmp file (among others)
filename = "my_drawing.jpg"
image1.save(filename)
root.mainloop()

I have found a great way of doing this which is really helpful. For it, you need the PIL module. Here is the code:
from PIL import ImageGrab
def getter(widget):
x=root.winfo_rootx()+widget.winfo_x()
y=root.winfo_rooty()+widget.winfo_y()
x1=x+widget.winfo_width()
y1=y+widget.winfo_height()
ImageGrab.grab().crop((x,y,x1,y1)).save("file path here")
What this does is you pass a widget name into the function. The command root.winfo_rootx() and the root.winfo_rooty() get the pixel position of the top left of the overall root window.
Then, the widget.winfo_x() and widget.winfo_y() are added to, basically just get the pixel coordinate of the top left hand pixel of the widget which you want to capture (at pixels (x,y) of your screen).
I then find the (x1,y1) which is the bottom left pixel of the widget. The ImageGrab.grab() makes a printscreen, and I then crop it to only get the bit containing the widget. Although not perfect, and won't make the best possible image, this is a great tool for just getting a image of any widget and saving it.
If you have any questions, post a comment! Hope this helped!

Use Pillow to convert from Postscript to PNG
from PIL import Image
def save_as_png(canvas,fileName):
# save postscipt image
canvas.postscript(file = fileName + '.eps')
# use PIL to convert to PNG
img = Image.open(fileName + '.eps')
img.save(fileName + '.png', 'png')

Maybe you can try to use widget_winfo_id to get the HWND of the canvas.
import win32gui
from PIL import ImageGrab
HWND = canvas.winfo_id() # get the handle of the canvas
rect = win32gui.GetWindowRect(HWND) # get the coordinate of the canvas
im = ImageGrab.grab(rect) # get image of the current location

A better way for #B.Jenkins's answer that doesn't need a reference to the root object:
from PIL import ImageGrab
def save_widget_as_image(widget, file_name):
ImageGrab.grab(bbox=(
widget.winfo_rootx(),
widget.winfo_rooty(),
widget.winfo_rootx() + widget.winfo_width(),
widget.winfo_rooty() + widget.winfo_height()
)).save(file_name)

On my system had serious issues with ghostscript and the ImageGrab in general. Solution draw on PIL Image, save as a file, load file on PhotoImage, which is used to create new TKinter Canvas.
canvas = Canvas(win, width=IMG_W, height=IMG_H)
img = PILImg.new("RGB", (IMG_W, IMG_H), "#000")
draw = ImageDraw.Draw(img)
draw.rectangle([x,y,w,h], fill=color, outline=border)
img.save("stock-chart.png")
copyImg = PhotoImage(file="stock-chart.png")
canvas.create_image(IMG_W_2, IMG_H_2, image=copyImg)

Related

Python PIL - How to put text on a video

I am writing a program that puts captions on the screen and I am using PIL because it has high-quality text that is way better than the default moviepy text. I currently can put the text on a black screen, I tried using a fully transparent image as a sort of hack but that didn't work. So if anyone knows how to put PIL text on an image, or up the quality of the default moviepy text, let me know, please. Here is my basic code to put white text on a black background:
import numpy as np
from moviepy.editor import *
from PIL import Image, ImageFont, ImageDraw
SIZE = (1280, 720)
DURATION = 2
FPS = 1
TEXT = "I am having a hard time"
FONT_SIZE = 100
TEXT_COLOR_HEX = "#ffffff"
FONT = "Amiri-Bold"
def create_PIL():
img = Image.new("RGB", size=SIZE)
#draw = ImageDraw.Draw(img)
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(f"../Fonts/UniNeue-Trial-Bold.ttf", size=FONT_SIZE)
draw.text((100, 100), TEXT, font=font, fill=TEXT_COLOR_HEX)
img = np.array(img)
return VideoClip(lambda t: img, duration=DURATION).set_fps(FPS)
final = concatenate_videoclips(
[create_PIL()]
)
final.write_videofile('./test.mp4')
I am trying to put this text onto a video, and I need to have custom start and duration times but all this does it put it on a black screen, which is what I expected but not what I want.

Image not getting drawn on tkinter canvas

I would like to display a knapsack image on green canvas. The height and width of that canvas is 250X250 pixels.
And the size of the image is 260X280 pixels.
When I try to execute below code, I get the output as shown in screenshot above 1. The location of the code file and image file is same.
from tkinter.font import BOLD
from tkinter import *
import tkinter
from PIL import Image, ImageTk
root = Tk()
def draw():
global canvas
root.geometry('1080x720')
root.state('zoomed')
canvas = Canvas(root,bg='black',highlightthickness=0)
canvas.pack(fill=tkinter.BOTH, expand=True)
sw = root.winfo_screenwidth()
sh = root.winfo_screenheight()
canvas.create_line(int(sw*0.0000),int(sh*0.1736),int(sw*0.6510),int(sh*0.1736),fill='white')
canvas.create_line(int(sw*0.6510),int(sh*0.0000),int(sw*0.6510),int(sh*1.0000),fill='white')
canvas.create_line(int(sw*0.6510),int(sh*0.1157),int(sw*1.0000),int(sh*0.1157),fill='white')
canvas.create_line(int(sw*0.6510),int(sh*0.8101),int(sw*1.0000),int(sh*0.8101),fill='white')
UI_frame1 = Frame(canvas,bg='black',width=int(sw*0.6510),height=int(sh*0.1580))
canvas.create_window(0,0, anchor=NW,window=UI_frame1)
N = Label(UI_frame1,text='N',bg ='black',fg='white',font=(12))
N.grid(row=0,column=0, padx=139,pady=22)
weights = Label(UI_frame1,text='Weights',bg ='black',fg='white',font=(12))
weights.grid(row=0,column=1,padx=140,pady=22)
val = Label(UI_frame1,text='Values',bg ='black',fg='white',font=(12))
val.grid(row=0,column=2,padx=140,pady=22)
n = Entry(UI_frame1,bg='white',width=4,font=(12))
n.grid(row=1,column=0,padx=50,pady=17)
value_arr = Entry(UI_frame1,bg='white',font=(12))
value_arr.grid(row=1,column=1,padx=50,pady=17)
weight_arr = Entry(UI_frame1,bg='white',font=(12))
weight_arr.grid(row=1,column=2,padx=50,pady=17)
Label(canvas,text='i',bg='black',fg='white',font=(14)).place(x=150,y=185)
i = Label(canvas,text=" i ",bg='white',font=(12)).place(x=175,y=185)
Label(canvas,text='j',bg='black',fg='white',font=(14)).place(x=525,y=185)
j = Label(canvas,text=" j ",bg='white',font=(12)).place(x=550,y=185)
table = Canvas(canvas,bg='red',width=600,height=450)
table.place(x=75,y=300)
w = int(600/8)
h = int(450/5)
x=0
y=0
for r in range(5):
for c in range(8):
table.create_rectangle(x,y,x+w,y+h)
x+=w
y+=h
x=0
UI_frame2 = Frame(canvas,bg='blue',width=250,height=250)
canvas.create_window(835,630, anchor=CENTER,window=UI_frame2)
image_c = Canvas(UI_frame2,bg='green',highlightthickness=0,width=250,height=250)
image_c.grid(row=0,column=0,padx=0,pady=0)
photo = ImageTk.PhotoImage(file ='knapsack.png')
image_c.create_image(835,630,image=photo,anchor=NW)
draw()
root.mainloop()
I would like to cover the entire green canvas with single image of knapsack. I am not any error while running the GUI. Anyone could help me out, I'll be really thankful.
There are two problems:
First:
You display in position (835,630) but canvas has visible only (250, 250) - (top left corner is (0,0)) - so photo is in place which you can see.
Second:
There is bug in PhotoImage() which removes image from memory when it is assigned to local variable. One of solution is to use global photo to assign it to global variable.
def draw():
global canvas
global photo # use global variable
#... code ...
photo = ImageTk.PhotoImage(file='knapsack.png')
image_c.create_image(0, 0,image=photo, anchor=NW) # position (0,0)
Doc (on archive.org): PhotoImage - see Note at the botton of doc.
Other problem can be that image has big (transparent) margins around object and it may show object in differnt place then you may expect.
Screenshot shows photo in position (0,0) but object is in center of green canvas.

creating a filled in rectangle/shape around text using PIL

I have a pre-existing image that I have added a border to and text, I would like to place a rectangle around the text area to make it similar to a gold embedded plate on a picture or painting.
I am using draw.text to write and position the text, but can't figure out how to do the shape border & fill around the text parameters.
import os
from PIL import Image, ImageOps, ImageDraw, ImageFont
import glob
## SINGLE IMAGE
img = Image.open('Art/AB-2020 (2).jpg')
add_grey_border = ImageOps.expand(img,border=50,fill='grey')
add_black_border = ImageOps.expand(add_grey_border,border=300,fill='black')
add_black_border.save('Art/imaged-with-border.png')
## Function to add Title to image
def add_title(img_src, title):
img = Image.open(img_src, 'r')
draw = ImageDraw.Draw(img)
w, h = img.size
font = ImageFont.truetype("arial.ttf", 100)
text_w, text_h = draw.textsize(title, font)
draw.text(((w - text_w) / 2, (h-100) - text_h), title, (255,255,255),
font=font, stroke_width=2, stroke_fill='#eeda19')
img.save(img_src)
return img_src
add_title('Art/imaged-with-border.png', 'test')
what I have so far:
painting with text
If I can get the nameplate correct I will remove the stroke_fill.
I want it to look like this:
What I am trying to achieve

Draggable Tkinter Label With Transparent Image Still Covers Image from Parent Canvas

I am working on adding drag-able labels on top of a background image, where the labels are images with a transparent background. The images used for the label themselves are transparent, but the Label itself is not transparent in relation to its parent canvas. Since the labels are drag-able, I can't easily use the parent image for label, and paste the transparent image on top.
The dragon is a drag-able label, with a transparent background, but you see that the label itself is not transparent, and covers the canvas image.
The dragon image itself has a transparent background, as the normal background is blue, so I know the issue is with the label transparency.
The desired behavior would be to allow the label to be transparent, so when the image on the label is transparent, the label should show through to the image below.
Here is a minimal example:
from PIL import Image, ImageTk
import numpy as np
import tkinter as tk
#Creates a blue square with a transparent border
blue_square_transparent_border = [[[0,0,0,0]]*100]*10 + [[[0,0,0,0]]*30 + [[0,0,255,255]]*40 + [[0,0,0,0]]*30]*40 + [[[0,0,0,0]]*100]*10
blue_square_transparent_border = np.array(blue_square_transparent_border, dtype='uint8')
#convert numpy array to PIL image
pil_image = Image.fromarray(blue_square_transparent_border)
root = tk.Tk()
root.configure(background='red')
#convert PIL image to tkinter image
tk_image = ImageTk.PhotoImage(pil_image)
#create label
image_label = tk.Label(root, image=tk_image)
image_label.pack()
root.mainloop()
What I would like to see is a blue square, on a red background, with no border. In the above example though, the border of grey appears, as it is the label being seen through the transparent image; it is easy to see this when you resize the window. I suspect if the label were transparent, this would solve my issues.
Any help would be great, Thanks!
If you want to drag partially transparent images (like the dragon) over a background image, you can do it with a Canvas:
The idea is not to use labels, but the create_image method of the Canvas.
First, display your background image with canvas.create_image(0, 0, image=background_image, anchor='nw'), then display all the draggable images with the tag 'draggable': canvas.create_image(x, y, image=draggable_image, anchor='nw', tag='draggable'). Finally, bind the tag 'draggable' to mouse events.
Here is an example:
import tkinter as tk
import numpy as np
from PIL import Image, ImageTk
# drag callbacks
dragged_item = None
current_coords = 0, 0
def start_drag(event):
global current_coords
global dragged_item
result = canvas.find_withtag('current')
if result:
dragged_item = result[0]
current_coords = canvas.canvasx(event.x), canvas.canvasy(event.y)
else:
dragged_item = None
def stop_drag(event):
dragged_item = None
def drag(event):
global current_coords
xc, yc = canvas.canvasx(event.x), canvas.canvasy(event.y)
dx, dy = xc - current_coords[0], yc - current_coords[1]
current_coords = xc, yc
canvas.move(dragged_item, dx, dy)
#Create pictures
blue_square_transparent_border = [[[0,0,0,0]]*100]*10 + [[[0,0,0,0]]*30 + [[0,0,255,255]]*40 + [[0,0,0,0]]*30]*40 + [[[0,0,0,0]]*100]*10
blue_square_transparent_border = np.array(blue_square_transparent_border, dtype='uint8')
pil_image = Image.fromarray(blue_square_transparent_border)
background_data = np.zeros((200, 400, 4))
background_data[:, :, 0] = 255 * np.ones((200, 400))
background_data[:, :, 3] = 255 * np.ones((200, 400))
background_data = np.array(background_data, dtype='uint8')
pil_image_bg = Image.fromarray(background_data)
# create GUI
root = tk.Tk()
background_image = ImageTk.PhotoImage(pil_image_bg)
tk_image = ImageTk.PhotoImage(pil_image)
canvas = tk.Canvas(root, width=400, height=200)
canvas.pack()
# bind 'draggable' tag to mouse events
canvas.tag_bind('draggable', '<ButtonPress-1>', start_drag)
canvas.tag_bind('draggable', '<ButtonRelease-1>', stop_drag)
canvas.tag_bind('draggable', '<B1-Motion>', drag)
# display pictures
canvas.create_image(0, 0, image=background_image, anchor='nw')
canvas.create_image(0, 0, image=tk_image, anchor='nw', tag='draggable')
root.mainloop()

how to make a reduced image file in python

I'm in a beginning programming class, and our project is to reduce an image to half it's size and then to double it's size.
How do I make a new picture that is the same picture as before, but with the reduced height and width?
This is the code that I have:
def main():
originalPic=makePicture(pickAFile())
show(originalPic)
w=getWidth(originalPic)
h=getHeight(originalPic)
printNow(str(w)+ " \n" + str(h))
if w % 2:
reducedW=w/2
else:
reducedW=w/2+1
printNow(reducedW)
if h % 2:
reducedH=h/2
else:
reducedH=h/2+1
printNow(reducedH)
reducedPic=makePicture(reducedW, reducedH)
show(reducedPic)
Here's how I do that in PIL (Pillow):
from PIL import Image, ImageTk
my_image = Image.open('image.png') # open image
my_image = my_image.resize((my_image.size[0] * 2, my_image.size[1] * 2)) # resize
my_image.save('new_image.png') # save image
tk_image = ImageTk.PhotoImage(my_image) # convert for use in tkinter
As a beginner, you might want to stick to using Pygame rather than PIL to actually implement your skeleton code. We'll just load our image into Pygame, scale it up or down, and then save it.
import pygame, sys
pygame.init(); scale = 2 # or a half
image = pygame.image.load('image.png')
w = image.get_width() * scale
h = image.get_height() * scale
pygame.transform.scale(image, (w, h), image)
pygame.image.save(image, 'scaled_image.png')

Categories