2025, Nov 28 03:02
Исправляем TypeError при set_window_icon в pyGLFW: передаем RGBA массив или PIL Image
Разбираем ошибку TypeError в pyGLFW при set_window_icon: почему нельзя сплющивать буфер, как правильно передать RGBA 3D‑массив из OpenCV или объект PIL Image.
Установка значка окна в pyGLFW может неожиданно оказаться непростой задачей, если передать пиксельные данные в неверной форме. Частая ошибка — сплющивать буфер изображения перед вызовом glfw.set_window_icon(), из‑за чего возникает TypeError внутри собственной логики преобразования pyGLFW.
Проблема в контексте
Проблема проявляется, когда изображение читается, при необходимости дополняется альфа‑каналом, затем сплющивается и в конце передаётся в pyGLFW как кортеж. Ниже — пример, который демонстрирует неверный подход.
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])
Это приводит к ошибке вроде «TypeError: 'int' object is not subscriptable», указывающей на внутренний доступ к pixels[i][j][k].
Что на самом деле происходит
Внутренняя реализация pyGLFW ожидает, что пиксельные данные будут структурированной коллекцией с тремя индексами: высота, ширина и канал. Иными словами, 3D‑массив формы H x W x 4. Соответствующий фрагмент в glfw/__init__.py явно индексирует pixels тремя индексами и итерируется по четырём каналам:
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]
Это означает, что сплющенный 1D‑буфер несовместим с ожидаемым шаблоном доступа. Это также подразумевает, что у значка должен быть альфа‑канал, поскольку внутренний цикл проходит по четырём каналам (RGBA).
Есть и другой поддерживаемый путь. Если объект, переданный в glfw.set_window_icon(), выглядит как изображение PIL, pyGLFW конвертирует его в RGBA и считывает данные напрямую. Логика проверяет наличие .size и .convert и обрабатывает это так:
if hasattr(image, 'size') and hasattr(image, 'convert'):
# Рассматривать объект как изображение PIL/Pillow
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
Как исправить код
Есть два надёжных варианта. Первый — продолжать использовать OpenCV, но передавать корректный 3D‑массив в порядке RGBA и не вызывать flatten(). Второй — передать объект PIL Image и позволить pyGLFW выполнить конвертацию.
Ниже — исправленные варианты, которые сохраняют исходную логику, но приводят форму данных и порядок каналов к ожидаемым.
import cv2
import numpy
import glfw
# изображение создано с помощью ImageMagick:
# convert -size 32x32 -define png:color-type=2 canvas:white empty.png
# или на Python с 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) # преобразование из BGR в 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‑массив, не сплющенный
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()
Почему это важно
API pyGLFW поддерживает две разные формы входных данных для значка: объект PIL Image или кортеж из ширины, высоты и 3D‑массива RGBA. Передача сплющенного буфера нарушает ожидаемый доступ pixels[i][j][k], что и приводит к увиденному TypeError. Понимание этого контракта экономит время на отладку и делает исправление очевидным. Когда документация скудна, чтение небольших, но понятных фрагментов исходного кода библиотеки помогает точно понять, какую форму и тип ожидает функция. Включение полного сообщения об ошибке тоже ускоряет поиск решения: проблемная строка часто прямо указывает на несоответствие.
Выводы
Держите пиксели в виде массива H x W x 4 при работе с OpenCV и добавляйте альфа‑канал, если его нет. Не сплющивайте буфер. Если нужен самый простой путь, передавайте объект PIL Image — pyGLFW преобразует его в RGBA сам. А если застряли или не находите документацию под Python, загляните в исходники библиотеки и приложите полный трейсбэк: строка и контекст обычно точно показывают, какую форму ожидает функция.