You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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释放,不然长时间运行脚本会出现内存泄漏问题。

额外排查点

如果还是有轻微失真,可以检查:

  1. 是否有其他窗口(比如系统菜单栏、Dock)影响?如果需要排除这些元素,可以把kCGWindowListOptionOnScreenOnly换成kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements。
  2. 确保你的Pillow是最新版本,旧版本在处理图像buffer时可能有bug,执行pip install --upgrade pillow更新即可。

备注:内容来源于stack exchange,提问作者Jack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 19:28:10