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()
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()