1

Hope you all are well!

Spent the last couple weeks researching image processing for my tkinter application and came up with this script:

import contextlib
import tkinter as tk
from PIL import Image, ImageTk, ImageSequence
import requests
from itertools import cycle


class ImageLabel(tk.Label):
    """
    A Label that displays images, and plays them if they are gifs
    :im: A PIL Image instance or a string filename
    """

    def load(self, url, width, height=None):
        request = requests.get(url, stream=True).raw
        im = Image.open(request)
        if (height != None):
            size = (width, height)
        else:
            size = (width, get_relative_height(im, width))
        try:
            self.delay = im.info['duration']
        except Exception:
            self.delay = 100

        global frames_complete
        frames_complete = False

        self.frames_chunk = cycle(process_frames(im, size))

        if frames_complete:
            self.next_frame()

    def next_frame(self):
        self.config(image=next(self.frames_chunk))
        self.after(self.delay, self.next_frame)

    def unload(self):
        self.destroy()


def get_relative_height(source, mywidth):
    _, height = source.size
    wpercent = (mywidth/float(height))
    return int((float(height)*float(wpercent)))


def process_frames(im, size):  # resize and arrange gifs
    frames_chunk = []
    mode = analyseImage(im)["mode"]
    last_frame = im.convert("RGBA")

    for i, frame in enumerate(ImageSequence.Iterator(im)):
        frame_image = Image.new("RGBA", frame.size)
        if mode == "partial":
            frame_image.paste(last_frame)
        print(f'Processing frame {i}')

        frame_image.paste(frame, (0, 0), frame.convert("RGBA"))
        frame_image.thumbnail(size, Image.BICUBIC)
        new_frame = ImageTk.PhotoImage(frame_image)
        frames_chunk.append(new_frame)
        print("appended frame to frames_chunk")
    print("frames completed")
    global frames_complete
    frames_complete = True
    return frames_chunk


def analyseImage(im):
    """
    Pre-process pass over the image to determine the mode (full or additive).
    Necessary as assessing single frames isn't reliable. Need to know the mode
    before processing all frames.ll
    """
    results = {
        "size": im.size,
        "mode": "full",
    }

    with contextlib.suppress(EOFError):
        while True:
            if im.tile:
                tile = im.tile[0]
                update_region = tile[1]
                update_region_dimensions = update_region[2:]
                if update_region_dimensions != im.size:
                    results["mode"] = "partial"
                    break
            im.seek(im.tell() + 1)
    return results


# test:
root = tk.Tk()
lbl = ImageLabel(root)
lbl.pack()
lbl.load("https://www.saic.edu/~anelso13/gif/images/cat14.gif", 300)
root.mainloop()

running this from inside a tkinter app slows the app down and also freezes the GUI until the frames are finished processing.

This class works alright when it's alone, but there are two major issues I've been struggling to solve,

  1. The process_frames function itterates frame by frame and is very slow. in the app I'm working on I instance this class two times and it takes about 10 seconds to process and resize every frame. I ran the function inside a thread but it didn't seem to improve speed whatsoever.

2: The Main tkinter application freezes until both sets of frames process. I've looked at a few resources and tried a few implementations (tkinter: preventing main loop from freezing) and here. I have a thread running in the program already which does work as expected but using the same method for processing the frames does not work.

Any and all help is greatly appreciated!

0 Answers0