首页
/ Microsoft PowerToys Color Picker 深度解析:屏幕像素捕获原理、多格式转换与剪贴板历史机制

Microsoft PowerToys Color Picker 深度解析:屏幕像素捕获原理、多格式转换与剪贴板历史机制

2026-09-04 14:51:27作者:咎竹峻Karen

Color Picker 是 PowerToys 中一个轻量但工程细节丰富的全局取色工具:它以刷新率自适应的轮询机制抓取鼠标光标下的屏幕像素,将颜色实时转换为 HEX、RGB、HSL 等十余种格式,并支持按可配置格式复制剪贴板、维护本地取色历史。本文基于开发者文档 colorpicker.md 的骨架,结合 src/modules/colorPicker 目录下的源码实现,完整讲透从"屏幕取色"到"格式落地"的全链路,读完后可掌握其捕获机制、交互模型、格式系统与配置文件的实际默认值。

Color Picker 取色界面示意

功能定位:系统级取色工具

Color Picker 的核心定位是:一个 Windows 系统级(system-wide)取色工具,允许用户从屏幕任意位置拾取颜色,并以可配置的格式复制到剪贴板。它面向设计、前端开发等需要从界面"逆向"取色的场景,典型使用方式是:按下热键唤起放大镜式取色界面 → 移动光标实时预览颜色 → 点击或回车将选定颜色按偏好格式送入剪贴板,直接粘贴到设计工具或代码环境。

文档列出的完整功能清单如下,后文会逐一对应到源码:

  • 从屏幕任意像素取色;
  • 以多种格式(RGB、HEX、HSL 等)查看颜色信息;
  • 以可配置格式复制颜色值到剪贴板;
  • 取色历史,方便快速访问此前选中的颜色;
  • 键盘快捷键快速唤起与操作。

核心捕获机制:从鼠标位置到像素值

文档对取色流程给出的四步描述是:

  1. 获取鼠标位置;
  2. 在该位置创建一个 1×1 像素的矩形区域;
  3. 创建一个 Bitmap 并基于它构建 Graphics 对象;
  4. 通过 CopyFromScreen 捕获该位置的像素信息。

文档中的核心代码示例如下:

private static Color GetPixelColor(System.Windows.Point mousePosition)
{
    var rect = new Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
    using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
    {
        var g = Graphics.FromImage(bmp);
        g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);

        return bmp.GetPixel(0, 0);
    }
}

仓库中的实际实现位于 MouseInfoProvider.cs,与文档示例一致,并做了一处资源管理上的加固——Graphics 对象同样用 using 包裹,确保 GDI 句柄及时释放:

private static Color GetPixelColor(System.Windows.Point mousePosition)
{
    var rect = new Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
    using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
    {
        using (var g = Graphics.FromImage(bmp)) // Ensure Graphics object is disposed
        {
            g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
        }

        return bmp.GetPixel(0, 0);
    }
}

几个关键实现细节值得注意:

  • 位深选择:位图固定使用 PixelFormat.Format32bppArgb(32 位含 Alpha 通道),GetPixel(0, 0) 返回的是完整的 ARGB 四分量。这也是后续颜色历史以 A|R|G|B 形式落盘、以及剪贴板格式能携带 Alpha 信息的基础。
  • 坐标来源:鼠标位置通过 Win32 API GetCursorPos 获取(见 MouseInfoProvider.cs 中的 GetCursorPosition),而非 WPF 的 Mouse 事件,保证取的是物理屏幕坐标。
  • 轮询节奏与刷新率挂钩MouseInfoProvider 构造函数 中,DispatcherTimer 的间隔被设为 1000 / 主显示器刷新率 毫秒。刷新率通过 EnumDisplaySettingsDEVMODEW.dmDisplayFrequency 读取,当返回值为 0 或 1(表示硬件默认)时回退到 60 Hz(见 GetDisplayRefreshRateOrDefault)。这种设计让 120/144 Hz 高刷屏上的光标移动与颜色更新更加跟手,同时避免低刷屏上做无谓的高频屏幕捕获。
  • 变更检测UpdateMouseInfo 只有在坐标变化或取色结果变化(或用户切换了复制格式)时才对外抛出 MousePositionChanged / MouseColorChanged 事件(MouseInfoProvider.cs),下游 ViewModel 只在颜色真正改变时刷新 UI,避免无谓的界面重绘。

定时器并非常驻:只有当取色窗口显示(AppShown)时才启动轮询并挂接鼠标钩子,窗口关闭或隐藏时通过 DisposeHook 停止定时器、解挂钩子并恢复原始光标(AppStateMonitor_AppClosed / DisposeHook),空闲时零开销。

交互模型:鼠标钩子、点击动作与键盘

取色过程中,PowerToys 用 MouseHook 全局低层钩子监听四类输入:左键按下、右键释放、中键按下、滚轮滚动(MouseHook 事件挂接)。

三种可配置的鼠标点击动作

左键、中键、右键各自映射到一个可配置的 ColorPickerClickAction,处理逻辑集中在 MainViewModel.HandleMouseClickAction

