2025, Nov 19 07:00
How to Fix pyGLFW set_window_icon Errors: Provide an RGBA HxWx4 Array or a PIL Image (No Flattening)
Fix pyGLFW set_window_icon TypeError with RGBA HxWx4 pixels or a PIL Image. Includes OpenCV/Pillow conversion steps, alpha channel requirements, and BGR to RGB.
Setting a window icon with pyGLFW can be unexpectedly tricky if you pass pixel data in the wrong shape. A common pitfall is flattening the image buffer before handing it to glfw.set_window_icon(), which triggers a TypeError originating inside pyGLFW’s own conversion logic.
Problem in context
The issue appears when an image is read, optionally extended with an alpha channel, then flattened, and finally passed as a tuple to pyGLFW. The following example illustrates the failing approach.
def apply_window_icon(self, icon_path: str):
frame = cv2.imread(icon_path, cv2.IMREAD_UNCHANGED)
if frame is None:
return False
hh, ww = frame.shape[:2]
if frame.shape[2] == 3:
a_layer = numpy.ones((hh, ww, 1), dtype=numpy.uint8) * 255
frame = numpy.concatenate((frame, a_layer), axis=2)
frame = frame.astype(numpy.uint8)
flat_buf = frame.flatten()
glfw_image = (ww, hh, flat_buf)
glfw.set_window_icon(self.handle, 1, [glfw_image])
This leads to an error like “TypeError: 'int' object is not subscriptable,” pointing at an internal pixels[i][j][k] access.
What actually goes wrong
The internal implementation of pyGLFW expects the pixel payload to be a structured collection with three indices: height, width, and channel. In other words, a 3D array shaped as H x W x 4. The relevant section in glfw/__init__.py makes this explicit by indexing pixels with three subscripts and iterating over four channels:
else:
self.width, self.height, pixels = image
array_type = ctypes.c_ubyte * 4 * self.width * self.height
self.pixels_array = array_type()
for i in range(self.height):
for j in range(self.width):
for k in range(4):
self.pixels_array[i][j][k] = pixels[i][j][k]
This means a flattened 1D buffer is incompatible with the expected access pattern. It also implies that the icon must carry an alpha channel, because the innermost loop ranges over four channels (RGBA).
There is another supported path as well. If the object passed to glfw.set_window_icon() looks like a PIL Image, pyGLFW converts it to RGBA and ingests the data directly. The logic checks for .size and .convert and handles it like this:
if hasattr(image, 'size') and hasattr(image, 'convert'):
# Treat image as PIL/pillow Image object
self.width, self.height = image.size
array_type = ctypes.c_ubyte * 4 * (self.width * self.height)
self.pixels_array = array_type()
pixels = image.convert('RGBA').getdata()
for i, pixel in enumerate(pixels):
self.pixels_array[i] = pixel
Fixing the code
There are two reliable ways forward. The first is to keep using OpenCV, but provide a proper 3D array in RGBA order and avoid flatten(). The second is to pass a PIL Image directly and let pyGLFW handle the conversion.
Below are corrected variants that preserve the original behavior while fixing the data layout and channel order.
import cv2
import numpy
import glfw
# image generated with ImageMagick:
# convert -size 32x32 -define png:color-type=2 canvas:white empty.png
# or with Python and PIL
# python3 -c 'from PIL import Image;Image.new("RGB", (32,32), color="white").save("empty.png")'
ICON_FILE = 'empty.png'
def attach_icon_via_pillow(win_handle, icon_path):
from PIL import Image
picture = Image.open(icon_path)
glfw.set_window_icon(win_handle, 1, [picture])
def attach_icon_via_cv2(win_handle, icon_path):
img_cv = cv2.imread(icon_path, cv2.IMREAD_UNCHANGED)
img_cv = cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB) # convert from BGR to RGB
if img_cv is None:
return False
hh, ww = img_cv.shape[:2]
if img_cv.shape[2] == 3:
alpha_chan = numpy.ones((hh, ww, 1), dtype=numpy.uint8) * 255
img_cv = numpy.concatenate((img_cv, alpha_chan), axis=2)
img_cv = img_cv.astype(numpy.uint8)
glfw_image = (ww, hh, img_cv) # 3D array, not flattened
glfw.set_window_icon(win_handle, 1, [glfw_image])
def demo():
if not glfw.init():
return
win = glfw.create_window(640, 480, "Hello World", None, None)
if not win:
glfw.terminate()
return
attach_icon_via_cv2(win, ICON_FILE)
# attach_icon_via_pillow(win, ICON_FILE)
glfw.make_context_current(win)
while not glfw.window_should_close(win):
glfw.swap_buffers(win)
glfw.poll_events()
glfw.terminate()
if __name__ == '__main__':
demo()
Why this matters
pyGLFW’s API supports two distinct icon input forms: a PIL Image or a tuple of width, height, and a 3D RGBA array. Passing a flattened buffer breaks the expected pixels[i][j][k] access, which results in the TypeError you saw. Understanding this contract saves time on debugging and makes the fix straightforward. When documentation is sparse, reading the small but clear sections of library source helps reveal exactly what shape and type the function expects. Including full error traces also shortens the feedback loop, because the failing line often points directly at the mismatch.
Takeaways
Keep the pixel data as a H x W x 4 array when using OpenCV and add an alpha channel if it’s missing. Do not flatten the buffer. If you prefer the simplest path, pass a PIL Image and let pyGLFW convert it to RGBA internally. And when you get stuck or can’t find Python-specific documentation, inspect the library’s source and share the complete error message—the line and context usually reveal the exact shape the function expects.