1

I am trying to write a PDF viewer in Python/Tkinter using the PyMuPDF library. I can successfully open the document and render the first page, but when attempting to move to the next page by deleting the Canvas image and creating a new one from the new page I get a blank screen. The first page is deleted, but the second page doesn't show.

However when I run the program through VS Code and set a debug breakpoint in the nxtBtn_Click function and step through it line by line, the second page will appear in the window as expected when the function completes.

I have tried, but get the same results:

  • Using canvas.update_idletasks() to force a re-paint of the canvas.
  • Splitting the delete step and the create_image step to the ondown and onup events.
  • Using a callback function passed to window.after_idle
  • Updating the exiting image with a new image
    • canvas.itemconfig(canvasPdf, image = tkimg)

I am running Python 3.7.1 on Windows 10.

from tkinter import *
from PIL import Image, ImageTk
import fitz
import math

window = Tk()
window.geometry("800x800")

doc = fitz.open(r"<<Path to pdf here>>")
currentPage = 0

canvas = Canvas(window, width=800, height=600)
canvas.grid(column=0, row=0)

pix = doc[currentPage].getPixmap()
shrinkFactor = int(canvas.cget("height")) / pix.height
mode = "RGBA" if pix.alpha else "RGB"
img = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
img = img.resize((math.floor(pix.width * shrinkFactor), math.floor(pix.height * shrinkFactor)))

tkimg = ImageTk.PhotoImage(img)
canvasPdfs = canvas.create_image(0, 0, anchor=NW, image=tkimg)
def nxtBtn_Click(event):
    global doc, currentPage, canvas, canvasPdfs
    canvas.delete(canvasPdfs)

    currentPage += 1
    pix = doc[currentPage].getPixmap()
    shrinkFactor = int(canvas.cget("height")) / pix.height
    mode = "RGBA" if pix.alpha else "RGB"
    img = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
    img = img.resize((math.floor(pix.width * shrinkFactor), math.floor(pix.height * shrinkFactor)))

    tkimg = ImageTk.PhotoImage(img)
    canvasPdfs = canvas.create_image(0, 0, anchor=NW, image=tkimg)

nxtBtn = Button(window, text="Next")
nxtBtn.grid(column=0, row=1)
nxtBtn.bind("<Button-1>", func=nxtBtn_Click)

window.mainloop()
PercyODI
  • 31
  • 5
  • The first page's `tkimg` is a global variable, and therefore persists for as long as the image is visible (*too* long, actually, as it's still around after you advance pages). The `tkimg` of subsequent pages, however, is a local variable, so the image gets garbage-collected immediately as the click handler function exits. – jasonharper Aug 04 '19 at 15:39
  • @jasonharper: tkimg does stay around too long, but it doesn't explain why the second image doesn't show. I did try setting tkimg as a global variable in `nxtBtn_Click` so that I would overwrite the global variable with the local one (both allowing the old tkimg to be garbage collected and to keep the new image from being garbage collected). This had the same result of the second page not showing, unless I'm debugging. – PercyODI Aug 04 '19 at 15:47
  • I'm pretty sure this is a duplicate of https://stackoverflow.com/questions/16424091/why-does-tkinter-image-not-show-up-if-created-in-a-function – Bryan Oakley Aug 04 '19 at 16:07
  • @BryanOakley: I think you are right. I was able to fix the problem by not using a global `tkimg`, and instead making a property on the canvas object itself: `canvas.image = ImageTk.PhotoImage(img)`. I assume that debugging must mess with garbage collection, explaining why it worked in debug mode. – PercyODI Aug 04 '19 at 16:17

0 Answers0