Home > Enterprise >  Saving content of tkinter canvas as image
Saving content of tkinter canvas as image

Time:10-15

Such a question has been posted many times before but I couldn't find a solution which would work with my code. I have a tkinter window with some plots (later the number will be dynamically) and a row and a column title. I need to save an image (preferably .png) from the content of the window. There are many solutions by making an image of a tkinter window by saving the content of a canvas which I tried many times but none of them worked for me. Either I got an empty .eps image or the conversion to a .png image didn't work. Also in principal a screenshot of a window can be done. I tried many suggestions already but the closest thing I received was an image whith half the window and half of the screen behind it. But I prefer to not do a screenshot because later the user should choose the quality of the image.

The code has a function which should create an .eps image which should be converted to a .png image but it doesn't work (Unable to locate Ghostscript on paths). But apart from what I tried I would appreciate any working code example embedded within my code.

P.S. Also maybe someone can tell me why the column title is so far away from the plots because I have no clue why it is.

import tkinter as tk
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from PIL import Image

# saving canvas as .png image
def save():
    fileName="Image"
    canvas.postscript(file=fileName   '.eps')
    # use PIL to convert to PNG
    img = Image.open(fileName   '.eps')
    img.save(fileName   '.png', 'png')

    canvas.update()
    canvas.postscript(file="file_name.ps", colormode='color')

# data for plots
cols = 2
x1, x2, x3, x4 = [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]
y1, y2, y3, y4 = [1, 2, 3], [1, 2, 1], [3, 2, 1], [3, 2, 3]

#
root = tk.Tk()
canvas = tk.Canvas(root, background="grey")
row_title = tk.Label(canvas, text="rows", font=("Arial 24 bold"), background="grey")
column_title = tk.Label(canvas, text="columns", font=("Arial 24 bold"), background="grey")

# plots
fig = plt.Figure(figsize=(5, 5))
fig.set_facecolor("grey")

plot1 = fig.add_subplot(221)
plot1.scatter(x1, y1)
plot2 = fig.add_subplot(222)
plot2.scatter(x2, y2)
plot3 = fig.add_subplot(223)
plot3.scatter(x3, y3)
plot4 = fig.add_subplot(224)
plot4.scatter(x4, y4)

chart1 = FigureCanvasTkAgg(fig, canvas)
chart1.get_tk_widget().grid(row=1, column=1)

button = tk.Button(text="Save", command=save)

canvas.grid(row=0, column=0)
row_title.grid(row=1, column=0, rowspan=2, padx=5, pady=5)
column_title.grid(row=0, column=1, columnspan=2)
button.grid(row=2, column=0)

root.mainloop()

CodePudding user response:

This code works on Windows.

import os
import tkinter as tk
from tkinter import filedialog as fido
from PIL import ImageGrab

# from matplotlib import pyplot as plt
# from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# NOTE: This explanation concentrates on tkinter not matplotlib
root = tk.Tk()

# make root window and contents resizeable
root.rowconfigure(0, weight = 1)
root.columnconfigure(0, weight = 1)

canvas = tk.Canvas(root, background = "grey")
# 'sticky' in grid is necessary so that the canvas will expand to fill the available space.
# Without it the canvas will wrap itself around the contents. (creating the smallest footprint)
canvas.grid(sticky = tk.NSEW)

# These labels are contained within the canvas so don't grid them
row_title = tk.Label(
    canvas, text="rows", font=("Arial 24 bold"), background="grey")
column_title = tk.Label(
    canvas, text="columns", font=("Arial 24 bold"), background="grey")

# Instead, wrap each in a canvas window object.
A = canvas.create_window(2, 200, window = row_title, anchor = tk.NW)
# fiddle with coordinates until it is positioned correctly

#
# INSERT YOUR CHARTS HERE
#

# Now wrap 'column_title'
B = canvas.create_window(200, 2, window = column_title, anchor = tk.NW)
# Again, fiddle with coordinates until it is positioned correctly

# Move and resize window as appropriate.

# To save image press Escape key.

# Function 'save' will take the entire visible canvas and save it as the
# filetype determined by your filename.
# The filename extension (.png, .gif, etc) determines the image format.
# You may need to check PIL documentation for filetype specifics.

def save(*event):

    filename = fido.asksaveasfilename(title = "Create Image")
    if filename:
        if os.path.exists(filename):
            print("Name already exists!")
        else:
            path, file = os.path.split(filename)
            name, ext = os.path.splitext(file)
            if ext.lower() in ['.gif', '.png']:
                # Standard way of calculating widget dimensions
                x1 = root.winfo_rootx()   canvas.winfo_x()
                y1 = root.winfo_rooty()   canvas.winfo_y()
                x2 = x1   canvas.winfo_width()
                y2 = y1   canvas.winfo_height()
                # Extract image
                image = ImageGrab.grab().crop((x1, y1, x2, y2))
                # And save it
                image.save(filename)
                print(f"Saved image {file}")
            else:
                print("Unknown file type")
    else:
        print("Cancel")

# Instead of a button i've used a key binding
root.bind("<Escape>", save)

root.mainloop()
  • Related