点击 默认动作 行为
左键 PickColorThenEditor 将当前颜色按选定格式复制到剪贴板、写入取色历史,并打开颜色编辑器窗口
中键 PickColorAndClose 复制到剪贴板、写入历史,然后结束取色会话
右键 Close 不取色,直接结束取色会话

这三个默认值在 UserSettings.cs 中定义,且可在设置界面改写;复制内容取的是视图模型中的 ColorText,即已经按 CopiedColorRepresentation 格式渲染好的字符串。

键盘操作

从源码注释可以看到键盘设计策略(MainViewModel 构造函数):

  • 作为 PowerToys 模块运行时,唤起取色走的是 runner 的全局键盘钩子——用户按下激活热键后,runner 通过命名事件 ShowColorPickerSharedEvent 唤醒取色进程,取色进程自身不维护常驻键盘钩子;
  • 独立运行(detached)时,进程内才启动本地的低层键盘钩子 KeyboardMonitor
  • 取色会话期间,AppStateHandler 负责捕获 Esc(取消)、空格/回车(确认取色)、方向键等操作,会话结束即释放钩子。注释明确说明这比维护一个常驻的低层键盘钩子开销小得多。

放大镜视图与滚轮缩放

文档描述的用户体验是:激活后显示光标周围的放大视图以便精确定位取色点。实现上由 ZoomWindowZoomWindowHelper 完成,放大窗口上叠加大网格线——网格效果由着色器 GridShader.fx(编译产物 GridShader.cso)实现。滚轮事件经 OnMouseWheel 转发为 ZoomWindowHelper.Zoom,以光标位置为中心放大或缩小(MainViewModel.cs)。此外,若启用了光标替换设置,取色期间会切换为专用的取色光标(CursorManager.SetColorPickerCursor),结束时再恢复。

颜色格式系统:16 种表示法与可定制模板

文档提到"以各种格式(RGB、HEX、HSL 等)查看颜色",实际仓库支持的格式远不止这些。ColorRepresentationHelper.cs 完整实现了以下表示法:

格式 输出示例形态 实现要点
HEX FF0000 各分量小写十六进制(ColorToHex
HEX Int 0xFFFF0000 0xFF Alpha 前缀的整型十六进制
RGB rgb(255, 0, 0) 十进制三元组
HSL hsl(0, 100%, 50%) 色相取整,饱和度/明度百分比取整
HSB hsb(0, 100%, 100%) 同 HSL 结构,亮度定义为 Value
HSV hsv(0, 100%, 100%) HSB 的等价命名
HSI hsi(0, 100%, 50%) 含强度分量的变体
HWB hwb(0, 0%, 0%) 色相 + 白度 + 黑度
CMYK cmyk(0%, 100%, 100%, 0%) 四色印刷模型,百分比取整
NCol 4, 0%, 0% 自然色模型(色相名 + 白/黑百分比)
CIEXYZ XYZ(x, y, z) CIE 标准色度,缩放 100 倍保留 4 位小数
CIELAB CIELab(L, a, b) CIE LAB,保留 2 位小数
Oklab / Oklch oklab(...) / oklch(...) 感知均匀的现代色彩空间
VEC4 (1f, 0f, 0f, 1f) 归一化浮点四元组,各分量保留 2 位小数
Decimal 单个整数 R*65536 + G*256 + B 的打包整数值

所有颜色分量到目标色空间的换算统一委托给设置库中的 ColorFormatHelper(各 ConvertTo*Color 方法),而字符串渲染统一使用 InvariantCulture 文化,保证剪贴板内容在不同地区设置的机器上格式一致(如小数点始终是 .)。

两个值得展开的机制:

1. 每种格式都是可编辑的模板字符串。 用户看到的"格式"背后是一组模板(如 hsl({h}, {s}%, {l}%)),在设置界面中可以直接改写模板以适配目标工具(CSS、着色器、CAD 等)的语法。取色时先做数值占位替换,再对色名占位符做本地化替换(GetStringRepresentation)。

2. 自然色名(ShowColorName)。 开启 ShowColorName 后,取色窗口会显示该颜色最接近的自然色名。实现方式是 ColorNameHelper 将颜色映射到 40 余个颜色标识(如 TEXT_COLOR_CORALTEXT_COLOR_INDIGO),再由 GetColorNameFromColorIdentifier 换成对应语言的本地化资源字符串。

默认显示哪些格式

各格式"是否显示"的默认值由设置库 ColorPickerProperties 的构造函数决定,VisibleColorFormats 字典中每个条目是 KeyValuePair<bool, string>——第一个分量控制界面是否显示该格式,第二个分量是它的模板字符串:

  • 默认显示:HEX、RGB、HSL(三项为 true);
  • 默认隐藏:HSV、CMYK、HSB、HSI、HWB、NCol、CIEXYZ、CIELAB、Oklab、Oklch、VEC4、Decimal、HEX Int(均为 false)。

剪贴板复制与取色历史

剪贴板内容

复制的内容由 CopiedColorRepresentation(默认 HEX)决定:MainViewModel 中每次颜色变化时都会用当前格式重新生成 ColorTextSetColorDetails),点击确认时通过 ClipboardHelper.CopyToClipboard(ColorText) 写剪贴板。也就是说,剪贴板里永远是"用户选定的那个格式"的字符串,而不是裸的 RGB 数字。

