Microsoft PowerToys Color Picker 深度解析:屏幕像素捕获原理、多格式转换与剪贴板历史机制
Color Picker 是 PowerToys 中一个轻量但工程细节丰富的全局取色工具:它以刷新率自适应的轮询机制抓取鼠标光标下的屏幕像素,将颜色实时转换为 HEX、RGB、HSL 等十余种格式,并支持按可配置格式复制剪贴板、维护本地取色历史。本文基于开发者文档 colorpicker.md 的骨架,结合 src/modules/colorPicker 目录下的源码实现,完整讲透从"屏幕取色"到"格式落地"的全链路,读完后可掌握其捕获机制、交互模型、格式系统与配置文件的实际默认值。
功能定位:系统级取色工具
Color Picker 的核心定位是:一个 Windows 系统级(system-wide)取色工具,允许用户从屏幕任意位置拾取颜色,并以可配置的格式复制到剪贴板。它面向设计、前端开发等需要从界面"逆向"取色的场景,典型使用方式是:按下热键唤起放大镜式取色界面 → 移动光标实时预览颜色 → 点击或回车将选定颜色按偏好格式送入剪贴板,直接粘贴到设计工具或代码环境。
文档列出的完整功能清单如下,后文会逐一对应到源码:
- 从屏幕任意像素取色;
- 以多种格式(RGB、HEX、HSL 等)查看颜色信息;
- 以可配置格式复制颜色值到剪贴板;
- 取色历史,方便快速访问此前选中的颜色;
- 键盘快捷键快速唤起与操作。
核心捕获机制:从鼠标位置到像素值
文档对取色流程给出的四步描述是:
- 获取鼠标位置;
- 在该位置创建一个 1×1 像素的矩形区域;
- 创建一个 Bitmap 并基于它构建 Graphics 对象;
- 通过
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 / 主显示器刷新率毫秒。刷新率通过EnumDisplaySettings从DEVMODEW.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(取消)、空格/回车(确认取色)、方向键等操作,会话结束即释放钩子。注释明确说明这比维护一个常驻的低层键盘钩子开销小得多。
放大镜视图与滚轮缩放
文档描述的用户体验是:激活后显示光标周围的放大视图以便精确定位取色点。实现上由 ZoomWindow 与 ZoomWindowHelper 完成,放大窗口上叠加大网格线——网格效果由着色器 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_CORAL、TEXT_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 中每次颜色变化时都会用当前格式重新生成 ColorText(SetColorDetails),点击确认时通过 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.cs 的 UpgradeSettings)。结合 ColorPickerProperties 与 UserSettings 的源码默认值,关键配置如下:
| 属性(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(配置模型)四个文件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
