Saving a Tkinter Canvas Drawing (Python)

vegaseat 4 Tallied Votes 20K Views Share

The Tkinter GUI toolkit's canvas object is very nice for all sorts of drawings and plotting. However, when it comes to saving the image Tkinter limits you to postscript printer files. One solution is to draw the same image in parallel on PIL's image-draw object in memory. This is made relatively simple, as the Tkinter and PIL methods function pretty much the same.

Here is a Python code snippet showing you the basics in the form of a plot of two trig functions.

# plot functions like y = sin(x) and y = cos(x) 
# with Tkinter canvas and line (visible), but can be saved only in .ps format 
# simultaneously use
# PIL image, draw, line (draws in memory, but can be saved in many formats)
# Python Image Library (PIL) free from:
# http://www.pythonware.com/products/pil/index.htm
# tested with Python24      vegaseat       15may2007

# use namespace to avoid conflict with PIL's Image
import Tkinter as tk
import math
import os
# needs Python Image Library (PIL)
import Image, ImageDraw

# some color constants for PIL
white = (255, 255, 255)
black = (0, 0, 0)
blue = (0, 0, 255)
red = (255, 0, 0)
green = (0,128,0)

root = tk.Tk()
root.title("Simple plot using canvas and line")

width = 400
height = 300
center = height//2
x_increment = 1
# width stretch
x_factor = 0.04
# height stretch
y_amplitude = 80

# Tkinter create a canvas to draw on
cv = tk.Canvas(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)

# create the coordinate list for the sin() curve
# have to be integers for Tkinter
sine_list = []
for x in range(400):
    # x coordinates
    sine_list.append(x * x_increment)
    # y coordinates
    sine_list.append(int(math.sin(x * x_factor) * y_amplitude) + center)

# create the coordinate list for the cos() curve
cos_list = []
for x in range(400):
    # x coordinates
    cos_list.append(x * x_increment)
    # y coordinates
    cos_list.append(int(math.cos(x * x_factor) * y_amplitude) + center)

# do the Tkinter canvas drawings (visible)
str1 = "sin(x)=blue  cos(x)=red"
cv.create_text(10, 20, anchor='sw', text=str1)
center_line = cv.create_line([0, center, width, center], fill='green')
sin_line = cv.create_line(sine_list, fill='blue')
cos_line = cv.create_line(cos_list, fill='red')

# Tkinter canvas object can only be saved as a postscipt file
# which is actually a postscript printer language text file
cv.postscript(file="my_drawing.ps", colormode='color')

# do the PIL image/draw (in memory) drawings
draw.text((10, 20), str1, black)
draw.line([0, center, width, center], green)
draw.line(sine_list, blue)
draw.line(cos_list, red)

# PIL image can be saved as .png .jpg .gif or .bmp file
filename = "my_drawing.jpg"
image1.save(filename)

# to test, view the saved file, works with Windows only
# behaves like double-clicking on the saved file
os.startfile(filename)

root.mainloop()

Dani AI

Generated

Need a bitmap (PNG/JPEG) from a Tkinter Canvas without redrawing everything in PIL? Export the canvas as PostScript and rasterize it with Pillow. This preserves strokes, text, and layering, and is far faster and more accurate than per-pixel probing of canvas items.

Example workflow:

  • Install Pillow and Ghostscript (Pillow uses Ghostscript to render EPS/PS).
  • Clip to the canvas area with canvas.postscript, then open the PS data with Pillow and save as PNG.
from PIL import Image, EpsImagePlugin
import io

# Optional on Windows if Ghostscript is not on PATH:
# EpsImagePlugin.gs_windows_binary = r"gswin64c"

w.update()  # ensure geometry is current
ps = w.postscript(colormode="color", x=0, y=0,
                  width=w.winfo_width(), height=w.winfo_height())
img = Image.open(io.BytesIO(ps.encode("utf-8")))
img.load(scale=2)  # higher scale => higher DPI
img.save("canvas.png", "PNG")

Notes and tips:

  • If you only need vector output, save the PostScript directly: w.postscript(file="canvas.ps"). See Tk canvas command.
  • For pure-Tk image export, PhotoImage.write can save PPM and, on some Tk builds, PNG; support varies by platform/build. See Tk photo image.
  • If Pillow cannot render EPS/PS, verify Ghostscript is installed and on PATH. Details: Pillow EPS support.
  • To persist editable drawings for later re-load, serialize canvas items (type, coords, options) to JSON and reconstruct, rather than saving only pixels.
MSV22 0 Newbie Poster

Hi

I am drawing different figures like circle,rectangle,line etc in my application. I am using wxpython. I have created a menu. I want to save the file and store its content . I have created the file dialog. But I am not getting how to store the contents.How do I go about it??
Do I have to use Cpickle?? I tried understanding it but not much luck in that.

Thanks

vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

To do a similar thing with wxPython see:
http://www.daniweb.com/forums/post1070295.html#post1070295

MSV22 0 Newbie Poster

Thank you for the quick reply :)

MatthiasBl 0 Newbie Poster
def savebitmap(event):
   form = "Saving {0}%"
   img = PhotoImage(width = canvas_width, height = canvas_height)
   perc = -1
   for y in range(canvas_height):
      perc1 = (100*y) // canvas_height
      if perc1 != perc:
         perc = perc1
         setmessage(form.format(perc), True)
      row = "{"
      for x in range(canvas_width):
         ids = w.find_overlapping(x, y, x, y)
         color = w.itemcget(ids[-1], "fill") if ids else canvas_background
         row += color + " "
      img.put(row + "}", (0, y))
   img.write("kritzel.ppm", format="ppm")
   setmessage()

This is my way to save the canvas elements as bitmap.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.