抠图新神器实测:魔棒、套索、一键主体识别,Compositor 的选区能力藏不住了
【免费下载链接】CompositorThe Photoshop alternative for Mac项目地址: https://gitcode.com/gh_mirrors/compos/Compositor
抠图是图像编辑里最"劝退"的一步:发丝、半透明边缘、复杂背景,手动一点一点描边能让人崩溃。而 Photoshop 之所以成为行业标准,很大程度上是因为它把"选区"这件小事做到了极致——魔棒、套索、选框、色彩范围、主体识别,一整套工具链配合快捷键,能覆盖从粗暴到精细的全部场景。如今,macOS 平台的开源图像编辑器 Compositor 把这套能力几乎完整地复刻了出来,而且是从 Swift + C 混合架构的底层开始实现的,选区是路径而非位图、像素运算交给 C 内核、主体识别直接调用 Apple Vision 模型。
本文不堆功能列表,而是深入仓库源码,把 Compositor 选区能力的真实实现拆开来看:手势与快捷键背后的状态机、一键主体识别背后的 Vision 调用链、扩展收缩羽化背后的几何运算,以及选区内填充复制的像素级玩法。
选区全家桶:魔棒、套索、选框的手势与快捷键
Compositor 的选区工具和 Photoshop 保持同一套快捷键体系:M是选框(Marquee)、L是套索(Lasso)、W是魔棒(Magic),三个工具在 EditorSession.swift 中被归为一类"选区工具":
var isSelectionTool: Bool { self == .marquee || self == .lasso || self == .wand }类 Photoshop 的地方不止快捷键。选区模式用枚举 Selection.swift 中的SelectionMode表示——replace(新建)、add(加选)、subtract(减选),而按住修饰键时有一套和 PS 完全一致的优先级规则:
/// Shift adds, Option (with or without Shift) subtracts; otherwise the options-bar mode. func selectionMode(shift: Bool, option: Bool) -> SelectionMode { option ? .subtract : shift ? .add : selectionModeChoice }即Shift 加选、Option 减选,Option 优先级高于 Shift(按住 Option 时无论是否按 Shift 都是减选)。选区轮廓和既有选区之间做的是真正的路径布尔运算——applySelection中通过union/subtracting/intersection组合路径,并用 winding 规则裁剪到画布范围内。
再看每个工具的细节:
- 选框(Marquee):矩形与椭圆两种形态,
Tab切换(toggleMarqueeKind)。拖拽时支持两个经典修饰行为——按住 Shift 画正方形/正圆,按住 Option 从锚点中心向外扩展。这部分逻辑抽象在DragBox.rect中,选框和 Shape 工具共用:
if square { let side = max(abs(dx), abs(dy)) dx = dx < 0 ? -side : side dy = dy < 0 ? -side : side } return fromCenter ? CGRect(x: anchor.x - abs(dx), y: anchor.y - abs(dy), width: abs(dx) * 2, height: abs(dy) * 2) : ...套索(Lasso):自由手绘(Freehand)与多边形(Polygonal)两种模式。多边形套索带"橡皮筋"式尾端光标(
LassoDraft.cursor),Esc或 Delete 可以逐点回退(removeLastLassoPoint),连续点距离小于 0.25 像素会被丢弃避免冗余(extendLasso)。收尾时套索轮廓addLines+closeSubpath闭合,新模式下点击未圈中任何内容会执行"取消选区"——注释明确写着 "as in Photoshop"。移动与微调:在选区内部拖拽只会移动轮廓(
beginSelectionMove),不动像素;方向键微调每次 1 像素,按住 Shift 每次 10 像素,且每次按键独立成为一个撤销步骤。整个选区操作都落在beginEdit/endEdit的历史记录体系里,随时可撤销。
另外还有几个容易被忽略的进阶入口:⌘A全选、⌘D取消选区、⌘⇧I反选(反选的结果若为空,会得到一个"显式空选区"而非无选区——这在后续编辑中语义完全不同,空选区意味着"什么都不碰",无选区在 Photoshop 语义里则意味着"作用于全部",Selection.swift 的注释专门强调了这一点)。最有意思的是⌘+点击图层缩略图:直接将该图层不透明度 ≥50% 的像素描成选区(loadLayerSelection),⌘+点击蒙版缩略图则把蒙版的黑色(隐藏)区域变成选区(loadMaskSelection)——这是把"图层像素"当作选区来源的快捷通道。
⌃⌘A 一键主体识别背后的 Vision 模型
选题大纲里最值得深挖的就是一键主体识别。在 Compositor 中,Select → Subject菜单绑定的是⌃⌘A(CompositorApp.swift 中configuredKeyboardShortcut("a", modifiers: [.command, .option])),一键把画面里最显著的前景主体描成选区。
它的实现路径非常清晰,位于 SubjectRemoval.swift:
func selectSubject(mode: SelectionMode = .replace) async { ... drawLiveComposite(document, in: context) // 1. 取画布当前合成结果 guard let shown = context.makeImage() else { return } ... let found = await Task.detached(priority: .userInitiated) { ... } // 2. 后台线程跑模型 ... guard let traced = MaskTracing.whitePixels(in: mask) else { ... } // 3. 描出白色主体轮廓 ... applySelection(outline, mode: mode, name: "Select Subject") // 4. 落成路径选区 }四步走:先把所有可见图层合成成一张图(尊重图层的透明度、混合模式、蒙版——所见即所得),然后在后台线程调用 Vision 的VNGenerateForegroundInstanceMaskRequest拿到主体实例掩膜,再通过MaskTracing.whitePixels把掩膜的白色区域追踪成矢量轮廓,最后作为普通路径选区写回文档。整个模型推理不阻塞主线程,一个撤销步骤搞定。
同样的 Vision 模型还被"魔棒工具的 Object 模式"复用。魔棒工具在W键下按Tab可在Wand(按颜色选)和Object(按主体选)两种模式间切换。Object 模式点的不是"颜色",而是"点中的主体"——ObjectSelection.swift 中的VNGenerateForegroundInstanceMaskRequest会先解析点击位置落到哪个实例上(instanceIndex把文档坐标映射到 Vision 低分辨率实例掩膜的像素,读出实例编号),再单独为这个实例生成掩膜:
let request = VNGenerateForegroundInstanceMaskRequest() try handler.perform([request]) guard let observation = request.results?.first else { return nil } guard let instance = try instanceIndex(in: observation.instanceMask, at: point, ...), observation.allInstances.contains(instance) else { return nil } let coarse = try observation.generateMask(forInstances: IndexSet(integer: instance))Vision 的实例掩膜是低分辨率的,直接放大会有锯齿,所以 ObjectSelection 用 Core Image 的CIEdgePreserveUpsampleFilter做保边放大——以原图作为引导(guide),把粗糙掩膜贴合到真实边缘上,再二值化。这一招在发丝、物体与背景颜色接近的场景里尤其关键,比单纯双线性放大锐利得多:
filter.setValue(guideImage, forKey: kCIInputImageKey) filter.setValue(coarse, forKey: "inputSmallImage") filter.setValue(5, forKey: "inputSpatialSigma") filter.setValue(0.15, forKey: "inputLumaSigma")之后还有一套可选的形态学精修:edgeOffset正值逐像素腐蚀(向主体内部收缩),负值膨胀(向外扩展),最多 10 步;最终轮廓再经过smoothed处理去掉二进制掩膜追踪产生的单像素阶梯。值得一提的还有约束:Vision 主体实例掩膜接口要求macOS 14 及以上,更早的系统会明确报错 "Object Selection requires macOS 14 or later.",不会静默降级。
社区里对 Compositor 选区能力的讨论,也基本都聚焦在这一套"魔棒按颜色/主体智能选择 + ⌃⌘A 一键主体识别"的组合上——实际上这正是它与 GIMP、Photopea 拉开体验差距的地方:主体识别不是自己训练的模型,而是直接站在 Apple Vision 的肩膀上,把系统级能力嫁接到开源编辑器里。
扩展收缩与羽化精修实战
抠图很少一次到位,选区需要反复"精修边缘"。Compositor 在Select菜单里提供了Expand…、Contract…、Feather…三项(CompositorApp.swift),选项栏里也有对应的 Expand / Contract / Feather 控件(LassoControls.swift),数值输入支持数字滚动微调(scrub)。
看实现会惊叹于它的简洁与正确。扩展/收缩用的是"条带"法:以当前轮廓为中线,strokingWithWidth描出一条|delta| × 2宽的圆角条带(lineCap: .round、lineJoin: .round),扩展就是原路径union条带再裁剪到画布,收缩就是原路径subtracting条带——这一步用 CoreGraphics 的路径描边直接实现了 Photoshop "Expand Selection 圆角外扩"的效果:
let band = current.path.copy(strokingWithWidth: abs(delta) * 2, lineCap: .round, lineJoin: .round, miterLimit: 10) let result = delta > 0 ? current.path.union(band, using: .winding) .intersection(CGPath(rect: CGRect(origin: .zero, size: document.size)), using: .winding) : current.path.subtracting(band, using: .winding)单次扩展/收缩上限 500 像素,且支持叠加(每次操作在上次结果上继续)。
羽化则更讲究。DocumentSelection中羽化值feather不是立即烧录进位图,而是"参数化"地保存在选区结构体里,直到需要像素时才通过coverage生成灰度覆盖图:白色代表选中,羽化边缘用高斯模糊(sigma = feather/2)模拟半透明过渡,最终以裁剪蒙版的形式参与一切像素编辑——这保证了羽化后的边缘是真实的"渐变过渡"而非硬锯齿。有意思的是羽化的叠加规则:连续两次羽化不是简单的相加,而是平方和的平方根((f² + a²).sqrt()),源码注释解释得很直白:"Two soft edges together spread a little less than their sum, as blurs do"——和模糊叠加的行为一致,符合直觉。
配合选项栏的Anti-alias(抗锯齿)开关,用户可以在"圆润的矢量轮廓"和"严格的像素边界"之间切换。这套"路径选区 + 参数化羽化 + 按需栅格化"的设计,让选区在整个文档生命周期里保持非破坏性——任何一步都可通过历史记录回退,且选区本身永远是干净的 CGPath。
选区内填充复制的进阶玩法
选区是编辑的"约束域",真正干活的是选区内的操作。Compositor 在这层同样做得非常完整:
填色与清除:fillSelection用前景/背景色填充选区,无选区时填整个图层,空选区则什么都不填;如果目标图层是文字层(liveText),填充会直接改变文字颜色而不是画上去——文字保持可编辑。Delete键在选中状态下执行clearSelectedPixels,像素变透明;如果目标选中了蒙版,则等价于用背景色填充蒙版(黑/白),实现显示/隐藏。
移动与复制选区像素:⌘+拖拽移动选区内像素(beginPixelMove先把选区像素"提起"成一块独立栅格PixelMove,原位置打出"洞",拖拽过程实时预览),⌘⌥+拖拽则是复制(duplicate),提交时像素和移动后的选区轮廓作为同一个"Move Pixels / Duplicate Pixels"撤销步骤落盘。GPU 画布下移动预览不重建贴图,只有提交时才重建——性能设计也写在了注释里。
复制三件套:⌘C复制选区内像素(选区存在时),且同时以 PNG 写入系统剪贴板,可以粘到其他应用;⇧⌘C是合并复制——把选区覆盖范围内所有可见图层按混合模式、不透明度、蒙版合成后的结果复制出来(renderMergedPixels,对 Color Burn / Color Dodge 这类依赖背景的混合模式专门做了先合成再裁剪的处理);⌘J(Layer via Copy)则把选区像素直接变成原位新图层。
内容感知填充:选区框住的区域可以直接智能填充(Content-Aware Fill)。底层 ContentFill.c 是一个典型的补丁合成算法——"Propagate coherent source offsets, then refine with randomized patch search",即先沿边界传播连贯的源偏移,再用随机补丁搜索细化,C 内核一次性处理整幅图像。它把选区外的未选、不透明像素当作唯一素材源,如果选区太大、周围没有足够参考像素,会明确报错:"Not enough unselected, opaque image pixels to synthesize a fill."——这种对"素材不足"的诚实提示,比静默输出糊成一团的填充体验专业得多。
色彩范围:Select → Color Range…打开实时预览面板(ColorRangeSelection.swift),点击画布取样颜色后,全图中所有相近色像素都会被选中(不要求连通),fuzziness 默认 40、范围 0–200,Shift点击追加颜色、Option点击排除颜色,面板内实时刷新黑白预览,确认后才作为一步撤销提交。这套交互几乎就是 Photoshop Color Range 的复刻。
最后补一个贯穿始终的设计:所有像素编辑——填充、清除、反选、内容感知填充——都通过SelectionClip把选区的灰度覆盖图作为裁剪蒙版应用到渲染上下文(Selection.swift 的apply(to:)),软边缘像素天然获得部分透明,硬边缘则是 0/1 二值。也就是说,"选区约束"不是事后叠加的 alpha 运算,而是从根上参与每一次像素写入,这与 Photoshop 的选区语义在数学上是等价的。
结语
Compositor 的选区体系,价值不在"又一个 PS 模仿者",而在于它用一套极其干净的架构把选区能力做透了:路径化选区 + 参数化羽化 + 按需栅格化的数据模型,Shift/Option 修饰符 + 布尔运算 + 历史记录的操作模型,C 内核做像素级重活 + Swift 层做状态管理的性能模型,以及直接复用系统 Vision 主体识别能力的 AI 捷径。对想学习图像编辑器架构的开发者来说,Selection.swift、MagicWand.swift 与 ObjectSelection.swift 三份文件几乎就是一部"选区从模型到实现"的教科书;对普通用户来说,W点一下、⌃⌘A一键、⇧⌘C合并复制、Delete 清空——这套肌肉记忆一旦形成,抠图这件事,在 macOS 上终于可以不依赖 Adobe 了。
【免费下载链接】CompositorThe Photoshop alternative for Mac项目地址: https://gitcode.com/gh_mirrors/compos/Compositor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考