Why Tk build-in command makes `create_image()` fast?

I’m making a simple image viewer for study Tkinter. The code is shown in the appendix A. I noticed that disp_img() becomes fast after asksaveasfilename() runed. Default disp_img() process time is about 0.04s. The disp_img() after asksaveasfilename() is about 0.002s. I checked detail about disp_img() 's process by cProfile. I noticed create_image() becomes fast. But why?

I tracked the source code (cpython/filedialog.py at main · python/cpython · GitHub). The asksaveasfilename() create SaveAs class. To find why create_image() becomes fast I tried to compare the case used SaveAs with the case used _Dialog (SaveAs parent class). The case used SaveAs (appendix B) is fast, but the case used _Dialog (appendix C) is not. SaveAs class is setted Tk build-in command “tk_getSaveFile”, _Dialog is not. So I think Tk build-in command is the reason why create_image() becomes fast.
I want to know why Tk build-in command makes create_image() fast because I want to make create_image() fast without the dialog.

Why Tk build-in command makes create_image() fast?

Appendix A: First code

When the code is uncommented, disp_img() becomes fast.

import tkinter as tk
from tkinter import filedialog
import numpy as np
import time
import cProfile as profile
from PIL import Image, ImageTk, ImageOps

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)
        self.pack()

        self.master.title("Image Viewer") 
        self.master.geometry("400x300")

        #filename = filedialog.asksaveasfilename(
        #  title = "Save As",
        #  filetypes = [("Image file", ".bmp .png .jpg .tif"), 
        #               ("Bitmap", ".bmp"), ("PNG", ".png"), 
        #               ("JPEG", ".jpg"), ("Tiff", ".tif") ],
        #  initialdir = "./"
        #  )
        
        self.back_color = "#008B8B"
        self.canvas = tk.Canvas(self.master, bg = self.back_color)
        self.canvas.pack(expand = True, fill = tk.BOTH)
    
        self.update_idletasks()
        self.canvas_width = self.canvas.winfo_width()
        self.canvas_height = self.canvas.winfo_height()
    
        self.pil_img = None
        self.after(10, self.timeEvent)
    
    def disp_image(self):
        disp_start_time = time.time()
    
        img_array = np.zeros((240,320,3), np.uint8)
        self.pil_img = Image.fromarray(img_array)
        self.photo_img = ImageTk.PhotoImage(image=self.pil_img)
    
        self.canvas.create_image(
            self.canvas_width / 2,
            self.canvas_height / 2,
            image=self.photo_img
            )
    
        print("disp time = "+str(time.time() - disp_start_time))
    
    def timeEvent(self):
        profile.runctx('self.disp_image()', globals(), locals())
        print('')
        self.after(10, self.timeEvent)
    
def main():
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

