Skip to content

fix: 取色器多屏取色与实时采样(DXGI Desktop Duplication) - #20

Open
snwjas wants to merge 2 commits into
ZToolsCenter:mainfrom
snwjas:fix/color-picker-multi-monitor
Open

snwjas wants to merge 2 commits into
ZToolsCenter:mainfrom
snwjas:fix/color-picker-multi-monitor

Conversation

@snwjas

@snwjas snwjas commented Oct 4, 2026 •

Copy link
Copy Markdown

问题

  1. 取色时 ZTools 自身界面会被取到,挡住后面的内容
  2. 双显示器下在副屏取不到颜色(恒返回 #FFFFFF)

原因

ColorPickerThreadFunc() 在启动时对屏幕做一次 BitBlt 快照,缓冲区按主屏尺寸 SM_CXSCREEN × SM_CYSCREEN 分配、原点固定在 (0, 0);而后续 CapturePixelsAroundCursor() 用 GetCursorPos() 返回的虚拟桌面坐标去索引这块缓冲区。

  • 副屏与主屏不重叠时,副屏坐标落在缓冲区之外,GetPixel 返回 CLR_INVALID → 表现为 #FFFFFF
  • 快照只取一次,画面停在取色器启动那一刻,宿主界面隐藏之前的内容被永久留在结果里

方案

改用 DXGI Desktop Duplication 逐帧实时采样:

  • 每帧 AcquireNextFrame 取桌面帧,按鼠标当前所在显示器建立 duplication
  • 9×9 采样区用 CopySubresourceRegion 拷到 CPU 可读的 staging 纹理后读取,不做整屏回读
  • 帧预算 33 ms;DXGI_ERROR_WAIT_TIMEOUT 时沿用上一帧
  • 显示器切换 / DXGI_ERROR_ACCESS_LOST 时全量释放并重建 duplication
  • SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE) 排除取色器自身窗口

DDA 的桌面帧不包含鼠标指针,采样结果因此不带光标像素。

重绘改由帧泵循环驱动(画面更新或光标移动时重绘,替代原 30 FPS WM_TIMER):桌面静止时线程阻塞在 AcquireNextFrame 的帧预算上,不空转;首帧 / 跨屏 / 失效重建被同一阻塞语义自然覆盖,无特判。

保留快照回退路径(DDA 初始化失败时启用,覆盖 Win7 / RDP / 旋转屏),并把快照的尺寸与原点一并改为虚拟桌面(SM_*VIRTUALSCREEN),使副屏在回退模式下同样可用。会话启动时决定走哪条路径,中途 DDA 失效不会切回快照。

顺带修复:

  • 放大镜窗口位置按光标所在显示器(MonitorFromPoint)钳位。旧实现按主屏尺寸 SM_CXSCREEN 计算边界,光标在左侧/上方副屏时窗口会被钳回主屏边界瞬移走
  • 放大镜 WM_PAINT 每帧创建/销毁 81 组画刷画笔,改为 stock DC_BRUSH 换色填充 + 一遍网格线(绘制结果不变)
  • 放大镜网格右/下边框缺失与准心黑框右下不对称(GDI 裁剪/宽笔触渲染细节,旧实现即存在,一并修复)

binding.gyp 新增 d3d11.lib / dxgi.lib。

实现注记

帧副本纹理必须用最小化 desc 创建,不能沿用桌面纹理的 GetDesc():继承其 MiscFlags 等附加属性后,CopyResource 会静默产出全黑帧(独立进程正常、宿主进程内必现),取色恒为 #000000,且无任何 API 报错。

平台范围

Windows。macOS 侧已核对:其取色器为逐帧 CGWindowListCreateImage 实时采样、走 CG 全局坐标,不存在上述两类问题,未做改动。

验收

新增 test/verify-color-picker.js:

npx electron test/electron-host.cjs test/verify-color-picker.js
  • 副屏取色:取与主屏不重叠的显示器中心点,改前恒为 #FFFFFF,改后取到实际色值
  • 实时取样:取色器启动后重绘屏幕内容,结果必须跟随变化(快照实现下不会变)

脚本在运行时探测显示器布局,无可用副屏时跳过对应断言并说明原因;主屏参照色块自检失败(锁屏 / 关屏)时以退出码 2 中止,避免给出无意义结果。

实测

物理替换安装目录下的 ztools_native.node 后,本机双屏环境实测:副屏取色正常、实时取样正常、放大镜无闪烁无卡顿。

snwjas added 2 commits October 4, 2026 10:13
旧实现启动时对主屏做一次 BitBlt 快照(SM_CXSCREEN 尺寸 + 原点 (0,0)),
导致两个问题:副屏坐标落在缓冲区外,取色恒返回 #FFFFFF;快照停在
启动那一刻,宿主界面来不及隐藏的内容被采进结果。

- 改用 DXGI Desktop Duplication 逐帧实时采样:GPU 路径不碰 GDI 回读
  (实测 BitBlt 回读闪光标、逐点 GetPixel 卡顿),桌面帧天然不含光标
- 9x9 采样区 CopySubresourceRegion 到 staging 后读取,不做整屏回读;
  帧预算 33ms,WAIT_TIMEOUT 沿用上一帧,跨屏/ACCESS_LOST 自动重建
- 放大镜重绘改由帧泵循环驱动(画面更新或光标移动),替代 30FPS
  WM_TIMER;桌面静止时阻塞在 AcquireNextFrame 上,不空转
- 保留快照回退(Win7/RDP/旋转屏),尺寸与原点改为虚拟桌面
  (SM_*VIRTUALSCREEN),回退模式下副屏同样可用
- 放大镜窗口按光标所在显示器钳位(旧实现按主屏尺寸算边界,
  光标在左侧/上方副屏时窗口瞬移)
- WM_PAINT 每帧创建/销毁 81 组画刷画笔改为 stock DC_BRUSH + 一遍网格线
- 新增验收脚本 test/verify-color-picker.js(Electron 宿主,运行时
  探测副屏布局,断言副屏取色与实时取样)
- binding.gyp 新增 d3d11.lib / dxgi.lib

macOS 侧核对无需改动:取色器为逐帧 CGWindowListCreateImage 实时采样、
走 CG 全局坐标,不存在上述问题。
均为 GDI 渲染细节问题(旧实现即存在):

- 网格最右/最下一条线画在 x/y=144,而客户区只有 144px(列 0~143),
  整条线落在客户区外被裁掉,右/下边框消失。钳到 143 绘制
- 准心 2px 黑框:GDI 宽笔触居中渲染,被相邻 1px 白框盖掉内侧一半后
  右/下只剩一丝。改为两条 1px 描边叠加,四侧厚度一致
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant