1

I am new to Tkinter (and Python) and I would like to find the most efficient way to format RGB values into a string so it can be used with the PhotoImage.put() function.

Let's say I have a Numpy rank 3 array in which the RGB values are stored, the 3rd dimension having a length of 3 for red, green and blue respectively. The most intuitive way to proceed would be:

for i in range(0, n_pixels_x):
    for j in range(0, n_pixels_y):
        hexcode = "#%02x%02x%02x" % (array[i,j,0], array[i,j,1], array[i,j,2])
        img.put(hexcode, (j,i))

Unfortunately, this is way too slow for large images.

As described in the PhotoImage Wiki, it is possible to pass one large string to put() so the function is called only once. Then, I need to efficiently convert my array into such a string, which should be formatted like this (for a 4x2 image):

"{#ff0000 #ff0000 #ff0000 #ff0000} {#ff0000 #ff0000 #ff0000 #ff0000}"

Again, this could easily be done with nested for loops, but I would like to avoid them for efficiency reasons. Is there any way to use join() in order to do what I want?

If needed, I can store the content of my array differently, the only constraint being that I should be able to modify the color values easily.

Edit: After working on this a bit, I found a way to format my values approximately 10 times faster than by using nested loops. Here is the commented piece of code:

# 1. Create RGB array
array = np.zeros((n_pixels_x*n_pixels_y, 3))
array = np.asarray(array, dtype = "uint32")
array[1,:] = [0, 100, 255]

# 2. Create a format string
fmt_str = "{" + " ".join(["#%06x"]*n_pixels_x) + "}"
fmt_str = " ".join([fmt_str]*n_pixels_y)

# 3. Convert RGB values to hex
array_hex = (array[:,0]<<16) + (array[:,1]<<8) + array[:,2]

# 4. Format array
img_str = fmt_str % tuple(array_hex)

For a 640x480 array, steps 3 and 4 take ~0.1s to execute on my laptop (evaluated with timeit.default_timer()). Using nested loops, it takes between 0.9s and 1.0s.

I would still like to reduce the computation time, but I'm not sure if any improvement is still possible at this point.

Iodestar
  • 198
  • 2
  • 13
  • The inefficiency comes from the fact that python uses strings to pass data to the Tkinter engine. I had the same problem before and was not able to find a faster way to transmit pixels to a `PhotoImage`. The only optimisation I can think of is to use `#f00` instead of `#ff0000` (if possible for all your pixels). This is not much. If you really need speed (e.g. for animation), you can have a look at `pygame`. – Sci Prog Mar 04 '16 at 03:00
  • @SciProg I was actually trying to avoid `pygame` so I could code myself almost everything I need, and `Tkinter` seemed to be a good compromise. Can `PhotoImage` accept anything other than string data? Maybe I could use the Python Image Library to process my array, but I'm not familiar with it yet. – Iodestar Mar 04 '16 at 03:17
  • Unfortunately, I was not able to find a faster way to put pixels in a PhotoImage. – Sci Prog Mar 06 '16 at 04:21

1 Answers1

0

I was able to find another way to format my array, and this really seems to be the quickest solution. The solution is to simply use Image and ImageTk to generate an image object directly from the array:

array = np.zeros((height, width, 3), 'uint8')

imageObject = Image.fromarray(array)
img = ImageTk.PhotoImage(image = imageObject, mode = 'RGB'))

This takes approximately 0.02s to run, which is good enough for my needs, and there is no need to use the put() function.

I actually found this answer from another question: How do I convert a numpy array to (and display) an image?

Community
  • 1
  • 1
Iodestar
  • 198
  • 2
  • 13