From 2ff683d5fe4bb0cc884b357cd63b5766c43149f2 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Tue, 20 Oct 2026 04:20:50 +0700 Subject: [PATCH] =?UTF-8?q?G2:=20Effect=20=E2=80=BA=20Sketch=20=E2=80=BA?= =?UTF-8?q?=20Chrome=20raster=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed from #2985. --- crates/effects/src/lib.rs | 7 +++ crates/effects/src/pixel/mod.rs | 17 +++++- crates/effects/src/pixel/sketch.rs | 75 +++++++++++++++++++++++- crates/effects/src/pixel/tests.rs | 39 ++++++++++++ crates/engine/src/tests_rasterfilters.rs | 11 +++- crates/engine/tests/command_sweep.rs | 1 + crates/ui-egui/src/tests_labels.rs | 19 +++--- docs/effects-parity.md | 4 +- docs/gaps.md | 4 +- 9 files changed, 156 insertions(+), 21 deletions(-) diff --git a/crates/effects/src/lib.rs b/crates/effects/src/lib.rs index ae3d60e19..87ca5ad9d 100644 --- a/crates/effects/src/lib.rs +++ b/crates/effects/src/lib.rs @@ -482,6 +482,13 @@ pub fn effect_catalog() -> Vec { "{chalkArea: 0..20 (6; chalk in light areas), charcoalArea: 0..20 (6; charcoal in dark areas), strokePressure: 0..5 (2)} redraws the object with chalk highlights and charcoal strokes on white paper, keeping its coverage. Original visual approximation from public documentation; slider ranges and defaults are VectorCraft choices, not verified proprietary values.", json!({"chalkArea": 6.0, "charcoalArea": 6.0, "strokePressure": 2.0}), ), + r( + "sketch.chrome", + "Chrome…", + SKETCH, + "{detail: 0..10 (4; finer surface texture), smoothness: 0..10 (4; softer relief before the highlights and shadows)} treats the object as polished chrome lit from the top left: bright highlights on high points and dark shadows in low ones, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.", + json!({"detail": 4.0, "smoothness": 4.0}), + ), r( "sketch.graphicPen", "Graphic Pen…", diff --git a/crates/effects/src/pixel/mod.rs b/crates/effects/src/pixel/mod.rs index 8a24027b9..a7075892a 100644 --- a/crates/effects/src/pixel/mod.rs +++ b/crates/effects/src/pixel/mod.rs @@ -41,7 +41,7 @@ pub use sketch::{HALFTONE_TYPES, HalftonePattern}; pub use texture::{GRAIN_TYPES, Grain, LIGHT_DIRECTIONS, Light, TEXTURES, Texture}; /// The Photoshop-style effect ids (all raster effects, see [`crate::is_raster`]). -pub const PIXEL_EFFECTS: [&str; 36] = [ +pub const PIXEL_EFFECTS: [&str; 37] = [ "artistic.cutout", "artistic.filmGrain", "artistic.neonGlow", @@ -66,6 +66,7 @@ pub const PIXEL_EFFECTS: [&str; 36] = [ "pixelate.pointillize", "sharpen.unsharpMask", "sketch.chalkCharcoal", + "sketch.chrome", "sketch.graphicPen", "sketch.halftonePattern", "sketch.reticulation", @@ -151,6 +152,8 @@ pub enum PixelFx { /// Sketch › Chalk & Charcoal: chalk highlights in light areas and charcoal in dark ones; /// `chalk` and `charcoal` (0..20) set how much, `pressure` (0..5) the stroke weight. ChalkCharcoal { chalk: f64, charcoal: f64, pressure: f64 }, + /// Sketch › Chrome: polished chrome relief with `detail` (0..10) and `smoothness` (0..10). + Chrome { detail: f64, smoothness: f64 }, /// Sketch › Graphic Pen: fine linear ink strokes along `dir`, each `length` (1..15) points long /// on white paper; `balance` (0..100) sets how much of the tone range becomes black ink. GraphicPen { length: f64, balance: f64, direction: StrokeDirection }, @@ -339,6 +342,10 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option { charcoal: num(p, "charcoalArea", 6.0).clamp(0.0, 20.0), pressure: num(p, "strokePressure", 2.0).clamp(0.0, 5.0), }, + "sketch.chrome" => PixelFx::Chrome { + detail: num(p, "detail", 4.0).clamp(0.0, 10.0), + smoothness: num(p, "smoothness", 4.0).clamp(0.0, 10.0), + }, "sketch.graphicPen" => PixelFx::GraphicPen { length: num(p, "strokeLength", 5.0).clamp(1.0, 15.0), balance: num(p, "lightDarkBalance", 50.0).clamp(0.0, 100.0), @@ -432,7 +439,11 @@ impl PixelFx { // Content shifted outwards reaches past the bounds by as much as the shift. PixelFx::Glass { distortion, .. } => distort::glass_shift(distortion), PixelFx::OceanRipple { size, magnitude } => distort::ripple_shift(size, magnitude), - PixelFx::UnsharpMask { .. } | PixelFx::ChalkCharcoal { .. } | PixelFx::HalftonePattern { .. } | PixelFx::Reticulation { .. } => 0.0, + PixelFx::UnsharpMask { .. } + | PixelFx::ChalkCharcoal { .. } + | PixelFx::Chrome { .. } + | PixelFx::HalftonePattern { .. } + | PixelFx::Reticulation { .. } => 0.0, PixelFx::GlowingEdges { width, smoothness, .. } => width.max(smoothness * 3.0), PixelFx::FilmGrain { .. } | PixelFx::Cutout { .. } @@ -483,6 +494,7 @@ impl PixelFx { PixelFx::ColorHalftone { max_radius, .. } => Some(3.0 * std::f64::consts::SQRT_2 * max_radius), PixelFx::Crystallize { cell } => Some(1.5 * cell), PixelFx::ChalkCharcoal { .. } => Some(sketch::CHALK_CHARCOAL_REACH), + PixelFx::Chrome { smoothness, .. } => Some(sketch::chrome_reach(smoothness)), PixelFx::FilmGrain { .. } | PixelFx::Mezzotint { .. } | PixelFx::HalftonePattern { .. } | PixelFx::Reticulation { .. } => Some(0.0), // Cutout's palette comes from the whole object. PixelFx::Cutout { .. } => None, @@ -540,6 +552,7 @@ impl PixelFx { PixelFx::OceanRipple { size, magnitude } => distort::ocean_ripple(px, w, h, space, size, magnitude), PixelFx::UnsharpMask { amount, radius, threshold } => sharpen::unsharp(px, w, h, amount, to_px(radius), threshold), PixelFx::ChalkCharcoal { chalk, charcoal, pressure } => sketch::chalk_charcoal(px, w, h, space, chalk, charcoal, pressure), + PixelFx::Chrome { detail, smoothness } => sketch::chrome(px, w, h, space, detail, smoothness), PixelFx::GraphicPen { length, balance, direction } => brushstrokes::graphic_pen(px, w, h, space, length, balance, direction), PixelFx::HalftonePattern { size, contrast, kind } => sketch::halftone_pattern(px, w, h, space, size, contrast, kind), PixelFx::Reticulation { density, foreground, background } => sketch::reticulation(px, w, h, space, density, foreground, background), diff --git a/crates/effects/src/pixel/sketch.rs b/crates/effects/src/pixel/sketch.rs index 323adb5a4..ee6a1e9d8 100644 --- a/crates/effects/src/pixel/sketch.rs +++ b/crates/effects/src/pixel/sketch.rs @@ -1,19 +1,24 @@ -//! Effect › Sketch (Chalk & Charcoal, Halftone Pattern and Reticulation; Graphic Pen lives in +//! Effect › Sketch (Chalk & Charcoal, Chrome, Halftone Pattern and Reticulation; Graphic Pen lives in //! [`super::brushstrokes`]). Original filters built from public behaviour documentation: //! (Sketch effects redraw in //! black and white) and the public Photoshop Elements Artistic filters guide. Visual //! approximations, not proprietary reproductions. -use std::f64::consts::TAU; +use std::f64::consts::{FRAC_1_SQRT_2, TAU}; + +use vectorcraft_geom::Vec2; use super::edges::sobel; use super::pixelate::{Place, cell, rand, value_noise}; -use super::texture::{Tones, luma, premultiply, straight}; +use super::texture::{Tones, luma, premultiply, smooth_noise, straight}; use super::{PixelSpace, blur_plane, pick}; const SALT_CHALK: u64 = 216; +const SALT_CHROME: u64 = 0xC40_4D45; const EDGE_SOFT: f64 = 0.8; const TONE_SOFT: f64 = 1.0; +/// Light from the top left (document axes, y down), as on polished relief surfaces. +const CHROME_LIGHT: Vec2 = Vec2::new(-FRAC_1_SQRT_2, -FRAC_1_SQRT_2); /// How far (document units) Chalk & Charcoal samples softened tones and edges. pub(super) const CHALK_CHARCOAL_REACH: f64 = 3.0 * TONE_SOFT + 1.0 + 3.0 * EDGE_SOFT; @@ -138,6 +143,70 @@ pub(super) fn chalk_charcoal(px: &mut [[u8; 4]], w: usize, h: usize, space: &Pix }); } +/// How far (document units) Chrome reaches for `smoothness` (0..10): 3 σ of the height-field blur +/// plus the Sobel neighbours. +pub(super) fn chrome_reach(smoothness: f64) -> f64 { + let smoothness = smoothness.clamp(0.0, 10.0); + 3.0 * (0.4 + 0.55 * smoothness) + 1.0 +} + +/// Chrome: the object as a polished chrome surface lit from the top left. `detail` (0..10) adds +/// finer relief; `smoothness` (0..10) softens the height field before the highlights and shadows +/// are drawn. Coverage is kept. +pub(super) fn chrome(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, detail: f64, smoothness: f64) { + if w == 0 || h == 0 { + return; + } + let detail = detail.clamp(0.0, 10.0); + let smoothness = smoothness.clamp(0.0, 10.0); + let Some(place) = Place::new(space, w, h) else { return }; + let sigma = (0.4 + smoothness * 0.55) / place.px.max(1e-9); + let cell = (14.0 - detail * 1.2).max(1.5); + let bump = (detail / 10.0) * 0.4; + let strength = 2.5 + detail * 0.35; + let mut height: Vec = Vec::with_capacity(w * h); + let mut alpha: Vec = Vec::with_capacity(w * h); + for y in 0..h { + for x in 0..w { + let p = px[y * w + x]; + let a = f32::from(p[3]) / 255.0; + alpha.push(a); + if a <= 0.0 { + height.push(0.0); + continue; + } + let q = place.at(x, y); + let base = f64::from(luma(straight(p))); + let n = if bump > 0.0 { bump * smooth_noise(SALT_CHROME, q, cell) } else { 0.0 }; + height.push((base + n).clamp(0.0, 1.0) as f32); + } + } + blur_plane(&mut height, w, h, sigma); + blur_plane(&mut alpha, w, h, sigma); + let tone: Vec = height + .iter() + .zip(&alpha) + .map(|(h, a)| if *a > 1e-4 { f64::from(*h / *a).clamp(0.0, 1.0) } else { 0.0 }) + .collect(); + let px_unit = place.px.max(1e-9); + place.each(px, |x, y, p| { + if p[3] == 0 { + return; + } + let at = |dx: isize, dy: isize| { + let (xx, yy) = ((x as isize + dx).clamp(0, w as isize - 1) as usize, (y as isize + dy).clamp(0, h as isize - 1) as usize); + tone[yy * w + xx] + }; + let h0 = tone[y * w + x]; + let gx = 0.5 * (at(1, 0) - at(-1, 0)) / px_unit; + let gy = 0.5 * (at(0, 1) - at(0, -1)) / px_unit; + let face = -(gx * CHROME_LIGHT.x + gy * CHROME_LIGHT.y); + let spec = (face * strength + (h0 - 0.5) * 1.5).tanh(); + let grey = (0.5 + 0.5 * spec).clamp(0.0, 1.0); + *p = premultiply([grey as f32; 3], f32::from(p[3]) / 255.0); + }); +} + /// Reticulation: the object redrawn in black ink on white paper through a high-contrast film-grain /// screen. `density` (0..50) sets how fine the grain is; `foreground` and `background` (0..50 each) /// pull shadows toward black and highlights toward white before the grain decides ink. Coverage is diff --git a/crates/effects/src/pixel/tests.rs b/crates/effects/src/pixel/tests.rs index ac210997a..707dea4f9 100644 --- a/crates/effects/src/pixel/tests.rs +++ b/crates/effects/src/pixel/tests.rs @@ -2187,6 +2187,12 @@ fn halftone_draws_in_black_and_white_only() { } } +fn chrome(p: serde_json::Value, src: &[u8], w: usize, h: usize, s: &PixelSpace) -> Vec { + let mut out = src.to_vec(); + fx("sketch.chrome", p).apply(&mut out, w, h, s); + out +} + /// Reticulation of `p` over `w` × `h` pixels of `src`, laid out by `s`. fn reticulation(p: serde_json::Value, src: &[u8], w: usize, h: usize, s: &PixelSpace) -> Vec { let mut out = src.to_vec(); @@ -2194,6 +2200,39 @@ fn reticulation(p: serde_json::Value, src: &[u8], w: usize, h: usize, s: &PixelS out } +#[test] +fn chrome_params_take_defaults_and_stay_in_range() { + assert_eq!(fx("sketch.chrome", json!({})), PixelFx::Chrome { detail: 4.0, smoothness: 4.0 }); + assert_eq!(fx("sketch.chrome", json!({"detail": -1, "smoothness": 99})), PixelFx::Chrome { detail: 0.0, smoothness: 10.0 }); + assert_eq!(fx("sketch.chrome", json!({"detail": "x"})), fx("sketch.chrome", json!({}))); + let doc = crate::effect_info("sketch.chrome").unwrap().params; + assert!(doc.contains("detail") && doc.contains("smoothness"), "{doc}"); + let f = fx("sketch.chrome", json!({})); + assert_eq!(f.outset(Rect::new(0.0, 0.0, 100.0, 50.0)), 0.0); + assert_eq!(f.reach(), Some(sketch::chrome_reach(4.0))); + assert_eq!(fx("sketch.chrome", json!({"smoothness": 10})).reach(), Some(sketch::chrome_reach(10.0))); +} + +/// Chrome redraws in greyscale, keeps coverage, and more detail changes the pattern. +#[test] +fn chrome_draws_polished_greyscale_relief() { + let (w, h) = (64, 64); + let src = image(w, h, |x, y| { + let t = ((x + y) * 4) as u8; + [t, t / 2, 255 - t, 255] + }); + let soft = chrome(json!({"detail": 0, "smoothness": 8}), &src, w, h, &space(w, h)); + let sharp = chrome(json!({"detail": 10, "smoothness": 0}), &src, w, h, &space(w, h)); + assert_ne!(soft, sharp); + for out in [&soft, &sharp] { + for (p, q) in out.as_chunks::<4>().0.iter().zip(src.as_chunks::<4>().0) { + assert_eq!(p[3], q[3], "coverage kept"); + assert!(p[0] == p[1] && p[1] == p[2], "greyscale {p:?}"); + } + assert!(share(out, |p| p[0] > 10 && p[0] < 245) > 0.05, "midtones"); + } +} + #[test] fn reticulation_params_take_defaults_and_stay_in_range() { assert_eq!(fx("sketch.reticulation", json!({})), PixelFx::Reticulation { density: 12.0, foreground: 6.0, background: 6.0 }); diff --git a/crates/engine/src/tests_rasterfilters.rs b/crates/engine/src/tests_rasterfilters.rs index b62d0c2d9..31fdcf908 100644 --- a/crates/engine/src/tests_rasterfilters.rs +++ b/crates/engine/src/tests_rasterfilters.rs @@ -1,5 +1,5 @@ //! The Photoshop-style raster effects (Effect › Artistic, Blur › Radial Blur and Smart Blur, Brush Strokes, -//! Distort › Diffuse Glow, Glass and Ocean Ripple, Pixelate › Color Halftone, Crystallize, Mezzotint and Pointillize, Sharpen › Unsharp Mask, Sketch › Halftone Pattern, Texture › +//! Distort › Diffuse Glow, Glass and Ocean Ripple, Pixelate › Color Halftone, Crystallize, Mezzotint and Pointillize, Sharpen › Unsharp Mask, Sketch › Chalk & Charcoal, Chrome, Graphic Pen, Halftone Pattern and Reticulation, Texture › //! Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass and Texturizer, Video): applied and //! edited as commands, listed in the catalogue, drawn on the canvas, and written to PDF and SVG //! as images of the effected object. @@ -49,8 +49,13 @@ const PIXELATE: [(&str, &str); 4] = [("pixelate.colorHalftone", "{}"), ("pixelate.crystallize", "{}"), ("pixelate.mezzotint", "{}"), ("pixelate.pointillize", "{}")]; /// Effect › Sketch, at their defaults. -const SKETCH: [(&str, &str); 4] = - [("sketch.chalkCharcoal", "{}"), ("sketch.graphicPen", "{}"), ("sketch.halftonePattern", "{}"), ("sketch.reticulation", "{}")]; +const SKETCH: [(&str, &str); 5] = [ + ("sketch.chalkCharcoal", "{}"), + ("sketch.chrome", "{}"), + ("sketch.graphicPen", "{}"), + ("sketch.halftonePattern", "{}"), + ("sketch.reticulation", "{}"), +]; /// Effect › Texture, at their defaults. const TEXTURE: [(&str, &str); 6] = [ diff --git a/crates/engine/tests/command_sweep.rs b/crates/engine/tests/command_sweep.rs index 1b65b1aad..1523c530e 100644 --- a/crates/engine/tests/command_sweep.rs +++ b/crates/engine/tests/command_sweep.rs @@ -334,6 +334,7 @@ fn structured_junk() { ("effect.apply", json!({"effect": "artistic.cutout", "params": {"levels": 1e308, "edgeSimplicity": "NaN", "edgeFidelity": -1e308}})), ("effect.apply", json!({"effect": "artistic.neonGlow", "params": {"glowSize": -1e308, "glowBrightness": "1e999", "color": [1]}})), ("effect.apply", json!({"effect": "artistic.posterEdges", "params": {"edgeThickness": 1e308, "edgeIntensity": [], "posterization": -7}})), + ("effect.apply", json!({"effect": "sketch.chrome", "params": {"detail": -1e308, "smoothness": "NaN"}})), ("effect.apply", json!({"effect": "sketch.graphicPen", "params": {"strokeLength": -1e308, "lightDarkBalance": "NaN", "strokeDirection": 3}})), ("effect.apply", json!({"effect": "sketch.halftonePattern", "params": {"size": -1e308, "contrast": "NaN", "patternType": 3}})), ("effect.apply", json!({"effect": "sketch.reticulation", "params": {"density": "NaN", "foregroundLevel": -1e308, "backgroundLevel": 1e308}})), diff --git a/crates/ui-egui/src/tests_labels.rs b/crates/ui-egui/src/tests_labels.rs index 98f23e541..b5d90df35 100644 --- a/crates/ui-egui/src/tests_labels.rs +++ b/crates/ui-egui/src/tests_labels.rs @@ -171,23 +171,24 @@ fn effect_menu_lists_the_texture_filters_between_sharpen_and_video() { assert!(at("Sharpen") < at("Texture") && at("Texture") < at("Video")); } -/// Effect › Sketch, between Sharpen and the raster Stylize: Chalk & Charcoal and Halftone Pattern open their dialogs. +/// Effect › Sketch, between Sharpen and the raster Stylize: the raster sketch filters open their dialogs. #[test] fn effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize() { let app = app(); let entries = menus::menu_entries(&app); let sketch: Vec<_> = entries.iter().filter(|e| e.path == ["Effect", "Sketch"]).collect(); let labels: Vec<&str> = sketch.iter().map(|e| e.label.as_str()).collect(); - assert_eq!(labels, ["Chalk & Charcoal…", "Graphic Pen…", "Halftone Pattern…", "Reticulation…"]); - assert_eq!(sketch[0].command.as_deref(), Some("effect.dialog")); + assert_eq!(labels, ["Chalk & Charcoal…", "Chrome…", "Graphic Pen…", "Halftone Pattern…", "Reticulation…"]); + assert!(sketch.iter().all(|e| e.command.as_deref() == Some("effect.dialog"))); assert_eq!(sketch[0].params, json!({"effect": "sketch.chalkCharcoal"})); - assert_eq!(sketch[1].params, json!({"effect": "sketch.graphicPen"})); - assert_eq!(sketch[2].params, json!({"effect": "sketch.halftonePattern"})); - assert_eq!(sketch[3].params, json!({"effect": "sketch.reticulation"})); + assert_eq!(sketch[1].params, json!({"effect": "sketch.chrome"})); + assert_eq!(sketch[2].params, json!({"effect": "sketch.graphicPen"})); + assert_eq!(sketch[3].params, json!({"effect": "sketch.halftonePattern"})); + assert_eq!(sketch[4].params, json!({"effect": "sketch.reticulation"})); let at = |label: &str| entries.iter().position(|e| e.path[0] == "Effect" && e.label == label).unwrap(); - assert!(at("Unsharp Mask…") < at("Chalk & Charcoal…") && at("Chalk & Charcoal…") < at("Graphic Pen…")); - assert!(at("Graphic Pen…") < at("Halftone Pattern…") && at("Halftone Pattern…") < at("Reticulation…")); - assert!(at("Reticulation…") < at("Glowing Edges…")); + assert!(at("Unsharp Mask…") < at("Chalk & Charcoal…") && at("Chalk & Charcoal…") < at("Chrome…")); + assert!(at("Chrome…") < at("Graphic Pen…") && at("Graphic Pen…") < at("Halftone Pattern…")); + assert!(at("Halftone Pattern…") < at("Reticulation…") && at("Reticulation…") < at("Glowing Edges…")); } /// Effect › Stylize under Raster Effects, between Sharpen and Texture: Glowing Edges opens its diff --git a/docs/effects-parity.md b/docs/effects-parity.md index 313cfd44e..8da75b6a7 100644 --- a/docs/effects-parity.md +++ b/docs/effects-parity.md @@ -1,6 +1,6 @@ # Effects parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 36/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 30/57, 27 remaining (22–38 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (G2: Sketch › Chrome; Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 37/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 30/57, 27 remaining (22–38 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x) Every entry of Illustrator's Effect menu, by its public feature name, against VectorCraft's effects catalogue (`effect_catalog()` in `crates/effects/src/lib.rs`) and the Effect menu built from it @@ -44,7 +44,7 @@ its resolution, PDF export warns once per object with the requested and the lowe | Distort | Diffuse Glow, Glass, Ocean Ripple | 3/3 | — | | Pixelate | Color Halftone, Crystallize, Mezzotint, Pointillize | 4/4 | — | | Sharpen | Unsharp Mask | 1/1 | — | -| Sketch | Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Conté Crayon, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Torn Edges, Water Paper | 4/14 (approximate Chalk & Charcoal, Graphic Pen, Halftone Pattern, Reticulation; defaults unverified) | Bas Relief, Charcoal, Chrome, Conté Crayon, Note Paper, Photocopy, Plaster, Stamp, Torn Edges, Water Paper | +| Sketch | Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Conté Crayon, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Torn Edges, Water Paper | 5/14 (approximate Chalk & Charcoal, Chrome, Graphic Pen, Halftone Pattern, Reticulation; Chalk & Charcoal and Graphic Pen defaults unverified) | Bas Relief, Charcoal, Conté Crayon, Note Paper, Photocopy, Plaster, Stamp, Torn Edges, Water Paper | | Stylize | Glowing Edges | 1/1 | — | | Texture | Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass, Texturizer | 6/6 | Texturizer's Load Texture | | Video | De-Interlace, NTSC Colors | 2/2 | — | diff --git a/docs/gaps.md b/docs/gaps.md index 938d29f0c..20d83a5e8 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -226,8 +226,8 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall - **Missing:** 23 of 57 filters (the remaining 11 Artistic filters and 12 of Sketch's 14), the Effect Gallery dialog, Load Texture for Texturizer. Measured in [effects-parity.md](effects-parity.md). -- **Done:** the pixel-filter pipeline and 34 filters (Artistic: Cutout, Film Grain, Neon Glow, Poster Edges, Blur, Brush Strokes, Distort, Pixelate, Sharpen, Sketch: - Graphic Pen and Halftone Pattern, Stylize, Texture, Video), Brush Strokes on 2026-10-11, Film Grain on 2026-10-12, Halftone Pattern on 2026-10-16, Graphic Pen on 2026-10-18, Cutout, Neon Glow and Poster Edges on 2026-10-16, and Smart Blur's Edge Only and Overlay Edge +- **Done:** the pixel-filter pipeline and 35 filters (Artistic: Cutout, Film Grain, Neon Glow, Poster Edges, Blur, Brush Strokes, Distort, Pixelate, Sharpen, Sketch: + Chalk & Charcoal, Chrome, Graphic Pen and Halftone Pattern, Stylize, Texture, Video), Brush Strokes on 2026-10-11, Film Grain on 2026-10-12, Halftone Pattern on 2026-10-16, Graphic Pen on 2026-10-18, Chrome on 2026-10-19, Cutout, Neon Glow and Poster Edges on 2026-10-16, and Smart Blur's Edge Only and Overlay Edge modes. - **Estimate:** 21–35 h (measured 0.7–1.2 h per filter); parallelizes well.