pythonpython-imaging-librarytkinter-canvas

Saving tkinter canvas with black background and white line objects using PIL results in fully white image


Probem

When trying to save a tkinter canvas using PIL, it works as expected when the canvas background is set to "white" and fill option for the created lines to "black" but when setting the canvas background to "black" and the fill parameter of created lines to "white", the saved image will just be a solid color (white). Why does this happen?

Versioning

Python 3.11.8
pillow==10.2.0

Code

saves canvas normally

import tkinter as tk
import io
from PIL import Image


class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.line_start = None
        self.canvas = tk.Canvas(self, width=300, height=300, bg="white")
        self.canvas.bind("<Button-1>", lambda e: self.draw(e.x, e.y))
        self.button = tk.Button(self, text="save", command=self.save)
        self.canvas.pack(fill="both", expand=True)
        self.canvas.focus_set()
        self.button.pack(pady=10)

    def draw(self, x, y):
        if self.line_start:
            x_origin, y_origin = self.line_start
            self.canvas.create_line(x_origin, y_origin, x, y, fill="black")
        self.line_start = x, y

    def save(self):
        ps = self.canvas.postscript(colormode="color")
        img = Image.open(io.BytesIO(ps.encode("utf-8")))
        img.save("working.png")


app = App()
app.mainloop()

saves fully white image

import tkinter as tk
import io
from PIL import Image


class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.line_start = None
        self.canvas = tk.Canvas(self, width=300, height=300, bg="black")
        self.canvas.bind("<Button-1>", lambda e: self.draw(e.x, e.y))
        self.button = tk.Button(self, text="save", command=self.save)
        self.canvas.pack(fill="both", expand=True)
        self.canvas.focus_set()
        self.button.pack(pady=10)

    def draw(self, x, y):
        if self.line_start:
            x_origin, y_origin = self.line_start
            self.canvas.create_line(x_origin, y_origin, x, y, fill="white")
        self.line_start = x, y

    def save(self):
        ps = self.canvas.postscript(colormode="color")
        img = Image.open(io.BytesIO(ps.encode("utf-8")))
        img.save("white.png")


app = App()
app.mainloop()

Solution

  • The contents of this article may apply to you.
    Canvas background color not saved

    I don't believe the postscript command is designed to preserve the background color of the canvas widget. It only saves the items that appear on the canvas.

    A simple solution is to draw a rectangle that is the exact size of the canvas, and change the color of that rectangle.

    So, you just need to add the following line to the source of the saves fully white image in the question.

            self.canvas = tk.Canvas(self, width=300, height=300, bg="black")
            self.canvas.create_rectangle(0, 0, 300, 300, fill='black') #### added this line
            self.canvas.bind("<Button-1>", lambda e: self.draw(e.x, e.y))