How to slow down image display in Pillow

Viewed 214

Probably an unusual question, but I am currently looking for a solution to display image files with PIL slower. Ideally so that you can see how the image builds up, pixel by pixel from left to right.

Does anyone have an idea how to implement something like this? It is a purely optical thing, so it is not essential.

Here an example:

from PIL import Image

im = Image.open("sample-image.png")

im.show()

Is there a way to "slow down" im.show()?

1 Answers

AFAIK, you cannot do this directly with PIL's Image.show() because it actually saves your image as a file to /var/tmp/XXX and then passes that file to your OS's standard image viewer to display on the screen and there is no further interaction with the viewer process after that. So, if you draw in another pixel, the viewer will not be aware and if you call Image.show() again, it will save a new copy of your image and invoke another viewer which will give you a second window rather than updating the first!

There are several possibilities to get around it:

  • use OpenCV's cv2.imshow() which does allow updates
  • use tkinter to display the changing image
  • create an animated GIF and start a new process to display that

I chose the first, using OpenCV, as the path of least resistance:

#!/usr/bin/env python3

import cv2
import numpy as np
from PIL import Image

# Open image
im = Image.open('paddington.png')

# Make BGR Numpy version for OpenCV
BGR = np.array(im)[:,:,::-1]
h, w = BGR.shape[:2]

# Make empty image to fill in slowly and display
d = np.zeros_like(BGR)

# Use "x" to avoid drawing and waiting for every single pixel
x=0
for y in range(h):
   for x in range(w):
       d[y,x] = BGR[y,x]
       if x%400==0:
          cv2.imshow("SlowLoader",d)
          cv2.waitKey(1)
       x += 1

# Wait for one final keypress to exit
cv2.waitKey(0)

Increase the 400 near the end to make it faster and update the screen after a greater number of pixels, or decrease it to make it update the screen after a smaller number of pixels meaning you will see them appear more slowly.

As I cannot share a movie on StackOverflow, I made an animated GIF to show how that looks:

enter image description here


I decided to try and do it with tkinter as well. I am no expert on tkinter but the following works just the same as the code above. If anyone knows tkinter better, please feel free to point out my inadequacies - I am happy to learn! Thank you.

#!/usr/bin/env python3

import numpy as np
from tkinter import *
from PIL import Image, ImageTk

# Create Tkinter Window and Label
root = Tk()
video = Label(root)
video.pack()

# Open image
im = Image.open('paddington.png')

# Make Numpy version for simpler pixel access
RGB = np.array(im)
h, w = RGB.shape[:2]

# Make empty image to fill in slowly and display
d = np.zeros_like(RGB)

# Use "x" to avoid drawing and waiting for every single pixel
x=0
for y in range(h):
   for x in range(w):
       d[y,x] = RGB[y,x]
       if x%400==0:
          # Convert the video for Tkinter
          img = Image.fromarray(d)
          imgtk = ImageTk.PhotoImage(image=img)
          # Set the image on the label
          video.config(image=imgtk)
          # Update the window
          root.update() 

       x += 1
Related