如何在macOS上通过Python结合Core Graphics(CGWindowListCreateImage)与PIL无失真且隐秘地截取屏幕
如何在macOS上通过Python结合Core Graphics(CGWindowListCreateImage)与PIL无失真且隐秘地截取屏幕
Hey Jack,我之前也踩过用Core Graphics在macOS上截图失真的坑,尤其是Retina屏幕下这个问题特别突出,咱们一步步来解决:
问题根源:Retina屏幕的像素密度适配
macOS的Retina屏幕采用了高像素密度(通常是2x,部分设备更高),而CGWindowListCreateImage默认使用逻辑坐标(也就是你在系统设置里看到的屏幕尺寸),不是物理像素。直接用逻辑坐标截图的话,图像会被拉伸压缩,导致文字和元素模糊失真。
解决方案:适配像素密度 + 正确处理图像格式
下面是经过验证的完整代码,我会把关键步骤拆解开讲:
import Quartz from PIL import Image import numpy as np def stealth_screenshot(): # 获取主显示器ID main_display = Quartz.CGDisplayGetMainDisplay() # 获取屏幕的逻辑尺寸(系统设置里的"点"单位) display_bounds = Quartz.CGDisplayBounds(main_display) logical_width = display_bounds.size.width logical_height = display_bounds.size.height # 计算屏幕缩放因子(物理像素 / 逻辑点) display_mode = Quartz.CGDisplayCopyDisplayMode(main_display) pixel_width = Quartz.CGDisplayModeGetPixelWidth(display_mode) pixel_height = Quartz.CGDisplayModeGetPixelHeight(display_mode) scale_factor = pixel_width / logical_width Quartz.CFRelease(display_mode) # 手动释放Core Graphics资源,避免内存泄漏 # 将逻辑坐标转换为物理像素范围,确保截图覆盖真实屏幕像素 screenshot_bounds = Quartz.CGRectMake(0, 0, logical_width * scale_factor, logical_height * scale_factor) # 创建隐秘截图:只截取当前可见屏幕,不触发系统通知 cg_image = Quartz.CGWindowListCreateImage( screenshot_bounds, Quartz.kCGWindowListOptionOnScreenOnly, # 只取可见窗口 Quartz.kCGNullWindowID, # 截取整个屏幕 Quartz.kCGWindowImageDefault ) if not cg_image: raise RuntimeError("截图失败,检查权限或系统设置") # 提取CGImage的关键参数 width = Quartz.CGImageGetWidth(cg_image) height = Quartz.CGImageGetHeight(cg_image) bytes_per_row = Quartz.CGImageGetBytesPerRow(cg_image) # 提取像素数据并转换为PIL可识别的格式 data_provider = Quartz.CGImageGetDataProvider(cg_image) data = Quartz.CGDataProviderCopyData(data_provider) # 释放不再需要的CG资源 Quartz.CFRelease(data_provider) Quartz.CFRelease(cg_image) # Core Graphics默认是BGRA格式,PIL需要RGBA,这里做通道转换 np_data = np.frombuffer(data, dtype=np.uint8) image = Image.frombuffer("RGBA", (width, height), np_data, "raw", "BGRA", bytes_per_row, 1) # 如果不需要透明通道,可以转成RGB格式 # image = image.convert("RGB") return image # 测试使用 if __name__ == "__main__": screenshot = stealth_screenshot() screenshot.save("隐秘截图.png") # 也可以直接查看截图 # screenshot.show()
关键细节说明
- 缩放因子适配:这是解决失真的核心,通过对比物理像素和逻辑点的比例,让截图覆盖真实的屏幕像素,避免拉伸。
- 图像格式转换:CGImage在内存中是BGRA字节顺序,而PIL默认用RGBA,直接转换会导致颜色错乱和扭曲,所以
frombuffer时指定格式转换。 - 隐秘性保障:用
kCGWindowListOptionOnScreenOnly只截取当前可见内容,且不会像系统自带的screencapture命令那样弹出截图通知,全程后台执行。 - 资源释放:Core Graphics的对象需要手动用
CFRelease释放,不然长时间运行脚本会出现内存泄漏问题。
额外排查点
如果还是有轻微失真,可以检查:
- 是否有其他窗口(比如系统菜单栏、Dock)影响?如果需要排除这些元素,可以把
kCGWindowListOptionOnScreenOnly换成kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements。 - 确保你的Pillow是最新版本,旧版本在处理图像buffer时可能有bug,执行
pip install --upgrade pillow更新即可。
备注:内容来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

