From 03a2da13ba7789214432d458ba3a30106285bc90 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 22 Oct 2026 00:30:39 +0700 Subject: [PATCH] =?UTF-8?q?G2:=20Effect=20=E2=80=BA=20Artistic=20=E2=80=BA?= =?UTF-8?q?=20Colored=20Pencil=20raster=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed from #3028 onto main. --- crates/effects/src/lib.rs | 7 ++++ crates/effects/src/pixel/artistic.rs | 52 +++++++++++++++++++++++- crates/effects/src/pixel/mod.rs | 16 +++++++- crates/effects/src/pixel/tests.rs | 30 +++++++++++++- crates/engine/src/tests_rasterfilters.rs | 3 +- crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/da.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/fi.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/id.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ka.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 | 2 + crates/ui-egui/src/i18n/sv.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + crates/ui-egui/src/tests_labels.rs | 5 ++- docs/effects-parity.md | 4 +- docs/gaps.md | 9 ++-- 31 files changed, 158 insertions(+), 14 deletions(-) diff --git a/crates/effects/src/lib.rs b/crates/effects/src/lib.rs index d5aea1d79..691527682 100644 --- a/crates/effects/src/lib.rs +++ b/crates/effects/src/lib.rs @@ -350,6 +350,13 @@ pub fn effect_catalog() -> Vec { ), r("stylize.feather", "Feather…", STYLIZE, "{radius: pt (5)}", json!({"radius": 5.0})), r("blur.gaussian", "Gaussian Blur…", BLUR, "{radius: pt (5)}", json!({"radius": 5.0})), + r( + "artistic.coloredPencil", + "Colored Pencil…", + ARTISTIC, + "{pencilWidth: 1..24 (5; stroke spacing in document units), strokePressure: 0..15 (5; how heavily the strokes darken), paperBrightness: 0..50 (25; lifts lighter areas toward the paper)} redraws the object in coloured pencil strokes along its edges, keeping its hues and coverage. Original visual approximation; chosen VectorCraft defaults.", + json!({"pencilWidth": 5.0, "strokePressure": 5.0, "paperBrightness": 25.0}), + ), r( "artistic.cutout", "Cutout…", diff --git a/crates/effects/src/pixel/artistic.rs b/crates/effects/src/pixel/artistic.rs index f14671a12..20b8662aa 100644 --- a/crates/effects/src/pixel/artistic.rs +++ b/crates/effects/src/pixel/artistic.rs @@ -1,4 +1,4 @@ -//! Effect › Artistic (Cutout, Dry Brush, Film Grain, Neon Glow, Paint Daubs, Palette Knife, Plastic Wrap, Poster +//! Effect › Artistic (Colored Pencil, Cutout, Dry Brush, Film Grain, Neon Glow, Paint Daubs, Palette Knife, Plastic Wrap, Poster //! Edges, Rough Pastels, Smudge Stick, Sponge, Underpainting and Watercolor). Original filters built from //! public behaviour documentation: //! and @@ -71,6 +71,7 @@ impl PaintDaubBrush { pub(super) fn paint_daub_radius(size: f64) -> f64 { size.clamp(1.0, 50.0) * 0.48 } +const SALT_PENCIL: u64 = 304; /// Keep the last cell's four corner values: magnified adjacent pixels often share a cell. /// The cache changes only cost, never the field, and needs constant storage per raster. @@ -219,6 +220,55 @@ fn planes(px: &[[u8; 4]]) -> ([Vec; 3], Vec) { (std::array::from_fn(|k| colours.iter().map(|c| c[k]).collect()), px.iter().map(|p| f32::from(p[3]) / 255.0).collect()) } +/// How far (document units) Colored Pencil reaches at pencil width `width`: soften at +/// `0.12 * width` and the Sobel neighbourhood around it. +pub(super) fn colored_pencil_reach(width: f64) -> f64 { + 3.0 * 0.12 * width.clamp(1.0, 24.0) + 1.0 +} + +/// Colored Pencil: the object redrawn with coloured pencil strokes along local edges, keeping its +/// hues. `width` (1..24) sets the stroke spacing in document units; `pressure` (0..15) darkens +/// along the strokes; `paper` (0..50) lifts lighter areas toward the paper. Coverage is kept. +pub(super) fn colored_pencil(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, width: f64, pressure: f64, paper: f64) { + if !([width, pressure, paper].iter().all(|v| v.is_finite()) && space.px.is_finite() && space.px > 0.0) { + return; + } + let width = width.clamp(1.0, 24.0); + let pressure = (pressure / 15.0).clamp(0.0, 1.0) as f32; + let paper_lift = (paper / 50.0).clamp(0.0, 1.0) as f32; + if pressure == 0.0 && paper_lift == 0.0 { + return; + } + let Some(place) = Place::new(space, w, h) else { return }; + let (mut colour, alpha) = planes(px); + soften(&mut colour, &alpha, w, h, (0.12 * width) / space.px); + let tones: Vec = (0..px.len()).map(|k| luma([colour[0][k], colour[1][k], colour[2][k]])).collect(); + let mut edges = vec![0.0f32; px.len()]; + for (e, s) in edges.iter_mut().zip(sobel(&tones, w, h)) { + *e = (s / 4.0).min(1.0); + } + let spacing = (0.35 * width).max(1.0); + place.each(px, |x, y, p| { + if p[3] == 0 { + return; + } + let k = y * w + x; + let c = [colour[0][k], colour[1][k], colour[2][k]]; + let gx = tone_at(&tones, w, h, x as isize + 1, y as isize) - tone_at(&tones, w, h, x as isize - 1, y as isize); + let gy = tone_at(&tones, w, h, x as isize, y as isize + 1) - tone_at(&tones, w, h, x as isize, y as isize - 1); + let angle = gy.atan2(gx) + std::f32::consts::FRAC_PI_2; + let (dx, dy) = (angle.cos() as f64, angle.sin() as f64); + let q = place.at(x, y); + let along = q.x * dx + q.y * dy; + let grain = value_noise(SALT_PENCIL, along / spacing, (q.x * 0.37 + q.y * 0.11) / spacing); + let stroke = (0.5 + 0.5 * (along / spacing * TAU + grain * 3.0).sin()) as f32; + let ink = pressure * (0.3 + 0.55 * stroke) * (0.55 + 0.45 * edges[k]); + let lift = paper_lift * (1.0 - luma(c)) * 0.35; + let c = c.map(|v| (v * (1.0 - ink * 0.45) + lift * (1.0 - v)).clamp(0.0, 1.0)); + *p = premultiply(c, f32::from(p[3]) / 255.0); + }); +} + /// Cutout: the object as flat pieces of coloured paper. Its colours, softened as `simplicity` /// (0..10) asks and less as `fidelity` (1..3) keeps detail, fall into `levels` (2..8) steps per /// colour channel; each step's piece is painted in the average colour of what falls into it, so diff --git a/crates/effects/src/pixel/mod.rs b/crates/effects/src/pixel/mod.rs index d3734ad75..69b9c3953 100644 --- a/crates/effects/src/pixel/mod.rs +++ b/crates/effects/src/pixel/mod.rs @@ -1,4 +1,4 @@ -//! Photoshop-style raster effects (Effect › Artistic › Cutout, Dry Brush, Film Grain, Neon Glow, Paint Daubs, Plastic Wrap and +//! Photoshop-style raster effects (Effect › Artistic › Colored Pencil, Cutout, Dry Brush, Film Grain, Neon Glow, Paint Daubs, Plastic Wrap and //! Poster Edges, Rough Pastels, 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, @@ -44,7 +44,8 @@ pub use sketch::{HALFTONE_TYPES, HalftonePattern}; pub use texture::{GRAIN_TYPES, Grain, LIGHT_DIRECTIONS, Light, TEXTURES, TEXTURES_LOADED, Texture}; /// The Photoshop-style effect ids (all raster effects, see [`crate::is_raster`]). -pub const PIXEL_EFFECTS: [&str; 52] = [ +pub const PIXEL_EFFECTS: [&str; 53] = [ + "artistic.coloredPencil", "artistic.cutout", "artistic.dryBrush", "artistic.filmGrain", @@ -103,6 +104,9 @@ pub const PIXEL_EFFECTS: [&str; 52] = [ /// are in document units. #[derive(Clone, Debug, PartialEq)] pub enum PixelFx { + /// Artistic › Colored Pencil: coloured strokes `width` (1..24) apart, as heavy as `pressure` + /// (0..15), on paper lifted by `paper` (0..50). + ColoredPencil { width: f64, pressure: f64, paper: f64 }, /// Artistic › Cutout: flat pieces of colour in `levels` (2..8) steps per channel, their /// edges as simple as `simplicity` (0..10) and as faithful as `fidelity` (1..3) say. Cutout { levels: f64, simplicity: f64, fidelity: f64 }, @@ -312,6 +316,11 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option { levels.iter().find(|(name, _)| name.eq_ignore_ascii_case(q)).map_or(default, |(_, v)| *v) }; Some(match id { + "artistic.coloredPencil" => PixelFx::ColoredPencil { + width: num(p, "pencilWidth", 5.0).clamp(1.0, 24.0), + pressure: num(p, "strokePressure", 5.0).clamp(0.0, 15.0), + paper: num(p, "paperBrightness", 25.0).clamp(0.0, 50.0), + }, "artistic.cutout" => PixelFx::Cutout { levels: num(p, "levels", 4.0).round().clamp(2.0, 8.0), simplicity: num(p, "edgeSimplicity", 4.0).clamp(0.0, 10.0), @@ -600,6 +609,7 @@ impl PixelFx { // These three keep the object's coverage. PixelFx::SmudgeStick { .. } | PixelFx::Sponge { .. } | PixelFx::Watercolor { .. } => 0.0, PixelFx::FilmGrain { .. } + | PixelFx::ColoredPencil { .. } | PixelFx::Cutout { .. } | PixelFx::DryBrush { .. } | PixelFx::NeonGlow { .. } @@ -658,6 +668,7 @@ impl PixelFx { // The cells around the pixel's, and the cells around theirs. PixelFx::ColorHalftone { max_radius, .. } => Some(3.0 * std::f64::consts::SQRT_2 * max_radius), PixelFx::Crystallize { cell } => Some(1.5 * cell), + PixelFx::ColoredPencil { width, .. } => Some(artistic::colored_pencil_reach(width)), PixelFx::ChalkCharcoal { .. } => Some(sketch::CHALK_CHARCOAL_REACH), PixelFx::Chrome { smoothness, .. } => Some(sketch::chrome_reach(smoothness)), PixelFx::FilmGrain { .. } @@ -699,6 +710,7 @@ impl PixelFx { let Some(px) = pixels(data, w, h) else { return }; let to_px = |len: f64| len / space.px.max(1e-9); match *self { + PixelFx::ColoredPencil { width, pressure, paper } => artistic::colored_pencil(px, w, h, space, width, pressure, paper), PixelFx::Cutout { levels, simplicity, fidelity } => artistic::cutout(px, w, h, space, levels, simplicity, fidelity), PixelFx::DryBrush { size, detail, texture } => artistic::dry_brush(px, w, h, space, size, detail, texture), PixelFx::FilmGrain { grain, highlight, intensity } => artistic::film_grain(px, w, h, space, grain, highlight, intensity), diff --git a/crates/effects/src/pixel/tests.rs b/crates/effects/src/pixel/tests.rs index edce4b431..b682d7e62 100644 --- a/crates/effects/src/pixel/tests.rs +++ b/crates/effects/src/pixel/tests.rs @@ -2421,6 +2421,12 @@ fn ramp(w: usize, h: usize) -> Vec { #[test] fn artistic_params_take_defaults_and_stay_in_range() { + assert_eq!(fx("artistic.coloredPencil", json!({})), PixelFx::ColoredPencil { width: 5.0, pressure: 5.0, paper: 25.0 }); + assert_eq!( + fx("artistic.coloredPencil", json!({"pencilWidth": 0, "strokePressure": 200, "paperBrightness": -5})), + PixelFx::ColoredPencil { width: 1.0, pressure: 15.0, paper: 0.0 } + ); + assert_eq!(fx("artistic.coloredPencil", json!({})).reach(), Some(artistic::colored_pencil_reach(5.0))); assert_eq!(fx("artistic.cutout", json!({})), PixelFx::Cutout { levels: 4.0, simplicity: 4.0, fidelity: 2.0 }); assert_eq!( fx("artistic.cutout", json!({"levels": 1e308, "edgeSimplicity": -5, "edgeFidelity": "NaN"})), @@ -2484,8 +2490,15 @@ fn artistic_params_take_defaults_and_stay_in_range() { fx("artistic.plasticWrap", json!({"highlightStrength": -1, "detail": 99, "smoothness": 0})), PixelFx::PlasticWrap { highlight: 0.0, detail: 15.0, smoothness: 1.0 } ); - for id in ["artistic.cutout", "artistic.dryBrush", "artistic.neonGlow", "artistic.plasticWrap", "artistic.posterEdges", "artistic.underpainting"] - { + for id in [ + "artistic.coloredPencil", + "artistic.cutout", + "artistic.dryBrush", + "artistic.neonGlow", + "artistic.plasticWrap", + "artistic.posterEdges", + "artistic.underpainting", + ] { assert_eq!(fx(id, json!({})).outset(Rect::new(0.0, 0.0, 40.0, 30.0)), 0.0, "{id} keeps the object's shape"); } assert_eq!(fx("artistic.paintDaubs", json!({})), PixelFx::PaintDaubs { size: 10.0, sharpness: 25.0, kind: PaintDaubBrush::Simple }); @@ -2509,6 +2522,19 @@ fn artistic_params_take_defaults_and_stay_in_range() { assert!(fx("artistic.roughPastels", json!({})).outset(Rect::new(0.0, 0.0, 40.0, 30.0)) > 0.0); } +#[test] +fn colored_pencil_strokes_change_pixels_and_keep_coverage() { + let (w, h) = (48, 32); + let src = ramp(w, h); + let mut out = src.clone(); + fx("artistic.coloredPencil", json!({"strokePressure": 12})).apply(&mut out, w, h, &space(w, h)); + assert_ne!(out, src); + assert!(out.as_chunks::<4>().0.iter().zip(src.as_chunks::<4>().0).all(|(o, s)| o[3] == s[3])); + let mut mild = src.clone(); + fx("artistic.coloredPencil", json!({"strokePressure": 0, "paperBrightness": 0})).apply(&mut mild, w, h, &space(w, h)); + assert_eq!(mild, src, "zero pressure and paper leaves the art unchanged"); +} + #[test] fn artistic_filters_leave_invalid_coordinate_spaces_alone() { let (w, h) = (16, 12); diff --git a/crates/engine/src/tests_rasterfilters.rs b/crates/engine/src/tests_rasterfilters.rs index ca709de50..b5142fc2c 100644 --- a/crates/engine/src/tests_rasterfilters.rs +++ b/crates/engine/src/tests_rasterfilters.rs @@ -31,7 +31,8 @@ const BRUSH_STROKES: [(&str, &str); 8] = [ ]; /// Effect › Artistic, at their defaults. -const ARTISTIC: [(&str, &str); 13] = [ +const ARTISTIC: [(&str, &str); 14] = [ + ("artistic.coloredPencil", "{}"), ("artistic.cutout", "{}"), ("artistic.dryBrush", "{}"), ("artistic.filmGrain", "{}"), diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index c7dbcf346..ffd3cc038 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -4557,6 +4557,8 @@ lighting Light Светлина Reset Ruler Origin Нулиране началото на линийката Artistic Художествени Film Grain… Филмова зърнистост… + Colored Pencil… Цветен молив… + Colored Pencil Цветен молив Cutout… Апликация… Cutout Апликация Dry Brush… Суха четка… diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 69b7b087d..3f23b24ce 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5056,6 +5056,8 @@ imageTrace Outline Obrys DWG (use DXF) DWG (použijte DXF) Artistic Umělecké Film Grain… Filmové zrno… + Colored Pencil… Barevná tužka… + Colored Pencil Barevná tužka Cutout… Výřez… Cutout Výřez Dry Brush… Suchý štětec… diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index f4fc9d500..6ddf99786 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5182,6 +5182,8 @@ swatch Bright Violet Kraftig violet Lock Variables Lås variabler Unlock Variables Lås variabler op Change to Artboard Rulers Skift til kunstpladelinealer + Colored Pencil… Farveblyant… + Colored Pencil Farveblyant Cutout… Udklip… Dry Brush… Tør pensel… Neon Glow… Neonglød… diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index f2954f330..c36145fec 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4637,6 +4637,8 @@ lighting Light Licht Reset Ruler Origin Linealursprung zurücksetzen Artistic Kunstfilter Film Grain… Filmkörnung… + Colored Pencil… Buntstift… + Colored Pencil Buntstift Simple Einfach Light Rough Leicht rau Dark Rough Dunkel rau diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index b530c8469..8566e058e 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2266,6 +2266,8 @@ lighting Light Φως Left Diagonal Αριστερή διαγώνιος Artistic Καλλιτεχνικό Film Grain… Κόκκος φιλμ… + Colored Pencil… Έγχρωμο μολύβι… + Colored Pencil Έγχρωμο μολύβι Cutout… Αποκοπή… Cutout Αποκοπή Dry Brush… Ξηρό πινέλο… diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 877b5826e..0d5ee1098 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4644,6 +4644,8 @@ lighting Light Luz Reset Ruler Origin Restablecer el origen de las reglas Artistic Artístico Film Grain… Granulado de película… + Colored Pencil… Lápiz de color… + Colored Pencil Lápiz de color Cutout… Recorte… Cutout Recorte Dry Brush… Pincel seco… diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 365452e4c..84af613c4 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4951,6 +4951,8 @@ imageTrace Outline Ääriviiva DWG (use DXF) DWG (käytä DXF:ää) Artistic Taiteelliset Film Grain… Filmin rakeisuus… + Colored Pencil… Väriliitu… + Colored Pencil Väriliitu Cutout… Leikkaus… Cutout Leikkaus Dry Brush… Kuiva sivellin… diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 4c4fbe06b..cc4af21e3 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4654,6 +4654,8 @@ lighting Light Lumière Reset Ruler Origin Réinitialiser l’origine des règles Artistic Artistique Film Grain… Grain photographique… + Colored Pencil… Crayon de couleur… + Colored Pencil Crayon de couleur Cutout… Découpage… Cutout Découpage Dry Brush… Pinceau sec… diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 63455d978..10adcbe9e 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1587,6 +1587,8 @@ file-filter Templates Sablonok Select Segments Szakaszok kijelölése Artistic Művészi Film Grain… Filmszemcse… + Colored Pencil… Színes ceruza… + Colored Pencil Színes ceruza Cutout… Kivágás… Cutout Kivágás Dry Brush… Száraz ecset… diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index b3a3051bc..9b65f48e0 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5182,6 +5182,8 @@ swatch Bright Violet Violet cerah Lock Variables Kunci variabel Unlock Variables Buka kunci variabel Change to Artboard Rulers Ubah ke penggaris artboard + Colored Pencil… Pensil warna… + Colored Pencil Pensil warna Cutout… Potongan… Dry Brush… Kuas kering… Neon Glow… Pendar neon… diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index f52676205..41df7fbc6 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4686,6 +4686,8 @@ lighting Light Luce Reset Ruler Origin Ripristina origine dei righelli Artistic Artistico Film Grain… Grana della pellicola… + Colored Pencil… Matita colorata… + Colored Pencil Matita colorata Cutout… Ritaglio… Cutout Ritaglio Dry Brush… Pennello secco… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index b24e36167..3c4904a66 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4725,6 +4725,8 @@ lighting Light ライト Reset Ruler Origin 定規の原点をリセット Artistic アーティスティック Film Grain… フィルム粒子… + Colored Pencil… 色鉛筆… + Colored Pencil 色鉛筆 Cutout… カットアウト… Cutout カットアウト Dry Brush… ドライブラシ… diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index c1e30279f..055096c7b 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5018,6 +5018,8 @@ imageTrace Outline კონტური Create Slice from Selection ნაჭრის შექმნა მონიშნულიდან Create Slices from Guides ნაჭრების შექმნა დამხმარე ხაზებიდან Cutout აპლიკაცია + Colored Pencil… ფერადი ფანქარი… + Colored Pencil ფერადი ფანქარი Dry Brush მშრალი ფერწანი Cutout… აპლიკაცია… Dry Brush… მშრალი ფერწანი… diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 02935f068..800daeac1 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4649,6 +4649,8 @@ lighting Light 광원 X11 (XWayland) X11 (XWayland) Artistic 예술 효과 Film Grain… 필름 그레인… + Colored Pencil… 색연필… + Colored Pencil 색연필 Cutout… 오려내기… Cutout 오려내기 Dry Brush… 드라이 브러시… diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 805597607..38cd0a07a 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5193,6 +5193,8 @@ swatch Bright Violet Felviolet Lock Variables Variabelen vergrendelen Unlock Variables Variabelen ontgrendelen Change to Artboard Rulers Wijzigen in tekenbordlinialen + Colored Pencil… Gekleurd potlood… + Colored Pencil Gekleurd potlood Cutout… Knipsel… Dry Brush… Droog penseel… Neon Glow… Neongloed… diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index e14037421..f4ab658c3 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4889,6 +4889,8 @@ imageTrace Outline Kontur DWG (use DXF) DWG (użyj DXF) Artistic Artystyczne Film Grain… Ziarno filmu… + Colored Pencil… Kolorowa kredka… + Colored Pencil Kolorowa kredka Cutout… Wycinanka… Cutout Wycinanka Dry Brush… Suchy pędzel… diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index aba95ccdf..97f179076 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -4039,6 +4039,8 @@ axis Both Ambos Reset Ruler Origin Redefinir origem das réguas Artistic Artístico Film Grain… Granulação de filme… + Colored Pencil… Lápis de cor… + Colored Pencil Lápis de cor Cutout… Recorte… Cutout Recorte Dry Brush… Pincel seco… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 71728573e..893f12ece 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4682,6 +4682,8 @@ lighting Light Свет Reset Ruler Origin Сбросить начало координат линеек Artistic Художественные Film Grain… Зернистость плёнки… + Colored Pencil… Цветной карандаш… + Colored Pencil Цветной карандаш Cutout… Аппликация… Cutout Аппликация Dry Brush… Сухая кисть… diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index fc43028ae..4b7070d4f 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4563,6 +4563,8 @@ lighting Light Ljus Reset Ruler Origin Återställ linjalens origo Artistic Konstnärligt Film Grain… Filmkorn… + Colored Pencil… Färgpenna… + Colored Pencil Färgpenna Cutout… Urklipp… Cutout Urklipp Dry Brush… Torr pensel… diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 9ab75b00c..61dc3865e 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4666,6 +4666,8 @@ lighting Light Світло Reset Ruler Origin Скинути початок координат лінійок Artistic Художні Film Grain… Зернистість плівки… + Colored Pencil… Кольоровий олівець… + Colored Pencil Кольоровий олівець Cutout… Аплікація… Cutout Аплікація Dry Brush… Суха кисть… diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index b508807cf..d80001204 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1268,6 +1268,8 @@ Fill: click for swatches, Shift-click for the color mixer Tô: bấm để xem mẫu màu, Shift-bấm để trộn màu Fills Vùng tô Film Grain… Hạt phim… + Colored Pencil… Bút chì màu… + Colored Pencil Bút chì màu Cutout… Cắt dán… Cutout Cắt dán Dry Brush… Cọ khô… diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index a6f520b2b..5ab3f370b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4950,6 +4950,8 @@ lighting Light 光源 Reset Ruler Origin 重置标尺原点 Artistic 艺术效果 Film Grain… 胶片颗粒… + Colored Pencil… 彩色铅笔… + Colored Pencil 彩色铅笔 Cutout… 剪纸… Dry Brush… 干画笔… Cutout 剪纸 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 549615b26..dc853661e 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4756,6 +4756,8 @@ lighting Light 光源 Reset Ruler Origin 重設尺標原點 Artistic 藝術效果 Film Grain… 底片顆粒… + Colored Pencil… 彩色鉛筆… + Colored Pencil 彩色鉛筆 Cutout… 剪紙… Dry Brush… 乾畫筆… Cutout 剪紙 diff --git a/crates/ui-egui/src/tests_labels.rs b/crates/ui-egui/src/tests_labels.rs index eb9da8a14..3529257bd 100644 --- a/crates/ui-egui/src/tests_labels.rs +++ b/crates/ui-egui/src/tests_labels.rs @@ -89,6 +89,7 @@ fn effect_menu_lists_the_artistic_filters_before_blur() { assert_eq!( labels, [ + "Colored Pencil…", "Cutout…", "Dry Brush…", "Film Grain…", @@ -105,8 +106,8 @@ fn effect_menu_lists_the_artistic_filters_before_blur() { ] ); assert!(artistic.iter().all(|e| e.command.as_deref() == Some("effect.dialog"))); - assert_eq!(artistic[1].params, json!({"effect": "artistic.dryBrush"})); - assert_eq!(artistic[3].params, json!({"effect": "artistic.neonGlow"})); + assert_eq!(artistic[2].params, json!({"effect": "artistic.dryBrush"})); + assert_eq!(artistic[4].params, json!({"effect": "artistic.neonGlow"})); let at = |name: &str| entries.iter().position(|e| e.path == ["Effect", name]).unwrap(); assert!(at("Artistic") < at("Blur")); } diff --git a/docs/effects-parity.md b/docs/effects-parity.md index 06cbf2acd..130c94d70 100644 --- a/docs/effects-parity.md +++ b/docs/effects-parity.md @@ -12,6 +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 | 49 | 86% | 7–12 | | Photoshop effects (raster, Effect Gallery) | 57 | 48 | 84% | 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 | — | — | @@ -38,6 +39,7 @@ its resolution, PDF export warns once per object with the requested and the lowe | Submenu | Items | Ours | Missing | |---|---|---:|---| +| Artistic | Colored Pencil, Cutout, Dry Brush, Film Grain, Fresco, Neon Glow, Paint Daubs, Palette Knife, Plastic Wrap, Poster Edges, Rough Pastels, Smudge Stick, Sponge, Underpainting, Watercolor | 10/15 (approximate Colored Pencil, Cutout, Dry Brush, Film Grain, Neon Glow, Plastic Wrap, Poster Edges, Smudge Stick, Sponge, Watercolor) | Fresco, Paint Daubs, Palette Knife, Rough Pastels, Underpainting | | Artistic | Colored Pencil, Cutout, Dry Brush, Film Grain, Fresco, Neon Glow, Paint Daubs, Palette Knife, Plastic Wrap, Poster Edges, Rough Pastels, Smudge Stick, Sponge, Underpainting, Watercolor | 9/15 (approximate Cutout, Film Grain, Neon Glow, Paint Daubs, Plastic Wrap, Poster Edges, Smudge Stick, Sponge, Watercolor) | Colored Pencil, Dry Brush, Fresco, Palette Knife, Rough Pastels, Underpainting | | Blur | Gaussian Blur, Radial Blur, Smart Blur | 3/3 | — | | Brush Strokes | Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes, Sumi-e | 8/8 | — | @@ -52,7 +54,7 @@ its resolution, PDF export warns once per object with the requested and the lowe The pixel-filter pipeline (`vectorcraft_effects::pixel`) is in place: each new filter is one function, a catalogue row, a menu entry and translations. Measured throughput: 0.7–1.2 agent hours per filter -(see [calibration](target-app-parity.md#calibration)), so the 12 missing filters are ~8–14 h and the Effect +(see [calibration](target-app-parity.md#calibration)), so the 10 missing filters are ~7–12 h and the Effect Gallery dialog another 4–6 h. These parallelize well across agents. Film Grain (`artistic.filmGrain`, Effect › Artistic) adds stable monochrome noise to shadows and diff --git a/docs/gaps.md b/docs/gaps.md index 8dda09bbe..62de3c1f1 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -234,12 +234,11 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall ## G2 Photoshop-style raster effects and the Effect Gallery - **Done:** Effect › Texture › Texturizer › Load Texture… (stored grayscale `textureImage`, Loaded Texture in the Texture menu), matching Glass. -- **Missing:** 24 of 57 filters (the remaining 11 Artistic filters and 13 of Sketch's 14), the Effect Gallery dialog. Measured in -- **Missing:** 10 of 57 filters (7 Artistic, 2 Sketch and 1 more), the Effect Gallery dialog, Load - Texture for Texturizer. Measured in +- **Missing:** 8 of 57 filters (5 Artistic, 2 Sketch and 1 more), the Effect Gallery dialog. Measured in [effects-parity.md](effects-parity.md). -- **Done:** the pixel-filter pipeline and 45 filters (Artistic: Cutout, Film Grain, Neon Glow, Plastic Wrap, Poster Edges, Smudge Stick, Sponge, Watercolor; Blur, Brush Strokes, Distort, Pixelate, Sharpen; Sketch: - Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Photocopy, Plaster, Reticulation, Stamp; Stylize, Texture, Video), with live preview and editable native settings. Smart Blur supports Edge Only and Overlay Edge +- **Done:** Effect › Artistic › Colored Pencil (`artistic.coloredPencil`, pencil width, stroke pressure, paper brightness; original visual approximation). +- **Done:** the pixel-filter pipeline and 49 filters (Artistic: Colored Pencil, Cutout, Dry Brush, Film Grain, Neon Glow, Plastic Wrap, Poster Edges, Smudge Stick, Sponge, Watercolor; Blur, Brush Strokes, Distort, Pixelate, Sharpen; Sketch: + Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Water Paper; Stylize, Texture, Video), with live preview and editable native settings. Smart Blur supports Edge Only and Overlay Edge modes. - **Estimate:** 8–14 h (measured 0.7–1.2 h per filter); parallelizes well.