如何用python-xlib渲染16x16窗口图标?代码报错求助
问题
我正在用Python 3开发窗口管理器,需要实现16x16窗口图标的渲染功能。使用Pillow和python-xlib编写了测试程序,已将代码中的screen.default_visual修改为root_visual,但仍报错。
测试代码:
from Xlib import display, X, Xutil from PIL import Image, ImageDraw import os def renderPng(image_path, width, height, x=50, y=50): img = Image.open(image_path) if img.mode != 'RGB': img = img.convert('RGB') image_data = img.tobytes("raw", "RGB") disp = display.Display() screen = disp.screen() root = screen.root visual = screen.root_visual depth = screen.root_depth win = root.create_window( x, y, width, height, 1, depth, X.InputOutput, visual, background_pixel=screen.white_pixel, event_mask=X.ExposureMask | X.KeyPressMask | X.StructureNotifyMask ) win.set_wm_name("PNG image") win.map() gc = win.create_gc( foreground=screen.black_pixel, background=screen.white_pixel ) ximage = X.Image( data=image_data, width=16, height=16, depth=depth, bitmap_unit=8, bitmap_pad=32, byte_order=X.LSBFirst, bitmap_bit_order=X.LSBFirst, format=X.ZPixmap, bytes_per_line=16 * 3 ) while True: event = disp.next_event() if event.type == X.Expose: if event.count == 0: win.put_image(gc, ximage, 0, 0, 0, 0, 16, 16) disp.flush() elif event.type == X.KeyPress: break elif event.type == X.ConfigureNotify: pass win.destroy() disp.close() if __name__ == '__main__': dummy = "dummy.png" if not os.path.exists(dummy): img = Image.new('RGB', (16, 16), color = 'red') d = ImageDraw.Draw(img) d.text((1,1), "abc", fill=(255,255,255)) img.save(dummy) renderPng(dummy, 200, 200, 100, 100)
运行后报错:
Traceback (most recent call last): File "/home/spincube/x/test.py", line 63, in <module> renderPng(dummy, 200, 200, 100, 100) ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/spincube/x/test.py", line 31, in renderPng ximage = X.Image( ^^^^^^^ AttributeError: module 'Xlib.X' has no attribute 'Image'
解决方案
错误原因
Xlib.X模块中没有Image类,创建XImage对象需要使用Xlib.XImage类,或者通过Display实例的create_image方法。此外,直接传入RGB原始数据可能和窗口的视觉格式不匹配,需要根据屏幕的depth转换像素格式。
修正后的代码
from Xlib import display, X, Xutil from Xlib.XImage import XImage # 导入正确的XImage类 from PIL import Image, ImageDraw import os def renderPng(image_path, width, height, x=50, y=50): img = Image.open(image_path) # 确保图像大小是16x16 img = img.resize((16,16)) disp = display.Display() screen = disp.screen() root = screen.root visual = screen.root_visual depth = screen.root_depth # 根据屏幕depth转换图像格式 if depth == 24 or depth == 32: # 对于24/32位深度,转换为RGBX格式(Xlib需要的顺序) img = img.convert('RGBX') image_data = img.tobytes("raw", "RGBX") bytes_per_line = 16 * 4 else: # 8位深度的情况(很少见,这里做兼容处理) img = img.convert('P', palette=Image.ADAPTIVE) image_data = img.tobytes() bytes_per_line = 16 win = root.create_window( x, y, width, height, 1, depth, X.InputOutput, visual, background_pixel=screen.white_pixel, event_mask=X.ExposureMask | X.KeyPressMask | X.StructureNotifyMask ) win.set_wm_name("PNG image") win.map() gc = win.create_gc( foreground=screen.black_pixel, background=screen.white_pixel ) # 使用正确的XImage类创建对象 ximage = XImage( data=image_data, width=16, height=16, depth=depth, bitmap_unit=8, bitmap_pad=32, byte_order=disp.byte_order(), # 使用Display的字节序,更可靠 bitmap_bit_order=X.LSBFirst, format=X.ZPixmap, bytes_per_line=bytes_per_line ) while True: event = disp.next_event() if event.type == X.Expose: if event.count == 0: win.put_image(gc, ximage, 0, 0, 0, 0, 16, 16) disp.flush() elif event.type == X.KeyPress: break elif event.type == X.ConfigureNotify: pass win.destroy() disp.close() if __name__ == '__main__': dummy = "dummy.png" if not os.path.exists(dummy): img = Image.new('RGB', (16, 16), color = 'red') d = ImageDraw.Draw(img) d.text((1,1), "abc", fill=(255,255,255)) img.save(dummy) renderPng(dummy, 200, 200, 100, 100)
关键修改点
- 导入
Xlib.XImage.XImage类,替换原错误的X.Image调用。 - 根据屏幕的depth转换图像格式:24/32位深度使用RGBX格式(Xlib的ZPixmap需要4字节对齐),避免格式不兼容。
- 使用
disp.byte_order()获取Display的字节序,替代硬编码的X.LSBFirst,提升兼容性。 - 强制将输入图像缩放到16x16,确保和渲染尺寸匹配。
内容的提问来源于stack exchange,提问作者SpinningCubes
相关产品推荐
相关产品推荐

