diff --git a/crates/effects/src/lib.rs b/crates/effects/src/lib.rs index 2e3c64243..0687d57fa 100644 --- a/crates/effects/src/lib.rs +++ b/crates/effects/src/lib.rs @@ -155,6 +155,7 @@ fn lengths_of(id: &str) -> Lengths { "texture.stainedGlass" => always(&["cellSize"]), "sketch.graphicPen" => always(&["strokeLength"]), "sketch.halftonePattern" => always(&["size"]), + "sketch.notePaper" => always(&["relief"]), RASTERIZE => always(&["addAround"]), _ => Lengths::default(), } @@ -535,9 +536,16 @@ pub fn effect_catalog() -> Vec { "sketch.halftonePattern", "Halftone Pattern…", SKETCH, - "{size: pt 1..12 (1; how far apart the screen's dots, rings or lines are), contrast: 0..50 (5; 50 draws pure black and white), patternType: \"circle\"|\"dot\"|\"line\" (\"dot\"; rings round the object's centre, dots on a square grid, horizontal lines)} redraws the object in black ink on white paper through a halftone screen whose ink covers as much as the colour is dark, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.", + "{size: pt 1..12 (1; how far apart the screen's dots, rings or lines are), contrast: 0..50 (5; 50 draws pure black and white), patternType: \"circle\"|\"dot\"|\"line\" (\"dot\"; rings round the object's centre, dots on a square grid, horizontal lines)} redraws the object in black ink on white paper through a halftone screen whose ink covers as much as the colour is dark, keeping its coverage. Original visual approximation from the public Sketch effects reference; slider ranges follow the documented control names, numeric defaults are chosen for VectorCraft and are not verified against proprietary defaults (the public help pages give no numbers).", json!({"size": 1.0, "contrast": 5.0, "patternType": "dot"}), ), + r( + "sketch.notePaper", + "Note Paper…", + SKETCH, + "{imageBalance: 0..50 (25; lower tones darken the emboss, higher lighten it), graininess: 0..10 (0; fibrous paper texture), relief: 0..50 (4; emboss strength in points)} embosses the object's tones on warm paper, keeping its coverage. Original visual approximation from the public Sketch effects reference; slider ranges follow the documented control names, numeric defaults are chosen for VectorCraft and are not verified against proprietary defaults (the public help pages give no numbers).", + json!({"imageBalance": 25.0, "graininess": 0.0, "relief": 4.0}), + ), r( "sketch.photocopy", "Photocopy…", diff --git a/crates/effects/src/pixel/mod.rs b/crates/effects/src/pixel/mod.rs index 365a41138..051278eb1 100644 --- a/crates/effects/src/pixel/mod.rs +++ b/crates/effects/src/pixel/mod.rs @@ -2,7 +2,7 @@ //! Poster Edges, Smudge Stick, Sponge and Watercolor, Blur › Radial Blur and Smart Blur, Brush Strokes › Accented Edges, Angled Strokes, //! Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes and Sumi-e, Distort › Diffuse //! Glow, Glass and Ocean Ripple, Pixelate › Color Halftone, Crystallize, Mezzotint and Pointillize, -//! Sharpen › Unsharp Mask, Sketch › Chalk & Charcoal, Graphic Pen, Halftone Pattern, Plaster and +//! Sharpen › Unsharp Mask, Sketch › Chalk & Charcoal, Graphic Pen, Halftone Pattern, Note Paper, Plaster and //! Reticulation, Stylize › //! Glowing Edges, Texture //! › Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass and Texturizer, and Video › @@ -43,7 +43,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; 45] = [ +pub const PIXEL_EFFECTS: [&str; 46] = [ "artistic.cutout", "artistic.filmGrain", "artistic.neonGlow", @@ -76,6 +76,7 @@ pub const PIXEL_EFFECTS: [&str; 45] = [ "sketch.chrome", "sketch.graphicPen", "sketch.halftonePattern", + "sketch.notePaper", "sketch.photocopy", "sketch.plaster", "sketch.reticulation", @@ -180,6 +181,8 @@ pub enum PixelFx { /// Sketch › Halftone Pattern: black ink on white paper through a `kind` screen of cells `size` /// (1..12) apart, its edges as hard as `contrast` (0..50) says. HalftonePattern { size: f64, contrast: f64, kind: HalftonePattern }, + /// Sketch › Note Paper: embossed tones on fibrous paper (`balance`, `graininess`, `relief`). + NotePaper { balance: f64, graininess: f64, relief: f64 }, /// Sketch › Bas Relief: low relief lit from `light`, keeping the finer features as `detail` /// (1..15) says and rounded by `smoothness` (1..15). BasRelief { detail: f64, smoothness: f64, light: Light }, @@ -404,6 +407,11 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option { contrast: num(p, "contrast", 5.0).clamp(0.0, 50.0), kind: HalftonePattern::parse(text(p, "patternType", "dot")), }, + "sketch.notePaper" => PixelFx::NotePaper { + balance: num(p, "imageBalance", 25.0).clamp(0.0, 50.0), + graininess: num(p, "graininess", 0.0).clamp(0.0, 10.0), + relief: num(p, "relief", 4.0).clamp(0.0, 50.0), + }, "sketch.basRelief" => PixelFx::BasRelief { detail: num(p, "detail", 13.0).clamp(1.0, 15.0), smoothness: num(p, "smoothness", 3.0).clamp(1.0, 15.0), @@ -512,6 +520,7 @@ impl PixelFx { | PixelFx::ChalkCharcoal { .. } | PixelFx::Chrome { .. } | PixelFx::HalftonePattern { .. } + | PixelFx::NotePaper { .. } | PixelFx::Plaster { .. } | PixelFx::Reticulation { .. } => 0.0, PixelFx::BasRelief { .. } | PixelFx::Charcoal { .. } | PixelFx::Photocopy { .. } | PixelFx::Stamp { .. } => 0.0, @@ -575,7 +584,11 @@ impl PixelFx { 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), + PixelFx::FilmGrain { .. } + | PixelFx::Mezzotint { .. } + | PixelFx::HalftonePattern { .. } + | PixelFx::NotePaper { .. } + | PixelFx::Reticulation { .. } => Some(0.0), PixelFx::Plaster { smoothness, .. } => Some(sketch::plaster_reach(smoothness)), // Cutout's palette comes from the whole object. PixelFx::Cutout { .. } => None, @@ -639,6 +652,7 @@ impl PixelFx { 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::NotePaper { balance, graininess, relief } => sketch::note_paper(px, w, h, space, balance, graininess, relief), PixelFx::BasRelief { detail, smoothness, light } => sketch_ink::bas_relief(px, w, h, space, detail, smoothness, light), PixelFx::Charcoal { thickness, detail, balance } => sketch_ink::charcoal(px, w, h, space, thickness, detail, balance), PixelFx::Photocopy { detail, darkness } => sketch_ink::photocopy(px, w, h, space, detail, darkness), diff --git a/crates/effects/src/pixel/sketch.rs b/crates/effects/src/pixel/sketch.rs index 476363db3..9e8851586 100644 --- a/crates/effects/src/pixel/sketch.rs +++ b/crates/effects/src/pixel/sketch.rs @@ -1,4 +1,4 @@ -//! Effect › Sketch (Chalk & Charcoal, Chrome, Halftone Pattern, Plaster and Reticulation; Graphic Pen lives in +//! Effect › Sketch (Chalk & Charcoal, Chrome, Halftone Pattern, Note Paper, Plaster 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 @@ -100,6 +100,65 @@ pub(super) fn halftone_pattern(px: &mut [[u8; 4]], w: usize, h: usize, space: &P }); } +/// Note Paper: the object embossed on fibrous paper. `balance` (0..50) shifts tones toward +/// highlights or shadows around the middle; `graininess` (0..10) adds paper fibre; `relief` +/// (0..50, points) is how strongly the tones stand out in the light. Coverage is kept. +pub(super) fn note_paper(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, balance: f64, graininess: f64, relief: f64) { + if !(balance.is_finite() && graininess.is_finite() && relief.is_finite()) { + return; + } + let Some(place) = Place::new(space, w, h) else { return }; + let height: Vec = px + .iter() + .map(|p| { + if p[3] == 0 { + 0.0 + } else { + let mut l = f64::from(luma(straight(*p))); + l += (balance - 25.0) / 125.0; + l.clamp(0.0, 1.0) as f32 + } + }) + .collect(); + let sample = |x: i32, y: i32| -> f32 { + if x < 0 || y < 0 || x >= w as i32 || y >= h as i32 { + return 0.0; + } + height.get(y as usize * w + x as usize).copied().unwrap_or(0.0) + }; + let emboss = relief / 50.0; + let grain = graininess / 10.0; + let paper = [0.95_f32, 0.93, 0.88]; + let light = (-0.6_f64, -0.6, 0.8); + let ln = (light.0 * light.0 + light.1 * light.1 + light.2 * light.2).sqrt().max(1e-9); + place.each(px, |x, y, p| { + if p[3] == 0 { + return; + } + let xi = x as i32; + let yi = y as i32; + let h0 = height.get(y * w + x).copied().unwrap_or(0.0); + let dx = (sample(xi + 1, yi) - sample(xi - 1, yi)) as f64; + let dy = (sample(xi, yi + 1) - sample(xi, yi - 1)) as f64; + let dz = emboss * 0.12; + let nx = -dx; + let ny = -dy; + let nz = dz; + let nn = (nx * nx + ny * ny + nz * nz).sqrt().max(1e-9); + let shade = ((nx * light.0 + ny * light.1 + nz * light.2) / (nn * ln) * 0.5 + 0.5).clamp(0.0, 1.0); + let tone = (0.35 + 0.65 * f64::from(h0)) * shade; + let q = place.at(x, y); + let fibre = if grain > 0.0 { + value_noise(0x4e4f_5450, q.x * 0.35, q.y * 0.35) * grain * 0.18 + } else { + 0.0 + }; + let v = (tone + fibre).clamp(0.0, 1.0) as f32; + let rgb = paper.map(|c| c * v); + *p = premultiply(rgb, f32::from(p[3]) / 255.0); + }); +} + /// Chalk & Charcoal: light areas get chalky highlights and dark areas charcoal strokes and grain; /// `chalk` and `charcoal` (0..20) set how strongly, `pressure` (0..5) the stroke weight. Black /// chalk and charcoal on white paper, coverage kept. diff --git a/crates/effects/src/pixel/tests.rs b/crates/effects/src/pixel/tests.rs index bcbdfac17..7d45556c5 100644 --- a/crates/effects/src/pixel/tests.rs +++ b/crates/effects/src/pixel/tests.rs @@ -2106,6 +2106,44 @@ fn halftone_pattern_params_take_defaults_and_stay_in_range() { assert_eq!(f.reach(), Some(0.0)); } +#[test] +fn note_paper_params_take_defaults_and_stay_in_range() { + assert_eq!(fx("sketch.notePaper", json!({})), PixelFx::NotePaper { balance: 25.0, graininess: 0.0, relief: 4.0 }); + assert_eq!( + fx("sketch.notePaper", json!({"imageBalance": -1, "graininess": 20, "relief": 100})), + PixelFx::NotePaper { balance: 0.0, graininess: 10.0, relief: 50.0 } + ); + let doc = crate::effect_info("sketch.notePaper").unwrap().params; + assert!(doc.contains("imageBalance") && doc.contains("graininess") && doc.contains("relief"), "{doc}"); + let f = fx("sketch.notePaper", json!({"relief": 12})); + assert_eq!(f.outset(Rect::new(0.0, 0.0, 100.0, 50.0)), 0.0); + assert_eq!(f.reach(), Some(0.0)); +} + +/// Note Paper embosses a horizontal gradient and graininess adds fibre. +#[test] +fn note_paper_embosses_a_gradient_and_grain_changes_pixels() { + let (w, h) = (32, 32); + let src = image(w, h, |x, _| grey((x * 255 / (w - 1)) as u8)); + let flat = { + let mut out = src.clone(); + fx("sketch.notePaper", json!({"relief": 0, "graininess": 0})).apply(&mut out, w, h, &space(w, h)); + out + }; + let embossed = { + let mut out = src.clone(); + fx("sketch.notePaper", json!({"relief": 50, "graininess": 0})).apply(&mut out, w, h, &space(w, h)); + out + }; + assert_ne!(flat, embossed, "relief changes the emboss"); + let grainy = { + let mut out = src.clone(); + fx("sketch.notePaper", json!({"relief": 0, "graininess": 10})).apply(&mut out, w, h, &space(w, h)); + out + }; + assert_ne!(flat, grainy, "graininess adds fibre"); +} + /// Line's rows repeat every Size points: Size pixels at 72 ppi, twice as many at 144 ppi. #[test] fn halftone_line_period_is_the_size_at_any_resolution() { diff --git a/crates/engine/src/tests_rasterfilters.rs b/crates/engine/src/tests_rasterfilters.rs index 387b78644..fa08af258 100644 --- a/crates/engine/src/tests_rasterfilters.rs +++ b/crates/engine/src/tests_rasterfilters.rs @@ -52,13 +52,14 @@ const PIXELATE: [(&str, &str); 4] = [("pixelate.colorHalftone", "{}"), ("pixelate.crystallize", "{}"), ("pixelate.mezzotint", "{}"), ("pixelate.pointillize", "{}")]; /// Effect › Sketch, at their defaults. -const SKETCH: [(&str, &str); 10] = [ +const SKETCH: [(&str, &str); 11] = [ ("sketch.basRelief", "{}"), ("sketch.chalkCharcoal", "{}"), ("sketch.charcoal", "{}"), ("sketch.chrome", "{}"), ("sketch.graphicPen", "{}"), ("sketch.halftonePattern", "{}"), + ("sketch.notePaper", "{}"), ("sketch.photocopy", "{}"), ("sketch.plaster", "{}"), ("sketch.reticulation", "{}"), diff --git a/crates/ui-egui/src/tests_labels.rs b/crates/ui-egui/src/tests_labels.rs index b197ea21a..8d62e4db1 100644 --- a/crates/ui-egui/src/tests_labels.rs +++ b/crates/ui-egui/src/tests_labels.rs @@ -187,6 +187,7 @@ fn effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize() { "Chrome…", "Graphic Pen…", "Halftone Pattern…", + "Note Paper…", "Photocopy…", "Plaster…", "Reticulation…", @@ -200,14 +201,16 @@ fn effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize() { assert_eq!(sketch[3].params, json!({"effect": "sketch.chrome"})); assert_eq!(sketch[4].params, json!({"effect": "sketch.graphicPen"})); assert_eq!(sketch[5].params, json!({"effect": "sketch.halftonePattern"})); - assert_eq!(sketch[6].params, json!({"effect": "sketch.photocopy"})); - assert_eq!(sketch[7].params, json!({"effect": "sketch.plaster"})); - assert_eq!(sketch[8].params, json!({"effect": "sketch.reticulation"})); - assert_eq!(sketch[9].params, json!({"effect": "sketch.stamp"})); + assert_eq!(sketch[6].params, json!({"effect": "sketch.notePaper"})); + assert_eq!(sketch[7].params, json!({"effect": "sketch.photocopy"})); + assert_eq!(sketch[8].params, json!({"effect": "sketch.plaster"})); + assert_eq!(sketch[9].params, json!({"effect": "sketch.reticulation"})); + assert_eq!(sketch[10].params, json!({"effect": "sketch.stamp"})); 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("Chrome…")); assert!(at("Chrome…") < at("Graphic Pen…") && at("Graphic Pen…") < at("Halftone Pattern…")); - assert!(at("Halftone Pattern…") < at("Plaster…") && at("Plaster…") < at("Reticulation…")); + assert!(at("Halftone Pattern…") < at("Note Paper…") && at("Note Paper…") < at("Plaster…")); + assert!(at("Plaster…") < at("Reticulation…")); assert!(at("Reticulation…") < at("Glowing Edges…")); } diff --git a/docs/effects-parity.md b/docs/effects-parity.md index 2e102c699..4fa4e6c17 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-20 · **Change:** minor (G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); 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 45/57, 12 remaining (8–14 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-20 · **Change:** minor (G2: Effect › Sketch › Note Paper; Halftone Pattern and Note Paper defaults documented as unverified VectorCraft choices; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 46/57, 11 remaining (8–14 h); 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 46/57, 11 remaining (8–14 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 @@ -12,7 +12,7 @@ Part of [target-app-parity.md](target-app-parity.md); open work is in [gaps.md]( | Group | Illustrator | VectorCraft | % | Remaining (h) | |---|---:|---:|---:|---:| | Illustrator effects (vector and stylize) | 50 | 46 | 92% | 6–10 | -| Photoshop effects (raster, Effect Gallery) | 57 | 45 | 79% | 8–14 | +| Photoshop effects (raster, Effect Gallery) | 57 | 46 | 81% | 8–14 | | 3D and Materials | 4 (+3 Classic) | 4 (initial: Extrude & Bevel, Revolve, Inflate, Rotate) | ~25% | 25–45 | | Beyond Illustrator: Effect › Color Adjustments (6), effect plug-ins (WebAssembly) | — | 6 + plug-ins | — | — | @@ -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 | 10/14 (approximate Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Photocopy, Plaster, Reticulation, Stamp; Chalk & Charcoal and Graphic Pen defaults unverified) | Conté Crayon, Note Paper, 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 | 11/14 (approximate Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp; Chalk & Charcoal, Graphic Pen and Note Paper defaults unverified) | Conté Crayon, 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 | — | @@ -149,6 +149,16 @@ VectorCraft, not asserted as measured proprietary defaults. Tests: `pixel::tests `tests_rasterfilters`, `command_sweep`, `dialogs::tests::raster_filter_dialogs_preview_and_apply` and `tests_labels::effect_menu_lists_the_sketch_filters_*`. +Effect › Sketch › Halftone Pattern and Note Paper (`sketch.halftonePattern`, `sketch.notePaper`) are +original black-and-white filters guided by [Illustrator's public Sketch effects summary](https://helpx.adobe.com/illustrator/using/summary-effects.html) +and [effects overview](https://helpx.adobe.com/illustrator/using/effects.html) (Halftone Size in points at any Document Raster Effects resolution). +The public help pages name each control but publish no numeric defaults or slider limits; VectorCraft's ranges +follow those control names and the emboss/halftone behaviour described there, while dialog defaults are chosen +for VectorCraft and are not verified against proprietary defaults. Evidence: `crates/effects/src/pixel/sketch.rs`, +`halftone_*` and `note_paper_*` in `crates/effects/src/pixel/tests.rs`, `SKETCH` and +`halftone_pattern_settings_survive_a_save_and_open` in `crates/engine/src/tests_rasterfilters.rs`, and +`tests_labels::effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize`. + ## 3D and Materials | Item | Ours | @@ -168,6 +178,7 @@ angular cut caps, exact visibility, 3D Classic, Turntable, 3D export) is 25–45 | Date | Change | Summary | |---|---|---| +| 2026-10-20 | minor | G2: Effect › Sketch › Note Paper (Image Balance, Graininess, Relief; embossed tones on fibrous paper); Halftone Pattern and Note Paper parameter ranges follow public control names, numeric defaults are unverified VectorCraft choices (public help gives no numbers); measured Photoshop filters 46/57, Sketch 11/14. | | 2026-10-19 | minor | Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap landed (#2972, #2967, #2976, #3034): 36/57 Photoshop effects (estimated; defaults unverified) | | 2026-10-16 | minor | Zig Zag with Smooth points draws a continuous wave: every peak and valley is a smooth point with handles along the path, a third of the way to the next ridge, so each span follows a half cosine through the path instead of a straight diagonal with rounded tips, also at the ends of open paths and for ridges much taller than they are apart (#2343); score unchanged (estimated) | | 2026-10-16 | minor | G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor (live, with their documented options; original approximations); measured Photoshop filters 36/57 with Cutout, Neon Glow and Poster Edges, 21 remaining (19–31 h). |