I am trying to make a tiling background and have 50 000 small images for the background. I tried doing it by creating a lot of images and I read here that having many canvas items can be very slow. The images are only 16 by 16 and I wonder how to make it faster (it takes around a quarter second to scroll to the side currently).
Should I use one large bitmap image that I edit somehow or is there a better way to do this. I need to be able to change the tiles but only one or two at a time.
I tried only loading the ones on screen (~10 000) but it was still very slow and I had to recreate them when I went back using up even more ids.
I expect to be able to scroll the canvas without it being incredibly slow.
I thought @BryanOakley 's advice was quite good, and I made some example code that does just that, and I think it works quite well, I was able to get about 1.05 ms update times, and that's with the .after
call with 1 ms.
import tkinter as tk
import time
from PIL import Image, ImageTk
import numpy as np
NUM_IMAGES = 30*300 # approx 10,000
IMAGE_FNAMES = ['a.png', 'b.png', 'c.png', 'd.png']
IMAGE_SIZE = 16
CANVAS_SIZE = 480
def _gt(s=0.0):
return time.perf_counter() - s
class JonathanGassendWindow():
def __init__(self, master):
self._master = master
self._canvas = tk.Canvas(self._master, width=CANVAS_SIZE, height=CANVAS_SIZE, bg='#FF00FF')
self._canvas.grid(row=0, column=0, sticky=tk.N + tk.E + tk.W + tk.S)
self._images = []
# https://stackoverflow.com/questions/12760389/
self._bg_image = Image.new('RGB', ((NUM_IMAGES // (CANVAS_SIZE // IMAGE_SIZE)) * IMAGE_SIZE * 2, CANVAS_SIZE))
print(self._bg_image)
# Load image files (I only made 4, but the concept remains)
for i in range(NUM_IMAGES):
with Image.open(IMAGE_FNAMES[i % len(IMAGE_FNAMES)]) as img:
img.load()
self._images.append(img)
# Build the bg image
rng = np.random.default_rng(seed=42)
for i in range(self._bg_image.height // IMAGE_SIZE):
for j in range(self._bg_image.width // IMAGE_SIZE):
self._bg_image.paste(
self._images[rng.integers(0, len(self._images), size=(1,))[0]],
(j * IMAGE_SIZE, i * IMAGE_SIZE)
)
self._bg_photoimage = ImageTk.PhotoImage(self._bg_image)
self._bg_handle = self._canvas.create_image(0, 0, image=self._bg_photoimage, anchor=tk.N + tk.W)
self._prev_loop = _gt()
self._master.after(5000, self._updateloop)
def _updateloop(self):
self._canvas.move(self._bg_handle, -1, 0)
s = _gt()
dur_total = s - self._prev_loop
self._prev_loop = s
print(f'\rUpdate loop dur: {dur_total * 1000:6.3f} ms', end='')
self._master.after(1, self._updateloop)
def _main():
root = tk.Tk()
jgw = JonathanGassendWindow(root)
root.mainloop()
if __name__ == '__main__':
_main()
That should be enough to get you started, let me know if you have any questions.