collective

Browse / github.com/storytold/photocraft / patch

Select sky beneath an obstructed top border

Select sky beneath an obstructed top border ## Problem `sky_mask_low` (`crates/algo/src/segment/sky.rs`) seeded sky growth only from the top 1/40th of the image. When fewer than 5% of those pixels were likely sky (overhanging trees, an archway, a bridge, a ceiling), it returned `None` and selected no sky at all, even with sky visible lower in the frame. ## Change - When the top-border seeds are sparse (< 5% of the band), clear them and look for high-confidence sky (`score > threshold + 0.1`) in the upper half of the image, as 4-connected components. - Each component must cover at least 0.5% of the image and at least 16 working-resolu

Status
open
Score
0.00
Adopters
0 other operators
Author
wanikhawar (imported from pull request #9693, open)
Base
7ac7b8b106acccf6826d45ed5c9e481dfe341035
Size
+166 −10 in 3 files
Review
pending
Shared
2026-10-28
Id
d7c117172b4157c794d94c80

How it went

No other operator has reported using it yet.

Diff

raw · crates/algo/src/segment/sky.rs docs/gaps.md docs/target-app-parity.md

diff --git a/crates/algo/src/segment/sky.rs b/crates/algo/src/segment/sky.rs--- a/crates/algo/src/segment/sky.rs+++ b/crates/algo/src/segment/sky.rs@@ -3,8 +3,9 @@ //! 1. At a reduced working resolution every pixel gets a *sky likelihood*: a colour prior //!    (blue and bright, or bright and unsaturated for overcast / cloud, or a bright warm glow //!    for sunsets) times a smoothness term (sky has little gradient energy).-//! 2. Seeds are likely-sky pixels in the top rows; the sky region grows from them through-//!    4-connected neighbours that stay likely-sky and change colour gradually.+//! 2. Seeds are likely-sky pixels in the top rows, falling back to substantial high-confidence+//!    components in the upper half when the top is obstructed. The sky region grows from them+//!    through 4-connected neighbours that stay likely-sky and change colour gradually. //! 3. Sky seen through gaps (between branches) is not connected to the top: likely-sky pixels //!    that fit a colour model (GMM) of the grown sky are added too. Small holes are filled. //! 4. The binary working-resolution mask is upsampled and refined at full resolution with a@@ -30,22 +31,31 @@ pub struct SkyParams { }  impl Default for SkyParams {+    /// Default parameters (threshold 50, softness 50).     fn default() -> Self {         Self { threshold: 50.0, softness: 50.0 }     } } +/// Hermite interpolation between `e0` and `e1` with cubic smoothing. #[inline] fn smooth(e0: f32, e1: f32, x: f32) -> f32 {     let t = ((x - e0) / (e1 - e0)).clamp(0.0, 1.0);     t * t * (3.0 - 2.0 * t) } +/// Rec. 601 perceptual luminance of an RGB triple. #[inline] fn luma(p: [f32; 3]) -> f32 {     0.299 * p[0] + 0.587 * p[1] + 0.114 * p[2] } +/// Euclidean distance between two RGB colours.+#[inline]+fn rgb_dist(a: [f32; 3], b: [f32; 3]) -> f32 {+    ((a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2)).sqrt()+}+ /// Colour prior: how sky-like a colour is on its own (0–1). pub fn colour_prior(p: [f32; 3]) -> f32 {     let [r, g, b] = p;@@ -79,7 +89,7 @@ pub fn likelihood(img: &RgbImage) -> Vec<f32> {     img.px.iter().zip(&tex).map(|(p, t)| colour_prior(*p) * (1.0 - smooth(0.015, 0.07, *t))).collect() } -/// Binary sky mask at working resolution (`None` when no sky touches the top of the image).+/// Binary sky mask at working resolution (`None` when no plausible sky region is found). pub fn sky_mask_low(img: &RgbImage, threshold: f32) -> Option<Vec<bool>> {     let (w, h) = (img.w, img.h);     if w < 2 || h < 2 {@@ -97,11 +107,22 @@ pub fn sky_mask_low(img: &RgbImage, threshold: f32) -> Option<Vec<bool>> {             stack.push(i);         }     }+    let max_step = 0.12f32;     if stack.len() * 20 < top * w {-        return None; // less than 5 % of the top edge looks like sky+        // An overhang can hide the entire top band. Discard the sparse top seeds before+        // looking for more certain, connected sky patches below it.+        mask.fill(false);+        stack = obstructed_sky_seeds(img, &like, t + 0.1, max_step);+        if stack.is_empty() {+            return None;+        }+        for &i in &stack {+            if let Some(m) = mask.get_mut(i) {+                *m = true;+            }+        }     }     // Region growing with a gradual-change constraint.-    let max_step = 0.12f32;     while let Some(i) = stack.pop() {         let (x, y) = (i % w, i / w);         let n = [(x > 0).then(|| i - 1), (x + 1 < w).then(|| i + 1), (y > 0).then(|| i - w), (y + 1 < h).then(|| i + w)];@@ -110,8 +131,7 @@ pub fn sky_mask_low(img: &RgbImage, threshold: f32) -> Option<Vec<bool>> {                 continue;             }             let (a, b) = (img.px[i], img.px[j]);-            let d = ((a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2)).sqrt();-            if d < max_step {+            if rgb_dist(a, b) < max_step {                 mask[j] = true;                 stack.push(j);             }@@ -138,6 +158,56 @@ pub fn sky_mask_low(img: &RgbImage, threshold: f32) -> Option<Vec<bool>> {     mask.iter().any(|v| *v).then_some(mask) } +/// Without border evidence, require a connected patch of at least 0.5% of the image (and+/// 16 pixels). Searching only the upper half avoids using water or blue ground as seeds;+/// counting each component separately keeps scattered bright specks from passing the gate.+/// Adjacent pixels must satisfy the same gradual colour constraint (`max_step`) as region growing.+fn obstructed_sky_seeds(img: &RgbImage, like: &[f32], threshold: f32, max_step: f32) -> Vec<usize> {+    let (w, h) = (img.w, img.h);+    let Some(area) = w.checked_mul(h) else { return Vec::new() };+    let Some(upper) = w.checked_mul(h.div_ceil(2)) else { return Vec::new() };+    if img.px.len() < upper {+        return Vec::new();+    }+    let Some(like) = like.get(..upper) else { return Vec::new() };+    let minimum = area.div_ceil(200).max(16);+    let mut seen = vec![false; upper];+    let mut seeds = Vec::new();+    let mut component = Vec::new();+    for (start, &score) in like.iter().enumerate() {+        if score <= threshold || seen.get(start).copied().unwrap_or(true) {+            continue;+        }+        component.clear();+        component.push(start);+        if let Some(s) = seen.get_mut(start) {+            *s = true;+        }+        let mut cursor = 0;+        while let Some(&i) = component.get(cursor) {+            cursor += 1;+            let x = i % w;+            let neighbours = [(x > 0).then(|| i - 1), (x + 1 < w).then(|| i + 1), i.checked_sub(w), i.checked_add(w).filter(|j| *j < upper)];+            for j in neighbours.into_iter().flatten() {+                if like.get(j).is_some_and(|v| *v > threshold)+                    && let Some(s) = seen.get_mut(j)+                    && !*s+                {+                    let (a, b) = (img.px[i], img.px[j]);+                    if rgb_dist(a, b) < max_step {+                        *s = true;+                        component.push(j);+                    }+                }+            }+        }+        if component.len() >= minimum {+            seeds.extend_from_slice(&component);+        }+    }+    seeds+}+ /// A neutral region that also covers most of the bottom rows is a studio or product backdrop, /// not an overcast sky (which never reaches the bottom of a photo); blue sky is never one. fn is_backdrop(img: &RgbImage, mask: &[bool], rows: usize) -> bool {@@ -291,6 +361,89 @@ mod tests {         assert!(r.at(30, 10) > 0.9 && r.at(30, 50) < 0.1);     } +    #[test]+    fn finds_obstructed_blue_and_overcast_sky_in_disconnected_openings() {+        for sky in [[0.35, 0.6, 0.95], [0.85, 0.86, 0.88]] {+            // A dark arch with two openings: neither sky component touches an image edge.+            let img =+                RgbImage::from_fn(+                    160,+                    100,+                    |x, y| {+                        if (15..65).contains(&y) && ((15..65).contains(&x) || (95..145).contains(&x)) { sky } else { [0.12, 0.2, 0.1] }+                    },+                );+            for threshold in [0.0, 50.0, 100.0] {+                let p = SkyParams { threshold, ..SkyParams::default() };+                let r = select_sky(&ImageSampler { img: &img, origin: (0, 0) }, Rect::new(0, 0, 160, 100), p).expect("sky beneath arch");+                assert!(r.at(40, 30) > 0.9 && r.at(120, 30) > 0.9, "both disconnected openings: {sky:?}, {threshold}");+                assert!(r.at(40, 55) > 0.9, "growth beyond the upper-half seed search");+                assert!(r.at(40, 5) < 0.1, "overhead obstruction");+                assert!(r.at(80, 30) < 0.1, "arch pillar");+                assert!(r.at(40, 85) < 0.1, "ground");+            }+        }+    }++    #[test]+    fn obstructed_fallback_rejects_small_patches_and_lower_half_objects() {+        for colour in [[0.35, 0.6, 0.95], [0.9, 0.9, 0.92]] {+            let img = RgbImage::from_fn(160, 100, |x, y| {+                if ((20..26).contains(&x) && (20..26).contains(&y)) || ((20..140).contains(&x) && (65..95).contains(&y)) { colour } else { [0.12, 0.2, 0.1] }+            });+            assert!(sky_mask_low(&img, 50.0).is_none(), "small upper patch and large lower object: {colour:?}");+        }+    }++    #[test]+    fn obstructed_neutral_backdrop_is_still_rejected() {+        let img = RgbImage::from_fn(160, 100, |_, y| if y < 12 { [0.12, 0.2, 0.1] } else { [0.95, 0.95, 0.94] });+        assert!(sky_mask_low(&img, 50.0).is_none());+    }++    #[test]+    fn fallback_requires_individually_large_four_connected_components() {+        let (w, h) = (80, 80);+        let img = RgbImage { w, h, px: vec![[0.35, 0.6, 0.95]; w * h] };+        // Enough total likely pixels to pass the area gate, but no large component.+        let mut like: Vec<f32> = (0..w * h).map(|i| if i % w % 4 == 0 && i / w % 4 == 0 { 1.0 } else { 0.0 }).collect();+        assert!(obstructed_sky_seeds(&img, &like, 0.5, 0.12).is_empty());+        // Adjacent row ends must not join across the horizontal image boundary.+        like.fill(0.0);+        for y in 10..20 {+            for x in 0..2 {+                like[y * w + x] = 1.0;+                like[(y - 1) * w + w - 1 - x] = 1.0;+            }+        }+        assert!(obstructed_sky_seeds(&img, &like, 0.5, 0.12).is_empty());+    }++    #[test]+    fn obstructed_fallback_excludes_attached_differently_coloured_patch() {+        let (w, h) = (160, 100);+        // Minimum qualifying component size: area.div_ceil(200).max(16) = 16000 / 200 = 80.+        // Sky component is 40x40 = 1600 pixels in rows 15..55, cols 20..60 (qualifies).+        // Attached differently coloured patch is 2x5 = 10 pixels in rows 20..25, cols 60..62 (< 80 min).+        // Both patches have high likelihood, but their colour distance exceeds max_step (0.12).+        let img = RgbImage::from_fn(w, h, |x, y| {+            if (15..55).contains(&y) && (20..60).contains(&x) {+                [0.35, 0.6, 0.95]+            } else if (20..25).contains(&y) && (60..62).contains(&x) {+                [0.85, 0.86, 0.88]+            } else {+                [0.12, 0.2, 0.1]+            }+        });+        let mask = sky_mask_low(&img, 50.0).expect("sky mask");+        assert!(mask[30 * w + 40], "qualifying blue sky must be in mask");+        for y in 20..25 {+            for x in 60..62 {+                assert!(!mask[y * w + x], "attached patch at ({x}, {y}) must be excluded from mask");+            }+        }+    }+     /// #4634: a near-white studio backdrop around a product is not an overcast sky, but blue sky     /// reaching the bottom (a photo taken upward) still is.     #[test]diff --git a/docs/gaps.md b/docs/gaps.md--- a/docs/gaps.md+++ b/docs/gaps.md@@ -1,6 +1,6 @@ # Where PhotoCraft falls short of Photoshop -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (cropped selection morphology and command mask conversion performance evidence added; G9: 12 of 178 preferences do nothing, as the scorecard prefs audit counts them; G6: Art History Brush landed as partial, TOOL-213-20, #1610, so no toolbar tool is missing; G4: five hard-round built-in presets use 5% spacing to remove stacked-tip outlines and reduce pressure-opacity bands, #8842; G9: Crop tool canvas menu, #6561; G2: Content-Aware Scale seam carving 1.65–3.6× faster with unchanged output, P28 row 3.3 → 1.6 s on a 4-core VM, #5236; G9: UI scale presets done, 105–120% steps, #3222; G14: lossy DNG, raw sidecars, Camera Raw Defaults live, Calibration and Auto, CR3 assessed as not clean-room feasible; G8: PSD type on a path opens on its path and saves back into the file's `Txt2`, #7962; G11: a recorded Save As imports with its format and file (`file.saveAs`, `file.saveACopy` for As a Copy) instead of as a plain Save that wrote over the played file, #7102; folder destinations stop playback until #5430; adjustment clipping stays visible in compact Properties, #5231; G9: Color panel modes, ramp and copy commands, the same in every theme, UI-217-17, #6000; G17: Help › Check for Updates and an opt-out check once per launch, DIST-220-1 partial, #5485; G10: `.asl` style libraries landed, #1051; G8: area type (TYPE-218-10); G1: smart objects keep Photoshop's Cylinder warp, #4112; G8: paragraph direction buttons in the Paragraph panel and options bar, TYPE-218-2 partial; G1: 32-bit Hue/Saturation and Colorize as Photoshop does them, Photoshop-oracle floor 190/258, #5781; G11: compound layer properties export as separate Photoshop Set events, rename last, #8304; G11: Batch and droplets take the result at the action's Save step, so actions ending in Save As and Close work; G11: `.atn` steps that switch to another open document import, play and save back, #7103; G14: the Nikon D4 white level follows the sample depth, so 12-bit D4 NEFs no longer open 2.6 stops dark, #5978; G14: a Nikon NEF's Adobe RGB preview is read as Adobe RGB (maker-note ColorSpace 0x001E), #5987; Apply Layer Comp action mapping, #7101; G10: JPEG Options dialog landed, FILE-215-19 partial (no Matte, progressive Scans or size preview); G10: Cineon write; G10: DPX read and write, #1049, Cineon and Amiga IFF read; Classical Object Finder improvement; learned segmentation remains open; G4: named, stateful section locks and visible keyboard focus in Brush Settings; G4: independent Brush Pose tilt-axis overrides, #3825 partial; G4: Transfer Mixer-only controls, #3825 partial; G9: Preferences › File Handling › Save in Background takes effect, #6207, 24 of 169 preferences do nothing; G7: Content-Aware Move previews every selection the command takes, coarse then exact, TOOL-213-4 done, #6680; G4: Pattern Stamp and Background Eraser draw live, TOOL-213-7; G9: Animated Zoom pref read: holding the Zoom tool zooms continuously, #6210, prefs that do nothing 25 of 166; G2: a cancelled 24 MP Gaussian Blur job stops in at most 44 ms, P25 under its 200 ms budget in local runs, baseline not re-run, #6205; G1: generated large-file fixtures, a 4.38 GB PSB opens on Windows through io, FILE-216-2 done, #5107; G9: fill and adjustment layers copy and paste whole, #2344; G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed; G10: Amiga IFF write; G10: PSD spot channels in Photoshop's polarity, earlier PhotoCraft PSDs keep their ink, #7415 split; G10: TIFF alpha and spot channels written and read as extra samples, FILE-215-7 done, FILE-215-17's TIFF part, #7415) · **Target:** Adobe Photoshop 2026+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (G15: classical Select › Sky can seed substantial high-confidence components beneath an obstructed top band; learned segmentation remains open; cropped selection morphology and command mask conversion performance evidence added; G9: 12 of 178 preferences do nothing, as the scorecard prefs audit counts them; G6: Art History Brush landed as partial, TOOL-213-20, #1610, so no toolbar tool is missing; G4: five hard-round built-in presets use 5% spacing to remove stacked-tip outlines and reduce pressure-opacity bands, #8842; G9: Crop tool canvas menu, #6561; G2: Content-Aware Scale seam carving 1.65–3.6× faster with unchanged output, P28 row 3.3 → 1.6 s on a 4-core VM, #5236; G9: UI scale presets done, 105–120% steps, #3222; G14: lossy DNG, raw sidecars, Camera Raw Defaults live, Calibration and Auto, CR3 assessed as not clean-room feasible; G8: PSD type on a path opens on its path and saves back into the file's `Txt2`, #7962; G11: a recorded Save As imports with its format and file (`file.saveAs`, `file.saveACopy` for As a Copy) instead of as a plain Save that wrote over the played file, #7102; folder destinations stop playback until #5430; adjustment clipping stays visible in compact Properties, #5231; G9: Color panel modes, ramp and copy commands, the same in every theme, UI-217-17, #6000; G17: Help › Check for Updates and an opt-out check once per launch, DIST-220-1 partial, #5485; G10: `.asl` style libraries landed, #1051; G8: area type (TYPE-218-10); G1: smart objects keep Photoshop's Cylinder warp, #4112; G8: paragraph direction buttons in the Paragraph panel and options bar, TYPE-218-2 partial; G1: 32-bit Hue/Saturation and Colorize as Photoshop does them, Photoshop-oracle floor 190/258, #5781; G11: compound layer properties export as separate Photoshop Set events, rename last, #8304; G11: Batch and droplets take the result at the action's Save step, so actions ending in Save As and Close work; G11: `.atn` steps that switch to another open document import, play and save back, #7103; G14: the Nikon D4 white level follows the sample depth, so 12-bit D4 NEFs no longer open 2.6 stops dark, #5978; G14: a Nikon NEF's Adobe RGB preview is read as Adobe RGB (maker-note ColorSpace 0x001E), #5987; Apply Layer Comp action mapping, #7101; G10: JPEG Options dialog landed, FILE-215-19 partial (no Matte, progressive Scans or size preview); G10: Cineon write; G10: DPX read and write, #1049, Cineon and Amiga IFF read; Classical Object Finder improvement; learned segmentation remains open; G4: named, stateful section locks and visible keyboard focus in Brush Settings; G4: independent Brush Pose tilt-axis overrides, #3825 partial; G4: Transfer Mixer-only controls, #3825 partial; G9: Preferences › File Handling › Save in Background takes effect, #6207, 24 of 169 preferences do nothing; G7: Content-Aware Move previews every selection the command takes, coarse then exact, TOOL-213-4 done, #6680; G4: Pattern Stamp and Background Eraser draw live, TOOL-213-7; G9: Animated Zoom pref read: holding the Zoom tool zooms continuously, #6210, prefs that do nothing 25 of 166; G2: a cancelled 24 MP Gaussian Blur job stops in at most 44 ms, P25 under its 200 ms budget in local runs, baseline not re-run, #6205; G1: generated large-file fixtures, a 4.38 GB PSB opens on Windows through io, FILE-216-2 done, #5107; G9: fill and adjustment layers copy and paste whole, #2344; G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed; G10: Amiga IFF write; G10: PSD spot channels in Photoshop's polarity, earlier PhotoCraft PSDs keep their ink, #7415 split; G10: TIFF alpha and spot channels written and read as extra samples, FILE-215-7 done, FILE-215-17's TIFF part, #7415) · **Target:** Adobe Photoshop 2026  Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact on the way to beta. This is the work list agents pick from: take the highest-ranked entry that@@ -198,6 +198,7 @@ what an agent can't finish alone. - **Evidence:** Photoshop's `Required/UXP` ships the Neural Filters gallery and its `.kys` has   a Neural Filters taskspace; users ask for local models (#2313, #1996, #2682). - **Progress (2026-10-17):** classical Object Finder no longer rejects a foreground solely for covering over 60% of the canvas (#6548); canvas-boundary coverage distinguishes it from the surrounding backdrop. This does not add semantic recognition or close #2788.+- **Progress (2026-10-26):** classical Select › Sky falls back to connected high-confidence components in the upper half when its top seed band is obstructed. Synthetic blue and overcast skies beneath an arch are covered, including disconnected openings; small patches, lower-half objects and neutral backdrops are rejected. This remains a colour/smoothness heuristic, not semantic recognition; #2788 stays open. - **Estimate:** 300–600 h. **Human:** owner decision #41 (model licences, local vs remote).  ### G16. Plug-in compatibility@@ -395,6 +396,7 @@ what an agent can't finish alone.  | Date | Change | Summary | |---|---|---|+| 2026-10-26 | minor | G15: classical Select › Sky can seed substantial high-confidence components beneath an obstructed top band; learned segmentation remains open; percentages and estimates unchanged | | 2026-10-26 | minor | Expand/Contract/Border distance work, mask reads and replacement cropped to nonzero bounds; bounded distances for radii ≤5; 36 MP exact-output comparisons improve 1.54–37.82× across eight cases; percentages, estimates and scorecard budgets unchanged | | 2026-10-25 | minor | G9: the count of preferences that do nothing is the scorecard's again, 12 of 178 (it still said 24 of 171, from 2026-10-15); no estimate changed | | 2026-10-25 | minor | G6: Art History Brush landed as partial (TOOL-213-20, #1610), so no toolbar tool is missing; no percentage or estimate changed |diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md--- a/docs/target-app-parity.md+++ b/docs/target-app-parity.md@@ -1,6 +1,6 @@ # PhotoCraft vs Adobe Photoshop: target-app parity -> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-26 · **Change:** minor (cropped selection morphology and command mask conversion performance evidence added; 12 of 178 preferences do nothing, from the scorecard; the Camera Raw row names per-camera colour for non-DNG cameras as missing, not the Calibration panel, which landed 2026-10-13; Content-Aware Scale measurements added, #5236; Help › Check for Updates and an opt-out check once per launch, DIST-220-1 partial, #5485; area type, TYPE-218-10 partial, Type scorecard 5 / 5 / 0; compound layer properties export as separate Photoshop Set events, #8304; JPEG Options dialog landed, FILE-215-19 partial; Print › Functions › Bleed through `file.print`; Content-Aware Move live preview for every selection size, TOOL-213-4 done, #6680; retouching evidence: Pattern Stamp and Background Eraser draw live, only the Mixer Brush is left of TOOL-213-7; fill and adjustment layers copy and paste whole, UI-217-23 partial, #2344; shape layers take Density and Feather, TOOL-214-12 done, #5846; on-canvas corner radius widgets, TOOL-214-12; `.atn` import maps 112 events; Free Transform H/V skew fields; stale rows corrected against `main` and the scorecard, estimates unchanged; Photoshop Actions `.atn` load and save; 2026-10-10: Tilt-Shift measurements added; earlier the same day: major, full re-measure against Photoshop 2026 27.11.0; merged `parity-estimate.md` and the roadmap's honest assessment; stale missing-tool rows corrected: Color Replacement and Red Eye are live toolbar tools, Frame landed; percentages unchanged; PSD spot plates in Photoshop's polarity, #7415 split; TIFF spot plates, FILE-215-17's TIFF part, #7415) · **Target:** Adobe Photoshop 2026+> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-26 · **Change:** minor (classical Select › Sky can seed substantial high-confidence components beneath an obstructed top band; learned segmentation remains open; cropped selection morphology and command mask conversion performance evidence added; 12 of 178 preferences do nothing, from the scorecard; the Camera Raw row names per-camera colour for non-DNG cameras as missing, not the Calibration panel, which landed 2026-10-13; Content-Aware Scale measurements added, #5236; Help › Check for Updates and an opt-out check once per launch, DIST-220-1 partial, #5485; area type, TYPE-218-10 partial, Type scorecard 5 / 5 / 0; compound layer properties export as separate Photoshop Set events, #8304; JPEG Options dialog landed, FILE-215-19 partial; Print › Functions › Bleed through `file.print`; Content-Aware Move live preview for every selection size, TOOL-213-4 done, #6680; retouching evidence: Pattern Stamp and Background Eraser draw live, only the Mixer Brush is left of TOOL-213-7; fill and adjustment layers copy and paste whole, UI-217-23 partial, #2344; shape layers take Density and Feather, TOOL-214-12 done, #5846; on-canvas corner radius widgets, TOOL-214-12; `.atn` import maps 112 events; Free Transform H/V skew fields; stale rows corrected against `main` and the scorecard, estimates unchanged; Photoshop Actions `.atn` load and save; 2026-10-10: Tilt-Shift measurements added; earlier the same day: major, full re-measure against Photoshop 2026 27.11.0; merged `parity-estimate.md` and the roadmap's honest assessment; stale missing-tool rows corrected: Color Replacement and Red Eye are live toolbar tools, Frame landed; percentages unchanged; PSD spot plates in Photoshop's polarity, #7415 split; TIFF spot plates, FILE-215-17's TIFF part, #7415) · **Target:** Adobe Photoshop 2026  The authoritative answer to "how close is PhotoCraft to Photoshop, and how much work is left". The one-page summary is [`ROADMAP.md`](../ROADMAP.md); the ranked work list is [`gaps.md`](gaps.md);@@ -168,7 +168,7 @@ parity for the area and are already contained in the dimension rows above. | Area | % | Hours | Notes | |---|---:|---:|---| | Layers, groups, masks, blend modes, smart objects, layer styles | 65% | 150–250 | Broad and GPU-accelerated. Open: a single pixel layer copies its pixels without its mask and styles (UI-217-23: smart object, type, shape, group, fill and adjustment layers and multi-layer selections copy and paste whole), smart-object perspective (FILE-215-6), a user contour library (FILE-215-15). Multi-layer Smart Object conversion (#2052, #2651), Stamp Visible (UI-217-22) and mask targeting by command (#2458) are done |-| Selections, Select and Mask, channels | 55% | 120–200 | All selection tools, single row/column marquee included (#2837). Select and Mask is a dialog with Photoshop's view modes, a Refine Edge Brush and six outputs, not a workspace (TOOL-214-1), no Refine Hair (#2671); Select Subject/Sky classical, 559 ms p50 on 24 MP vs a 100 ms budget |+| Selections, Select and Mask, channels | 55% | 120–200 | All selection tools, single row/column marquee included (#2837). Select and Mask is a dialog with Photoshop's view modes, a Refine Edge Brush and six outputs, not a workspace (TOOL-214-1), no Refine Hair (#2671); Select Subject/Sky classical; Sky has an upper-half component fallback for obstructed top bands (2026-10-26), still without semantic recognition. Select Subject: 559 ms p50 on 24 MP vs a 100 ms budget | | Painting and brush engine | 55% | 150–250 | Every Brush Settings section exists; feel is the gap: lag on big brushes, pressure jumps, smoothing and edge quality reports ([brush-parity.md](brush-parity.md)) | | Retouching (healing, clone, patch, remove, content-aware) | 50% | 100–180 | Remove tool non-generative; Content-Aware Fill has no workspace (TOOL-214-15); no live stroke preview for the Mixer Brush yet (TOOL-213-7) | | Transform, warp, puppet, perspective, liquify | 55% | 100–180 | Free Transform modifiers right, with a right-click transform menu (TOOL-214-5) and H/V skew fields; Puppet Warp mesh follows the object outline (TOOL-214-6); Liquify called "useless" by a user (#2606) |@@ -580,6 +580,7 @@ See [method, raw measurements and reproduction](five-algorithm-performance.md).  | Date | Change | Summary | |---|---|---|+| 2026-10-26 | minor | Selections: classical Select › Sky can seed substantial high-confidence components beneath an obstructed top band; learned segmentation remains open; percentages and estimates unchanged | | 2026-10-26 | minor | Expand/Contract/Border distance work, mask reads and replacement cropped to nonzero bounds; bounded distances for radii ≤5; 36 MP exact-output comparisons improve 1.54–37.82× across eight cases; percentages, estimates and scorecard budgets unchanged | | 2026-10-25 | minor | UI / UX and Workspace rows: 12 of 178 preferences do nothing, the scorecard's count. Camera Raw row: the Calibration panel landed on 2026-10-13 (#5128); what is missing is per-camera colour for non-DNG cameras ([gaps.md](gaps.md) G14). Percentages and estimates unchanged | | 2026-10-23 | minor | Colour management and modes: PSD spot plates are stored as Photoshop stores them (ink 0 = solid), and PSDs from earlier PhotoCraft versions keep their ink (#7415 split, part 4); estimates unchanged |