Python实现RotBlt时调用SetPixel触发pywintypes.error报错排查
问题现象
使用Python基于pywin32实现支持自定义旋转角度的RotBlt位块传输函数时,调用SetPixel绘制旋转后像素触发运行时异常,报错追踪如下:
Traceback (most recent call last): File "C:\Users\MY\Desktop\bckup\Folder rafi\New folder\python-recorder\rotblt.py", line 50, in <module> RotBlt(hdc, 0, 0, x, y, hdc, 0,0, 10, SRCCOPY) File "C:\Users\MY\Desktop\bckup\Folder rafi\New folder\python-recorder\rotblt.py", line 37, in RotBlt SetPixel(memDC, nX+nC, nY+nC, c) pywintypes.error: (0, 'SetPixel', 'No error message is available')
系统未返回有效错误描述,异常抛出位置为内存DC的像素写入操作。
根因定位
代码存在4个核心问题,直接触发SetPixel调用失败:
- 坐标越界:这是触发异常的直接原因。首先内存DC的画布尺寸计算错误:代码强行将画布调整为正方形,且没有计算旋转后图像的外接矩形尺寸,画布大小不足以容纳旋转后的完整画面;其次偏移量
nC计算逻辑完全错误,原有逻辑nC = int(float(sx1)/2)在传入sx1=0时取值为0,旋转后产生的负坐标没有被正向偏移到画布有效范围内,传给SetPixel的坐标要么小于0,要么大于等于画布宽高,触发GDI调用失败。 - 旋转映射逻辑错误:原有实现采用源像素到目标像素的正向映射,这种写法会导致目标画布出现像素空洞、像素覆盖错乱,正确的旋转逻辑应该是反向映射:遍历目标画布的每个像素点,反推其对应源图的像素坐标取值,才能保证画面完整。
- 变量名笔误导致GDI资源泄漏:资源释放阶段写的
DeleteObject(ombp),但前面选入DC的位图变量名为obmp,变量名拼写错误会导致创建的位图资源无法被正常释放,运行时间稍长就会耗尽系统GDI资源。 - 同DC读写冲突:代码将桌面DC同时作为源DC和目标DC传入,旋转过程中边读取源DC像素边写入目标DC,会因为DC内容被实时修改出现画面错乱、像素读取异常的问题。
修复要点
- 先将源图像完整拷贝到独立的临时源DC中,避免同DC读写冲突
- 计算旋转后图像的外接矩形尺寸,创建足够大的内存画布容纳旋转后的内容
- 修正坐标偏移逻辑:旋转后的坐标需要偏移画布宽高的一半,保证所有像素坐标落在画布
[0, 宽-1]、[0, 高-1]的有效范围内,写入像素前增加坐标范围校验,避免越界 - 改用反向映射逻辑计算像素位置
- 修正变量名笔误,确保所有GDI资源被正常释放
修正后的参考实现
from win32api import * from win32gui import * from win32con import * from math import * def RotBlt(destDC, sx1, sy1, sx2, sy2, srcDC, dx1, dy1, deg, mode): theta = radians(deg) w = sx2 - sx1 h = sy2 - sy1 cX = w / 2 cY = h / 2 # 计算旋转后外接矩形尺寸 cos_theta = cos(theta) sin_theta = sin(theta) new_w = int(abs(w * cos_theta) + abs(h * sin_theta)) new_h = int(abs(w * sin_theta) + abs(h * cos_theta)) nCx = new_w // 2 nCy = new_h // 2 # 先把源内容拷贝到独立临时DC,避免读写冲突 src_memDC = CreateCompatibleDC(srcDC) src_bmp = CreateCompatibleBitmap(srcDC, w, h) src_obmp = SelectObject(src_memDC, src_bmp) BitBlt(src_memDC, 0, 0, w, h, srcDC, sx1, sy1, SRCCOPY) # 创建目标内存DC memDC = CreateCompatibleDC(destDC) membmp = CreateCompatibleBitmap(destDC, new_w, new_h) obmp = SelectObject(memDC, membmp) # 反向映射遍历目标像素 for nX in range(new_w): for nY in range(new_h): # 反算对应源图坐标 x = (nX - nCx) * cos_theta + (nY - nCy) * sin_theta + cX y = -(nX - nCx) * sin_theta + (nY - nCy) * cos_theta + cY # 坐标校验,只取源图范围内的像素 if 0 <= int(x) < w and 0 <= int(y) < h: c = GetPixel(src_memDC, int(x), int(y)) SetPixel(memDC, nX, nY, c) # 输出到目标DC BitBlt(destDC, dx1, dy1, new_w, new_h, memDC, 0, 0, mode) # 释放所有GDI资源 SelectObject(src_memDC, src_obmp) DeleteDC(src_memDC) DeleteObject(src_bmp) SelectObject(memDC, obmp) DeleteDC(memDC) DeleteObject(membmp) if __name__ == "__main__": while True: x, y = GetSystemMetrics(0), GetSystemMetrics(1) hwnd = GetDesktopWindow() hdc = GetWindowDC(hwnd) RotBlt(hdc, 0, 0, x, y, hdc, 0, 0, 10, SRCCOPY) ReleaseDC(hwnd, hdc) Sleep(10)
注:逐像素GetPixel/SetPixel的实现性能很低,仅适合功能验证,实际使用建议采用矩阵运算或者GDI+、OpenCV等图形库实现旋转逻辑。
内容的提问来源于stack exchange,提问作者Bulieme
相关产品推荐
相关产品推荐