Part of the output is below.

  • Default
   disp time 0.04877352714538574  
         84 function calls in 0.049 seconds  
  
   Ordered by: standard name  
  
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)  
        1    0.000    0.000    0.049    0.049 <string>:1(<module>)  
        3    0.000    0.000    0.000    0.000 Image.py:2644(_check_size)  
        1    0.000    0.000    0.000    0.000 Image.py:2662(new)  
        1    0.000    0.000    0.000    0.000 Image.py:2701(frombytes)  
        1    0.000    0.000    0.000    0.000 Image.py:2739(frombuffer)  
        1    0.000    0.000    0.000    0.000 Image.py:2792(fromarray)  
        1    0.000    0.000    0.000    0.000 Image.py:413(_getdecoder)  
        2    0.000    0.000    0.000    0.000 Image.py:524(__init__)  
        3    0.000    0.000    0.000    0.000 Image.py:556(size)  
        1    0.000    0.000    0.000    0.000 Image.py:560(_new)  
        1    0.000    0.000    0.000    0.000 Image.py:788(frombytes)  
        1    0.000    0.000    0.000    0.000 Image.py:814(load)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:117(__del__)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:125(__str__)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:151(paste)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:85(__init__)  
        1    0.000    0.000    0.000    0.000 __init__.py:101(_cnfmerge)  
        1    0.000    0.000    0.000    0.000 __init__.py:1468(_options)  
        1    0.000    0.000    0.048    0.048 __init__.py:2768(_create)  
        1    0.000    0.000    0.048    0.048 __init__.py:2788(create_image)  
        1    0.000    0.000    0.000    0.000 __init__.py:291(_get_default_root)  
        1    0.000    0.000    0.000    0.000 __init__.py:3994(__init__)  
        2    0.000    0.000    0.000    0.000 __init__.py:4012(__str__)  
        1    0.000    0.000    0.000    0.000 __init__.py:4014(__del__)  
        1    0.000    0.000    0.000    0.000 __init__.py:4059(__init__)  
        1    0.000    0.000    0.049    0.049 igl_display_py.py:33(disp_image)  
        1    0.000    0.000    0.000    0.000 {built-in method PIL._imaging.fill}  
        1    0.000    0.000    0.000    0.000 {built-in method PIL._imaging.raw_decoder}  
        2    0.000    0.000    0.000    0.000 {built-in method _tkinter._flatten}  
        3    0.000    0.000    0.000    0.000 {built-in method builtins.callable}  
        1    0.000    0.000    0.049    0.049 {built-in method builtins.exec}  
        2    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}  
        2    0.000    0.000    0.000    0.000 {built-in method builtins.hasattr}  
       11    0.000    0.000    0.000    0.000 {built-in method builtins.isinstance}  
        7    0.000    0.000    0.000    0.000 {built-in method builtins.len}  
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}  
        1    0.000    0.000    0.000    0.000 {built-in method numpy.zeros}  
        2    0.000    0.000    0.000    0.000 {built-in method time.time}  
        4    0.048    0.012    0.048    0.012 {method 'call' of '_tkinter.tkapp' objects}  
        1    0.000    0.000    0.000    0.000 {method 'convert2' of 'ImagingCore' objects}  
        1    0.000    0.000    0.000    0.000 {method 'copy' of 'dict' objects}  
        1    0.000    0.000    0.000    0.000 {method 'decode' of 'ImagingDecoder' objects}  
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}  
        1    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}  
        1    0.000    0.000    0.000    0.000 {method 'getint' of '_tkinter.tkapp' objects}  
        1    0.000    0.000    0.000    0.000 {method 'isblock' of 'ImagingCore' objects}  
        2    0.000    0.000    0.000    0.000 {method 'items' of 'dict' objects}  
        1    0.000    0.000    0.000    0.000 {method 'new_block' of 'ImagingCore' objects}  
        1    0.000    0.000    0.000    0.000 {method 'pixel_access' of 'ImagingCore' objects}  
        1    0.000    0.000    0.000    0.000 {method 'setimage' of 'ImagingDecoder' objects}  
        2    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}  
  • Uncommented
   disp time = 0.0014710426330566406  
         84 function calls in 0.002 seconds  
  
   Ordered by: standard name  
  
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)  
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)  
        3    0.000    0.000    0.000    0.000 Image.py:2644(_check_size)  
        1    0.000    0.000    0.000    0.000 Image.py:2662(new)  
        1    0.000    0.000    0.000    0.000 Image.py:2701(frombytes)  
        1    0.000    0.000    0.000    0.000 Image.py:2739(frombuffer)  
        1    0.000    0.000    0.000    0.000 Image.py:2792(fromarray)  
        1    0.000    0.000    0.000    0.000 Image.py:413(_getdecoder)  
        2    0.000    0.000    0.000    0.000 Image.py:524(__init__)  
        3    0.000    0.000    0.000    0.000 Image.py:556(size)  
        1    0.000    0.000    0.000    0.000 Image.py:560(_new)  
        1    0.000    0.000    0.000    0.000 Image.py:788(frombytes)  
        1    0.000    0.000    0.000    0.000 Image.py:814(load)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:117(__del__)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:125(__str__)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:151(paste)  
        1    0.000    0.000    0.000    0.000 ImageTk.py:85(__init__)  
        1    0.000    0.000    0.000    0.000 __init__.py:101(_cnfmerge)  
        1    0.000    0.000    0.000    0.000 __init__.py:1468(_options)  
        1    0.000    0.000    0.001    0.001 __init__.py:2768(_create)  
        1    0.000    0.000    0.001    0.001 __init__.py:2788(create_image)  
        1    0.000    0.000    0.000    0.000 __init__.py:291(_get_default_root)  
        1    0.000    0.000    0.000    0.000 __init__.py:3994(__init__)  
        2    0.000    0.000    0.000    0.000 __init__.py:4012(__str__)  
        1    0.000    0.000    0.000    0.000 __init__.py:4014(__del__)  
        1    0.000    0.000    0.000    0.000 __init__.py:4059(__init__)  
        1    0.000    0.000    0.001    0.001 igl_display_py.py:33(disp_image)  
        1    0.000    0.000    0.000    0.000 {built-in method PIL._imaging.fill}  
        1    0.000    0.000    0.000    0.000 {built-in method PIL._imaging.raw_decoder}  
        2    0.000    0.000    0.000    0.000 {built-in method _tkinter._flatten}  
        3    0.000    0.000    0.000    0.000 {built-in method builtins.callable}  
        1    0.000    0.000    0.002    0.002 {built-in method builtins.exec}  
        2    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}  
        2    0.000    0.000    0.000    0.000 {built-in method builtins.hasattr}  
       11    0.000    0.000    0.000    0.000 {built-in method builtins.isinstance}  
        7    0.000    0.000    0.000    0.000 {built-in method builtins.len}  
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}  
        1    0.000    0.000    0.000    0.000 {built-in method numpy.zeros}  
        2    0.000    0.000    0.000    0.000 {built-in method time.time}  
        4    0.001    0.000    0.001    0.000 {method 'call' of '_tkinter.tkapp' objects}  
        1    0.000    0.000    0.000    0.000 {method 'convert2' of 'ImagingCore' objects}  
        1    0.000    0.000    0.000    0.000 {method 'copy' of 'dict' objects}  
        1    0.000    0.000    0.000    0.000 {method 'decode' of 'ImagingDecoder' objects}  
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}  
        1    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}  
        1    0.000    0.000    0.000    0.000 {method 'getint' of '_tkinter.tkapp' objects}  
        1    0.000    0.000    0.000    0.000 {method 'isblock' of 'ImagingCore' objects}  
        2    0.000    0.000    0.000    0.000 {method 'items' of 'dict' objects}  
        1    0.000    0.000    0.000    0.000 {method 'new_block' of 'ImagingCore' objects}  
        1    0.000    0.000    0.000    0.000 {method 'pixel_access' of 'ImagingCore' objects}  
        1    0.000    0.000    0.000    0.000 {method 'setimage' of 'ImagingDecoder' objects}  
        2    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}  

