From 28874bbfad56cc31d75fab1aa7be37c6b45fc37d Mon Sep 17 00:00:00 2001 From: Valentin Klein Date: Sun, 11 Oct 2026 21:16:45 +0200 Subject: [PATCH] Select and Mask: Output To New Document, with or without a layer mask (#2998) (#3007) * Select and Mask: Output To New Document, with or without a layer mask (#2998) select.refineEdge takes output newDocument / newDocumentWithMask: a new document like the source (size, mode, depth, resolution, profile) with the active layer cut out by the refined edge, or with the matte as a layer mask. The source document is left untouched. The dialog's Output choice lists all six targets, translated in every catalog. Co-Authored-By: Claude Opus 5.5 * i18n: drop the duplicate pt-br New Layer row (main already has it) Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 Co-authored-by: Brandon Thomas --- crates/engine/src/smartselect_cmds.rs | 76 ++++++++++++++++++++++++--- crates/ui-egui/src/filter_dialog.rs | 15 ++++++ crates/ui-egui/src/i18n/cs.tsv | 3 ++ crates/ui-egui/src/i18n/de.tsv | 3 ++ crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 3 ++ crates/ui-egui/src/i18n/fr.tsv | 3 ++ crates/ui-egui/src/i18n/id.tsv | 3 ++ crates/ui-egui/src/i18n/it.tsv | 3 ++ crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ko.tsv | 2 + crates/ui-egui/src/i18n/nl.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 3 ++ crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 3 ++ crates/ui-egui/src/i18n/zh-hant.tsv | 3 ++ docs/gaps.md | 2 +- docs/scorecard.md | 2 +- scorecard/tools.toml | 2 +- 21 files changed, 127 insertions(+), 11 deletions(-) diff --git a/crates/engine/src/smartselect_cmds.rs b/crates/engine/src/smartselect_cmds.rs index 5a1d87ad..6c15c6be 100644 --- a/crates/engine/src/smartselect_cmds.rs +++ b/crates/engine/src/smartselect_cmds.rs @@ -259,9 +259,10 @@ fn refine_strokes(p: &Value) -> Result> { } /// Select and Mask: refines the selection edge, then outputs to the selection, a layer mask on -/// the active layer, or a new layer (with or without a mask). Decontaminating colours needs a -/// new layer, so it upgrades `selection` / `layerMask` output to `newLayerWithMask` (as -/// Photoshop does). New-layer outputs hide the source layer. +/// the active layer, a new layer or a new document (each with or without a mask). +/// Decontaminating colours needs new pixels, so it upgrades `selection` / `layerMask` output to +/// `newLayerWithMask` (as Photoshop does). New-layer outputs hide the source layer; new-document +/// outputs leave the source document untouched. fn refine_edge(s: &mut Session, p: &Value) -> Result { let params = refine_params(p); let strokes = refine_strokes(p)?; @@ -272,8 +273,8 @@ fn refine_edge(s: &mut Session, p: &Value) -> Result { let default_output = if d.doc.selection.is_none() && active.and_then(|id| d.doc.layer(id)).and_then(|l| l.mask.as_ref()).is_some() { "layerMask" } else { "selection" }; let mut output = p.get("output").and_then(Value::as_str).unwrap_or(default_output).to_string(); - if !matches!(output.as_str(), "selection" | "layerMask" | "newLayer" | "newLayerWithMask") { - return Err(bad("select.refineEdge", "output must be selection|layerMask|newLayer|newLayerWithMask")); + if !matches!(output.as_str(), "selection" | "layerMask" | "newLayer" | "newLayerWithMask" | "newDocument" | "newDocumentWithMask") { + return Err(bad("select.refineEdge", "output must be selection|layerMask|newLayer|newLayerWithMask|newDocument|newDocumentWithMask")); } if decontaminate && matches!(output.as_str(), "selection" | "layerMask") { output = "newLayerWithMask".into(); @@ -315,12 +316,26 @@ fn refine_edge(s: &mut Session, p: &Value) -> Result { let empty = Region { bbox: Rect::EMPTY, mask: Vec::new() }; let reg = region.as_ref().unwrap_or(&empty); let pixels = if decontaminate { matting::decontaminate(src, reg, params.radius, amount) } else { src.clone() }; - let with_mask = output == "newLayerWithMask"; + let with_mask = matches!(output.as_str(), "newLayerWithMask" | "newDocumentWithMask"); let (content, mask) = if with_mask { (pixels, Some(LayerMask { surface: matting::region_surface(reg), ..LayerMask::reveal_all() })) } else { (matting::masked_copy(&pixels, reg), None) }; + if output.starts_with("newDocument") { + // Same canvas, colour mode, depth and profile, so the pixels need no conversion. + let mut doc = Document::new("Untitled", d.doc.size, d.doc.mode, d.doc.depth); + doc.resolution_dpi = d.doc.resolution_dpi; + doc.icc_profile = d.doc.icc_profile.clone(); + doc.color_table = d.doc.color_table.clone(); + doc.duotone = d.doc.duotone.clone(); + let mut layer = Layer::new(src_layer.name.clone(), LayerContent::Raster(content)); + layer.mask = mask; + let layer_id = layer.id; + doc.layers.push(layer); + let i = s.add_document(doc, None); + return Ok(json!({ "output": output, "document": i, "layer": layer_id.0, "selected": false })); + } let new_id = s.edit("Select and Mask", |doc, act| { let mut layer = Layer::new(name, LayerContent::Raster(content)); layer.mask = mask; @@ -374,7 +389,7 @@ pub fn specs() -> Vec { "select.refineEdge", "Refine Edge", [], - r##"{"radius":px=0,"smartRadius":bool=false,"smooth":0..100=0,"feather":px=0,"contrast":0..100=0,"shiftEdge":-100..100=0,"decontaminate":bool=false,"amount":0..100=100,"output":"selection|layerMask|newLayer|newLayerWithMask"="selection","sampleAllLayers":bool=false,"refineStrokes":[{"points":[[x,y],…],"size":px=30,"mode":"add|subtract"="add"},…]=[]} (refineStrokes: the Refine Edge Brush, in painting order: add refines the painted area from the image, subtract keeps the original edge there)"##, + r##"{"radius":px=0,"smartRadius":bool=false,"smooth":0..100=0,"feather":px=0,"contrast":0..100=0,"shiftEdge":-100..100=0,"decontaminate":bool=false,"amount":0..100=100,"output":"selection|layerMask|newLayer|newLayerWithMask|newDocument|newDocumentWithMask"="selection","sampleAllLayers":bool=false,"refineStrokes":[{"points":[[x,y],…],"size":px=30,"mode":"add|subtract"="add"},…]=[]} (refineStrokes: the Refine Edge Brush, in painting order: add refines the painted area from the image, subtract keeps the original edge there)"##, has_selection_or_mask, refine_edge ), @@ -590,7 +605,11 @@ mod tests { /// A vertical Gaussian-blurred edge at x = 40 (warm left, cool right) with the left half selected. fn blurred_edge_selected() -> Session { - let mut s = session_with(80, 40, 8, |x, _| { + blurred_edge_selected_at(8) + } + + fn blurred_edge_selected_at(depth: u32) -> Session { + let mut s = session_with(80, 40, depth, |x, _| { let t = 1.0 / (1.0 + ((x as f32 + 0.5 - 40.0) / 1.8).exp()); [0.9 * t + 0.1 * (1.0 - t), 0.8 * t + 0.2 * (1.0 - t), 0.2 * t + 0.6 * (1.0 - t)] }); @@ -665,6 +684,47 @@ mod tests { assert!(!s.is_enabled("select.refineEdge")); } + /// Output To New Document (with or without a layer mask) opens a document like the source, + /// at every depth, and leaves the source document as it was. + #[test] + fn refine_edge_outputs_to_a_new_document() { + for depth in [8, 16, 32] { + let mut s = blurred_edge_selected_at(depth); + s.edit("profile", |doc, _| { + doc.resolution_dpi = 300.0; + Ok(()) + }) + .unwrap(); + let before = s.active().unwrap().doc.clone(); + let r = s.execute("select.refineEdge", json!({"radius": 8, "output": "newDocument"})).unwrap(); + assert_eq!((r["output"].as_str(), r["document"].as_u64()), (Some("newDocument"), Some(1)), "depth {depth}"); + assert_eq!(s.active_index(), Some(1)); + let nd = &s.active().unwrap().doc; + assert_eq!((nd.size, nd.mode, nd.depth, nd.resolution_dpi), (before.size, before.mode, before.depth, 300.0)); + assert_eq!(nd.layers.len(), 1); + assert!(nd.selection.is_none() && nd.layers[0].mask.is_none()); + let surf = nd.layers[0].surface().unwrap(); + let a = |x: i32| surf.pixel(x, 20)[3]; + assert!(a(5) > 0.99 && a(75) == 0.0 && a(40) > 0.05 && a(40) < 0.95, "depth {depth}: {} {} {}", a(5), a(40), a(75)); + // The source is untouched: same layers, still visible, selection kept, no history step. + let src = &s.documents()[0]; + assert_eq!(src.doc.layers, before.layers); + assert_eq!(src.doc.selection, before.selection); + assert_eq!(src.history.undo_label(), Some("profile")); + + // With a layer mask: full pixels, the refined matte as the mask. + s.execute("file.close", json!({"document": 1})).unwrap(); + let r = s.execute("select.refineEdge", json!({"radius": 8, "output": "newDocumentWithMask"})).unwrap(); + assert_eq!(r["output"], "newDocumentWithMask"); + let nd = &s.active().unwrap().doc; + let l = &nd.layers[0]; + assert_eq!(l.surface().unwrap().pixel(75, 20), before.layers[0].surface().unwrap().pixel(75, 20), "depth {depth}"); + let m = l.mask.as_ref().unwrap(); + assert!(soft_count(&m.surface, 20) >= 4, "depth {depth}"); + assert!(m.value(5, 20) > 0.99 && m.value(75, 20) < 0.01, "depth {depth}"); + } + } + #[test] fn refine_edge_shift_and_contrast() { let mut s = blurred_edge_selected(); diff --git a/crates/ui-egui/src/filter_dialog.rs b/crates/ui-egui/src/filter_dialog.rs index f6c116a8..61f6a54f 100644 --- a/crates/ui-egui/src/filter_dialog.rs +++ b/crates/ui-egui/src/filter_dialog.rs @@ -1070,6 +1070,21 @@ mod tests { assert!(missing.is_empty(), "missing Korean dynamic labels: {missing:#?}"); } + /// Select and Mask's Output To lists all six of Photoshop's targets, each translated. + #[test] + fn select_and_mask_output_choices_are_translated() { + let spec = photocraft_engine::commands::find("select.refineEdge").unwrap().params; + let p = parse_spec(spec).into_iter().find(|p| p.key == "output").unwrap(); + let Kind::Choice { options: choices, .. } = p.kind else { panic!("output is not a choice") }; + let labels: Vec = choices.iter().map(|c| source_label(c)).collect(); + assert_eq!(labels, ["Selection", "Layer Mask", "New Layer", "New Layer With Mask", "New Document", "New Document With Mask"]); + for lang in crate::i18n::Lang::all().filter(|l| l.code() != "en") { + for label in &labels { + assert!(crate::i18n::has(lang, label), "{}: untranslated {label}", lang.code()); + } + } + } + #[test] fn preview_runs_engine_command_on_proxy() { let mut doc = Document::with_background( diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 01ee0139..2360fe1e 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1561,6 +1561,9 @@ ↑↓ navigate {key} run esc close ↑↓ pohyb {key} spustit esc zavřít Version {version} Verze {version} New Document Nový dokument + New Document With Mask Nový dokument s maskou + New Layer With Mask Nová vrstva s maskou + New Layer Nová vrstva Saved Uložené Save Preset… Uložit předvolbu… Preset Name Název předvolby diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index e0cb3cdc..c0ae347a 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1540,6 +1540,9 @@ ↑↓ navigate {key} run esc close ↑↓ Navigieren {key} Ausführen Esc Schließen Version {version} Version {version} New Document Neues Dokument + New Document With Mask Neues Dokument mit Maske + New Layer With Mask Neue Ebene mit Maske + New Layer Neue Ebene Save Preset… Vorgabe speichern… Preset Name Name der Vorgabe No saved presets yet. Noch keine gespeicherten Vorgaben. diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index e7250c3a..d6423dba 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -436,6 +436,8 @@ Text Layer Επίπεδο κειμένου Type Layer Επίπεδο κειμένου New Document Νέο έγγραφο + New Document With Mask Νέο έγγραφο με μάσκα + New Layer With Mask Νέο επίπεδο με μάσκα Document Έγγραφο Documents Έγγραφα File Name Όνομα αρχείου diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index d2b4e7cf..103f70af 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1546,6 +1546,9 @@ ↑↓ navigate {key} run esc close ↑↓ navegar {key} ejecutar esc cerrar Version {version} Versión {version} New Document Documento nuevo + New Document With Mask Documento nuevo con máscara + New Layer With Mask Nueva capa con máscara + New Layer Nueva capa Save Preset… Guardar ajuste preestablecido… Preset Name Nombre del ajuste preestablecido No saved presets yet. Todavía no hay ajustes preestablecidos guardados. diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index b569dfb8..bab23e8c 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1527,6 +1527,9 @@ ↑↓ navigate {key} run esc close ↑↓ naviguer {key} exécuter échap fermer Version {version} Version {version} New Document Nouveau document + New Document With Mask Nouveau document avec masque + New Layer With Mask Nouveau calque avec masque + New Layer Nouveau calque Preset Name Nom du préréglage No saved presets yet. Aucun préréglage enregistré pour le moment. Error Erreur diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index e6be90d8..d4b4da49 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1521,6 +1521,9 @@ ↑↓ navigate {key} run esc close ↑↓ navigasi {key} jalankan esc tutup Version {version} Versi {version} New Document Dokumen Baru + New Document With Mask Dokumen Baru dengan Mask + New Layer With Mask Layer Baru dengan Mask + New Layer Layer Baru Preset Name Nama Preset No saved presets yet. Belum ada preset tersimpan. Error Error diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index a0896ba5..89aa9ff9 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1545,6 +1545,9 @@ ↑↓ navigate {key} run esc close ↑↓ naviga {key} esegui esc chiudi Version {version} Versione {version} New Document Nuovo documento + New Document With Mask Nuovo documento con maschera + New Layer With Mask Nuovo livello con maschera + New Layer Nuovo livello Save Preset… Salva predefinito… Preset Name Nome del predefinito No saved presets yet. Nessun predefinito salvato. diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 8b82462f..6a9ae4e3 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1545,6 +1545,8 @@ ↑↓ navigate {key} run esc close ↑↓ 移動 {key} 実行 esc 閉じる Version {version} バージョン {version} New Document 新規ドキュメント + New Document With Mask 新規ドキュメント(マスク付き) + New Layer With Mask 新規レイヤー(マスク付き) Saved 保存済み Save Preset… プリセットを保存… Preset Name プリセット名 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 8734bcc3..3e7c6c24 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1516,6 +1516,8 @@ ↑↓ navigate {key} run esc close ↑↓ 이동 {key} 실행 esc 닫기 Version {version} 버전 {version} New Document 새 문서 + New Document With Mask 새 문서(마스크 포함) + New Layer With Mask 새 레이어(마스크 포함) Saved 저장됨 Save Preset… 사전 설정 저장… Preset Name 사전 설정 이름 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index dc1397f9..e300169a 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1527,6 +1527,8 @@ ↑↓ navigate {key} run esc close ↑↓ navigeren {key} uitvoeren esc sluiten Version {version} Versie {version} New Document Nieuw document + New Document With Mask Nieuw document met masker + New Layer With Mask Nieuwe laag met masker Save Preset… Voorinstelling opslaan… Preset Name Naam voorinstelling No saved presets yet. Nog geen opgeslagen voorinstellingen. diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 83b18a41..a651de28 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1540,6 +1540,8 @@ ↑↓ navigate {key} run esc close ↑↓ nawigacja {key} uruchom esc zamknij Version {version} Wersja {version} New Document Nowy dokument + New Document With Mask Nowy dokument z maską + New Layer With Mask Nowa warstwa z maską Preset Name Nazwa ustawienia wstępnego No saved presets yet. Nie ma jeszcze zapisanych ustawień wstępnych. Error Błąd diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 3caf7e7b..7223b18b 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1525,6 +1525,8 @@ ↑↓ navigate {key} run esc close ↑↓ navegar {key} executar esc fechar Version {version} Versão {version} New Document Novo documento + New Document With Mask Novo documento com máscara + New Layer With Mask Nova camada com máscara Preset Name Nome da predefinição No saved presets yet. Nenhuma predefinição salva ainda. Error Erro diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index f7b6f431..3a7309bc 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1753,6 +1753,9 @@ Divide Разделить Result Результат New Document Новый документ + New Document With Mask Новый документ с маской + New Layer With Mask Новый слой с маской + New Layer Новый слой Preset Name Имя набора No saved presets yet. Сохранённых наборов пока нет. New Channel Новый канал diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 9f3085e8..0ec4edd0 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1733,6 +1733,8 @@ Divide Ділення Result Результат New Document Новий документ + New Document With Mask Новий документ з маскою + New Layer With Mask Новий шар з маскою Preset Name Назва набору No saved presets yet. Збережених наборів ще немає. New Channel Новий канал diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 96e1ee7a..90753bc2 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1558,6 +1558,9 @@ ↑↓ navigate ↵ run esc close ↑↓ 导航 ↵ 运行 esc 关闭 Version {version} 版本 {version} New Document 新建文档 + New Document With Mask 带蒙版的新建文档 + New Layer With Mask 带蒙版的新建图层 + New Layer 新建图层 Saved 已保存 Save Preset… 保存预设… Preset Name 预设名称 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 6dc45ae3..3dffcbc3 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1552,6 +1552,9 @@ ↑↓ navigate {key} run esc close ↑↓ 瀏覽 {key} 執行 esc 關閉 Version {version} 版本 {version} New Document 新增文件 + New Document With Mask 含遮色片的新增文件 + New Layer With Mask 含遮色片的新增圖層 + New Layer 新增圖層 Saved 已儲存 Save Preset… 儲存預設… Preset Name 預設集名稱 diff --git a/docs/gaps.md b/docs/gaps.md index e1508caa..8a2b90cb 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -65,7 +65,7 @@ what an agent can't finish alone. - **Estimate:** 60–100 h (2–9 h each, per the calibration). ### G7. Workspaces shown as parameter dialogs -- **Missing:** Select and Mask workspace layout and New Document outputs +- **Missing:** Select and Mask workspace layout (TOOL-214-1, Refine Hair #2671; its view modes preview live since #1754, Refine Edge Brush #2972); Content-Aware Fill workspace with a paintable sampling area (TOOL-214-15); Vanishing Point plane editor (TOOL-214-14); Content-Aware Scale through the transform box (TOOL-214-7); Puppet Warp mesh following the outline (TOOL-214-6); Content-Aware diff --git a/docs/scorecard.md b/docs/scorecard.md index 098f7539..93d4bdf9 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -145,7 +145,7 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t | TOOL-213-13 | Brush regression tests: no seams, hard edge, zero-scatter defaults, ABR never black | done | [#213](https://github.com/storytold/photocraft/issues/213) | Seam, hard-edge and zero-scatter tests in brush_tests.rs; ABR tip colour test in abr_map/tests.rs | | TOOL-213-14 | Tablet: no OS arrow cursor over the canvas on macOS, Windows and Linux | partial | [#213](https://github.com/storytold/photocraft/issues/213) | Brush tools set CursorIcon::None (canvas.rs:1259-1275); not verified per OS | | TOOL-213-18 | Pattern Stamp tool: sibling of Clone Stamp in the S flyout | done | [#213](https://github.com/storytold/photocraft/issues/213) | Tool::PatternStamp in the S group; paint.patternStamp stamps the Patterns-panel tile with Aligned and Impressionist (an approximation: it jitters each dab's tile phase, not Photoshop's smeared impressionist look); engine tests cover depths, seams, hostile input | -| TOOL-214-1 | Select and Mask workspace: view modes, Refine Edge brush, 6 outputs | partial | [#214](https://github.com/storytold/photocraft/issues/214) | select.refineEdge dialog with a live preview in Photoshop's view modes: Onion Skin, Marching Ants, Overlay, On Black, On White, Black & White, On Layers, per-view Opacity, Show Edge (J), Show Original (P), F cycles, X shows the image (select_mask_view.rs, #1754); Refine Edge Brush (R, size, Expand Detection Area / Restore Original Edge, Alt swaps) paints select.refineEdge refineStrokes, estimated by colour-line matting (matting/brush.rs, #2972); outputs Selection, Layer Mask, New Layer, New Layer with Mask; no workspace layout or New Document outputs | +| TOOL-214-1 | Select and Mask workspace: view modes, Refine Edge brush, 6 outputs | partial | [#214](https://github.com/storytold/photocraft/issues/214) | select.refineEdge dialog with a live preview in Photoshop's view modes: Onion Skin, Marching Ants, Overlay, On Black, On White, Black & White, On Layers, per-view Opacity, Show Edge (J), Show Original (P), F cycles, X shows the image (select_mask_view.rs, #1754); Refine Edge Brush (R, size, Expand Detection Area / Restore Original Edge, Alt swaps) paints select.refineEdge refineStrokes, estimated by colour-line matting (matting/brush.rs, #2972); outputs Selection, Layer Mask, New Layer, New Layer with Mask, New Document, New Document with Mask (#2998); no workspace layout | | TOOL-214-2 | Marquee: Shift square, Alt from centre, live W x H readout | done | [#214](https://github.com/storytold/photocraft/issues/214) | canvas.rs marquee_corners: Shift square or circle, Alt from the centre, live W x H readout beside the cursor (marquee_readout); marquee_tests.rs | | TOOL-214-3 | Color Range test: Sampled Colors at Fuzziness 0 selects the exact match only | partial | [#214](https://github.com/storytold/photocraft/issues/214) | Fuzziness-0 tests cover midtones/shadows only (selection_cmds.rs:444-447) | | TOOL-214-4 | Move tool: box-select layers, Auto-Select hover outline, double-click fit, drag to a tab | partial | [#214](https://github.com/storytold/photocraft/issues/214) | Auto-Select hover outline (Preferences › Tools › Show Highlight on Rollover, #2834) and box-select layers from an empty spot (move_box.rs, layer.selectInRect, #2844) done; double-click fit and drag to a tab open | diff --git a/scorecard/tools.toml b/scorecard/tools.toml index e73f7cde..ba562042 100644 --- a/scorecard/tools.toml +++ b/scorecard/tools.toml @@ -115,7 +115,7 @@ id = "TOOL-214-1" target = "Select and Mask workspace: view modes, Refine Edge brush, 6 outputs" status = "partial" issue = 214 -note = "select.refineEdge dialog with a live preview in Photoshop's view modes: Onion Skin, Marching Ants, Overlay, On Black, On White, Black & White, On Layers, per-view Opacity, Show Edge (J), Show Original (P), F cycles, X shows the image (select_mask_view.rs, #1754); Refine Edge Brush (R, size, Expand Detection Area / Restore Original Edge, Alt swaps) paints select.refineEdge refineStrokes, estimated by colour-line matting (matting/brush.rs, #2972); outputs Selection, Layer Mask, New Layer, New Layer with Mask; no workspace layout or New Document outputs" +note = "select.refineEdge dialog with a live preview in Photoshop's view modes: Onion Skin, Marching Ants, Overlay, On Black, On White, Black & White, On Layers, per-view Opacity, Show Edge (J), Show Original (P), F cycles, X shows the image (select_mask_view.rs, #1754); Refine Edge Brush (R, size, Expand Detection Area / Restore Original Edge, Alt swaps) paints select.refineEdge refineStrokes, estimated by colour-line matting (matting/brush.rs, #2972); outputs Selection, Layer Mask, New Layer, New Layer with Mask, New Document, New Document with Mask (#2998); no workspace layout" [[item]] id = "TOOL-214-2"