How can I convert canvas content to an image?
Asked Answered
C

8

32
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?

Caloric answered 27/3, 2012 at 8:59 Comment(1)
In actionscript it has draw() function to realize,how to work in python tkinter?Caloric
A
38

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()
Arctogaea answered 3/5, 2012 at 13:55 Comment(2)
when I try to convert to postscript, it resizes my image (the saved postscript is smaller respect to canvas); why??Geiger
Since postscript is a vector format, I don't think this should be a problemFiber
S
34

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!

Sycamine answered 28/7, 2016 at 20:13 Comment(10)
Hi, when I run the code it makes the picture too soon. Can you please post an example script with tkinter graphics and then saving it? Maybe I'm placing or calling out the function at the wrong time.Tribulation
@EerikMuuli You must only call the function when you want the picture taken. For example, you could get a button with a command, and in the function for the button's command, simply put getter(x) where x is the widget, or even the whole root window. It should be no trouble. If you have further problems just reply in a comment here.Sycamine
Okay, I found out where the problem is - I will paste my code here. What the script now does is that it prints the canvas twice and runs into an error - although it should open it once and close it after 4.5 sec. It also prints out the "wut" 2 times which is really weird. Here's the code: pastebin.com/MPgABMEvTribulation
@EerikMuuli Firstly, you are using pyscreenshot module instead of PIL, so I'm not entirely sure how this works. As PIL can do screen-shots and lots of other functions, I would simply get the PIL module instead. Also, it looks like you just want to make the canvas, and them make a picture of a rectangle. There are simpler ways to do this! To do it your way, then use PIL, and the save function that I used in my answer, and if you really want to exit after you have taken the screenshot, then just put an exit() or preferably root.destroy() command in the getter function definition.Sycamine
Marvelous approach! Much better than trying to repeat all the drawing commands on a blank PIL image or creating Postscipt output and using another tool to convert it into an image. Note that Instead of capturing the whole screen and then cropping it, this can be done in a single step with ImageGrab.grab((x,y,x1,y1)).save("file path here") (see ImageGrab.grab() docs). Should also point-out that the file name extension given determines the format of the image file created.Neighborhood
Very useful quick and dirty trick, might not suit some cases, but so damn faster to pull off than building a scene object outputing every coordinates and info needed by either PIL or Tkinter.CanvasStaff
ImportError: ImageGrab is macOS and Windows onlyJarrettjarrid
@MartinThoma Perhaps this article will help: #70145 Hopefully you can choose a tool, and crop the image as required.Sycamine
you could update this to include these imports: try: from PIL ImageGrab # For Windows & OSx except: import pyscreenshot as ImageGrab # For LinuxBoroughenglish
As of Pillow 7.1.0 ImageGrab supports Linux pillow.readthedocs.io/en/stable/releasenotes/…Bullion
M
9

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') 
Melodrama answered 27/3, 2019 at 20:17 Comment(5)
Nice. How do I do this without the canvas object? Let's say I have a .ps file and what to convert it to .png?Salivate
Just use the bottom two lines then.Blois
When I try this I get an error from PIL (Pillow) that OSError: Unable to locate Ghostscript on paths.Neighborhood
When I try this I get an error from PIL (Pillow) that OSError: Unable to locate Ghostscript on paths - me toooooNahshu
I have the same problem OSError: Unable to locate Ghostscript on paths on saving the image,Crescen
R
5

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
Remediosremedy answered 20/4, 2019 at 14:45 Comment(1)
You don't need to use win32gui to get the coordinates of the canvas. There are built in tkinter methods for that.Ferdinand
E
2

Rather than rely on:

  • hokey, platform-specific screen-capture, or
  • writing images needlessly to disk and then reading them back, or
  • wasting time and memory writing to two canvases

I think it is better to render the canvas to EPS and wrap the PostScript in a BytesIO and allow PIL to read that without using disk:

#!/usr/bin/env python3

from tkinter import *
from PIL import Image, ImageTk
from io import BytesIO
root = Tk()
cv = Canvas(root)
cv.create_rectangle(10,10,50,50, fill='green')
cv.pack()

cv.update()
# Get the EPS corresponding to the canvas
eps = cv.postscript(colormode='color')

# Save canvas as "in-memory" EPS and pass to PIL without writing to disk
im = Image.open(BytesIO(bytes(eps,'ascii')))
im.save('result.png')

root.mainloop()

enter image description here

Extractor answered 4/8, 2023 at 17:47 Comment(3)
This doesn't work for me. It shows the following error: OSError: Unable to locate Ghostscript on paths Is there a missing dependency?Endostosis
@Endostosis Not really. If you want to use Postscript format, you need to install Ghostscript and have your PATH set so it can be found... but you didn't say what OS you are using, nor if/how/where you installed Ghostscript nor which line caused the error.Extractor
Windows 11 doesn't come with Ghostscript installed.Hoffman
O
1

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)
Organon answered 24/3, 2022 at 10:41 Comment(1)
This kinda works, but when resizing the window, the resulting image it resized too instead of the dimensions I set in the canvasEndostosis
M
1

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)
Mccain answered 11/12, 2022 at 23:21 Comment(0)
S
0

Theres an way easier way to do that, without any imagegrab or anything other.

You can grab every pixel from the canvas and then draw it to image. Just like this program.

It may be kinda slow if you have a lot of things of canvas, But atleast, it doesnt use ghostscript tho, And you can just keep the findobjat, save functions.

from tkinter import *
from PIL import Image
import webcolors

drawing = False

def findobjat(x,y,C):
    obj = C.find_overlapping(x,y,x,y)
    
    if len(obj) == 0:
        return (255,255,255)
    
    color = C.itemcget(obj[-1],"fill")

    if type(color) == str:
        color = webcolors.name_to_rgb(color)

    return color

def save(C,w,h):
    im = Image.new("RGB",(w,h))
    for x in range(w):
        for y in range(h):
            color = findobjat(x,y,C)
            im.putpixel((x,y),color)
    return im

def postsave():
    save(C,200,200).save("image.png")
    
def drawstart(event):
    global drawing
    drawing = True

def drawstop(event):
    global drawing
    drawing = False

def draw(event):
    if drawing:
        C.create_oval(event.x-5,event.y-5,event.x+5,event.y+5,fill = "black")

root = Tk()

root.geometry("200x200")

C = Canvas(root,bg = "white",width = 200,height = 200)
C.bind("<Button-1>",drawstart)
C.bind("<ButtonRelease-1>",drawstop)
C.bind("<B1-Motion>",draw)

button = Button(root,text = "Save image",command = postsave)
button.pack()

C.pack()
root.mainloop()
Serica answered 11/6, 2024 at 11:52 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.