Appendix B: Use filedialog.SaveAs

Code

import tkinter as tk
from tkinter import filedialog
import numpy as np
import time
import cProfile as profile
from PIL import Image, ImageTk, ImageOps

class Application(tk.Frame):
  def __init__(self, master = None):
    super().__init__(master)
    self.pack()

    self.master.title("Image Viewer") 
    self.master.geometry("400x300")

    filename = filedialog.SaveAs(
      title = "Save As",
      filetypes = [("Image file", ".bmp .png .jpg .tif"), ("Bitmap", ".bmp"), ("PNG", ".png"), ("JPEG", ".jpg"), ("Tiff", ".tif") ],
      initialdir = "./"
      ).show()
      
    self.back_color = "#008B8B"
    self.canvas = tk.Canvas(self.master, bg = self.back_color)
    self.canvas.pack(expand = True, fill = tk.BOTH)

    self.update_idletasks()
    self.canvas_width = self.canvas.winfo_width()
    self.canvas_height = self.canvas.winfo_height()

    self.pil_img = None
    self.after(200, self.timeEvent)

  def disp_image(self):
    disp_start_time = time.time()

    img_array = np.zeros((240,320,3), np.uint8)
    self.pil_img = Image.fromarray(img_array)
    self.photo_img = ImageTk.PhotoImage(image=self.pil_img)

    self.canvas.create_image(
            self.canvas_width / 2,
            self.canvas_height / 2,
            image=self.photo_img
            )

    print("disp time = "+str(time.time() - disp_start_time))


  def timeEvent(self):
    self.disp_image()
    self.after(200, self.timeEvent)

