This is a follow up question from Calling ImageTk.PhotoImage() in a thread causing deadlock I have so far fixed the deadlock issue following the design in the accepted answer. Now I am working on a responsive ui that allows user to resize the window. As the window size changes, the image shown in the label should also resize to fill the window. However, I found large images can significantly impact the frame rate. Displaying and updating images on a 4K monitor causes the UI to become inresponsive. I timed the set_image method, which is called recursively with root.after and found that root.after actually delays behind significantly when showing large images: Average delay time: 0.0014089000100102568, image size: 100 x 100 Average delay time: 0.001673793329991895, image size: 200 x 200 Average delay time: 0.0033536416499555344, image size: 400 x 400 Average delay time: 0.02414946045006218, image size: 800 x 800 Average delay time: 0.07494734207000875, image size: 1600 x 1600 Average delay time: 0.1494195945899719, image size: 3200 x 3200 I am using a MacBook M3 Pro so it is not a terrible machine, similar results are obtained from an i7 machine also. Is there a better way to display images in a Tkinter UI? Am I reaching the limitation of python and should consider C++ at this point instead? Below is a minimum viable code for you test. import tkinter as tk from PIL import Image, ImageTk import threading import time ITERATION = 20 class Timer: def __init__(self): self.reset() def reset(self): self.time_accumulated = 0 self.start_time = None self.count = 0 def begin(self): self.start_time = time.perf_counter() def stop(self): if self.start_time is None: return self.time_accumulated += time.perf_counter() - self.start_time self.count += 1 def get_average_time(self): return self.time_accumulated / self.count class App: def __init__(self, root): self.root = root self.label = tk.Label(root) self.label.pack() self.running = True self.frame = None self.image_size = 100 # run in other thread self.display_thread = threading.Thread(target=self.update_image_loop, daemon=True) self.display_thread.start() self.root.protocol("WM_DELETE_WINDOW", self.on_close) self.loop_time_previous = time.perf_counter() self.timer = Timer() self.root.after(1, self.set_image) def update_image_loop(self): """Run in other thread.""" while self.running: # self.frame = Image.open(self.image_path) # make a small black image self.frame = Image.new('RGB', (self.image_size, self.image_size), (0, 0, 0)) time.sleep(0.001) def set_image(self): """Run in main thread.""" self.timer.stop() if self.frame: self.image = ImageTk.PhotoImage(image=self.frame) self.label.config(image=self.image) if self.timer.count >= ITERATION: self.next() if self.running: self.timer.begin() self.root.after(1, self.set_image) def next(self): print(f'Average delay time: {self.timer.get_average_time()}, image size: {self.image_size} x {self.image_size}') self.image_size *= 2 if self.image_size >= 5000: self.root.destroy() return self.timer.reset() def on_close(self): self.running = False self.display_thread.join() self.root.destroy() if __name__ == '__main__': root = tk.Tk() app = App(root) root.mainloop() Continue reading...