历史存储结构

取色历史的落盘逻辑在 UpdateColorHistory

  • 颜色以 A|R|G|B 四段竖线分隔的字符串存储(见 GetColorString),保留 Alpha;
  • 若该颜色已在历史中,移动到最前(去重);否则插入索引 0;
  • 超过 ColorHistoryLimit(默认 20 条)时从尾部截断。

序列化由 UserSettings 完成:任何一次 ColorHistory 集合变更都会把整个列表以缩进 JSON 写入独立的 colorHistory.json,文件路径为 %LocalAppData%\Microsoft\PowerToys\ColorPicker\colorHistory.json(注意历史不在 settings.json 里,ColorPickerProperties 中保留了 colorhistory 字段但源码注释明确标注"已弃用,历史单独保存在 colorHistory.json")。加载侧有防御:文件缺失或损坏时记录日志并回退为空历史,兼容旧版本把历史内嵌在 settings.json 的情况(LoadSettingsFromJson)。

对外服务接口:ColorPickerService

Color Picker 同时暴露了可编程的模块服务。IColorPickerService 定义两个能力:

  • OpenPickerAsync:唤起取色器。实现上就是 SignalEventAsync 触发 ShowColorPickerSharedEvent 命名事件(ColorPickerService.cs),取色 UI 进程端的 NativeEventWaiter 监听该事件并启动用户会话(MainViewModel.cs)。这条"命名事件解耦"通道让 runner / 设置界面 / CLI 无需直接持有取色进程句柄即可驱动它;
  • GetSavedColorsAsync:读取 colorHistory.json,按 A|R|G|B 解析每条记录(TryParseArgb),再按用户当前可见的 VisibleColorFormats 为每个历史颜色批量渲染各格式值(HEX 会自动补 # 前缀),返回 SavedColor 列表供其他组件消费(GetSavedColorsAsync)。

配置项与默认值速查

Color Picker 的持久化配置为 %LocalAppData%\Microsoft\PowerToys\settings\ColorPicker\settings.json(模块名 ColorPicker,当前结构版本 "2.1",带 v1 结构自动升级逻辑,见 ColorPickerSettings.csUpgradeSettings)。结合 ColorPickerPropertiesUserSettings 的源码默认值,关键配置如下:

属性(JSON 名) 含义 默认值
ActivationShortcut 唤起取色的全局热键 Ctrl + Break(虚拟键码 0x43,即 Break/Pause 键)
copiedcolorrepresentation 复制到剪贴板的格式 HEX
activationaction 触发时行为 OpenColorPicker(打开取色器)
primaryclickaction 左键动作 PickColorThenEditor(取色并进编辑器)
middleclickaction 中键动作 PickColorAndClose(取色并关闭)
secondaryclickaction 右键动作 Close(取消)
colorhistorylimit 历史条数上限 20
changecursor 取色时是否替换为专用光标 设置库默认 false;应用内 SettingItem 回退值为 true,生效值以实际 settings.json 为准
showcolorname 是否显示自然色名 false
visiblecolorformats 各格式的"显示开关 + 模板"字典 HEX/RGB/HSL 显示,其余 12 种隐藏(见上节)

运行时的配置同步机制值得一提:UserSettings 通过文件监视器监听 settings.json 变化,变更后以 300 ms 节流延迟重新加载,避免多进程并发写导致的"文件占用"异常,并带最多 5 次、间隔 500 ms 的读取重试(UserSettings 构造函数与 LoadSettingsFromJson)。

小结

Color Picker 的实现展示了 PowerToys 模块的典型工程范式:

  • 捕获层:1×1 CopyFromScreen 截屏取色,配合"刷新率自适应轮询 + 变更检测",在保证实时性的同时把 GDI 开销压到最低;
  • 交互层:会话期间才挂低层鼠标/键盘钩子,三种鼠标动作全部可配置,滚轮驱动网格着色器放大镜;
  • 格式层:16 种颜色表示法 + 可编辑模板 + 本地化色名,覆盖 CSS、印刷、着色器、色彩科学等多类下游场景;
  • 数据层A|R|G|B 字符串化的独立历史文件 + 带节流与重试的设置热加载 + 命名事件驱动的模块服务接口,使其既能作为 PowerToys 模块被 runner 全局唤起,也能独立运行。

如需继续深入,可优先阅读 MouseInfoProvider.cs(捕获与输入)、MainViewModel.cs(会话与动作路由)、ColorRepresentationHelper.cs(格式渲染)、ColorPickerProperties.cs(配置模型)四个文件。

登录后查看全文
热门项目推荐
相关项目推荐