def main():
  root = tk.Tk()
  app = Application(master = root)
  app.mainloop()

Result

disp time = 0.00701141357421875
disp time = 0.005995988845825195
disp time = 0.005327463150024414
disp time = 0.004936695098876953
disp time = 0.0044977664947509766
disp time = 0.004569053649902344
disp time = 0.00503087043762207
disp time = 0.004759550094604492
disp time = 0.004475593566894531
disp time = 0.004952907562255859
disp time = 0.0070192813873291016
disp time = 0.004935503005981445
disp time = 0.003088235855102539
disp time = 0.004522085189819336
disp time = 0.004429817199707031
disp time = 0.004549503326416016
disp time = 0.004712581634521484
disp time = 0.0046079158782958984
disp time = 0.004618406295776367
disp time = 0.004693508148193359

Appendix C: Use filedialog._Dialog (SaveAs parent)

Code

import tkinter as tk
from tkinter import filedialog
import numpy as np
import time
import cProfile as profile
from PIL import Image, ImageTk, ImageOps

class Application(tk.Frame):
  def __init__(self, master = None):
    super().__init__(master)
    self.pack()

    self.master.title("Image Viewer") 
    self.master.geometry("400x300")

    filename = filedialog._Dialog(
      title = "Save As",
      filetypes = [("Image file", ".bmp .png .jpg .tif"), ("Bitmap", ".bmp"), ("PNG", ".png"), ("JPEG", ".jpg"), ("Tiff", ".tif") ],
      initialdir = "./"
      ).show()
      
    self.back_color = "#008B8B"
    self.canvas = tk.Canvas(self.master, bg = self.back_color)
    self.canvas.pack(expand = True, fill = tk.BOTH)

    self.update_idletasks()
    self.canvas_width = self.canvas.winfo_width()
    self.canvas_height = self.canvas.winfo_height()

    self.pil_img = None
    self.after(200, self.timeEvent)

  def disp_image(self):
    disp_start_time = time.time()

    img_array = np.zeros((240,320,3), np.uint8)
    self.pil_img = Image.fromarray(img_array)
    self.photo_img = ImageTk.PhotoImage(image=self.pil_img)

    self.canvas.create_image(
            self.canvas_width / 2,
            self.canvas_height / 2,
            image=self.photo_img
            )

    print("disp time = "+str(time.time() - disp_start_time))


  def timeEvent(self):
    self.disp_image()
    self.after(200, self.timeEvent)

def main():
  root = tk.Tk()
  app = Application(master = root)
  app.mainloop()

Result

disp time = 0.11635494232177734
disp time = 0.1086418628692627
disp time = 0.09522628784179688
disp time = 0.09302139282226562
disp time = 0.09846663475036621
disp time = 0.09961771965026855
disp time = 0.10175251960754395
disp time = 0.09738302230834961
disp time = 0.09523272514343262
disp time = 0.15919780731201172
disp time = 0.15273475646972656
disp time = 0.1178138256072998
disp time = 0.09615159034729004
disp time = 0.08730411529541016
disp time = 0.10056090354919434
disp time = 0.14258956909179688
disp time = 0.1004493236541748
disp time = 0.09966635704040527
disp time = 0.09452080726623535
disp time = 0.1370866298675537