diff --git a/README.md b/README.md index f457509e5..4023951e2 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,7 @@ The scores are self-assessed from our code against public documentation; - reliable exchange of .ai files (layer structure in real files; proprietary editing data isn't written); - an interaction-fidelity pass covering every tool's modifiers and small behaviours; - 3D and Materials beyond the initial live Revolve (Extrude & Bevel, Inflate, Rotate, materials); -- most of the raster effects (10 of 57 still missing; Cutout, Film Grain, Neon Glow, Plastic Wrap, Poster Edges, Smudge Stick, Sponge and Watercolor are available under Artistic, Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp and Water Paper under Sketch) and the Effect Gallery; +- most of the raster effects (9 of 57 still missing; Cutout, Film Grain, Neon Glow, Plastic Wrap, Poster Edges, Smudge Stick, Sponge and Watercolor are available under Artistic, Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Torn Edges and Water Paper under Sketch) and the Effect Gallery; - CJK composition for vertical type (vertical type itself has initial support, with a Japanese interface); - scripting, and the rest of Variables (image and graph kinds, dataset import); - generative AI features. diff --git a/crates/effects/src/lib.rs b/crates/effects/src/lib.rs index 7c2c9ac85..20a351c79 100644 --- a/crates/effects/src/lib.rs +++ b/crates/effects/src/lib.rs @@ -162,6 +162,7 @@ fn lengths_of(id: &str) -> Lengths { "sketch.graphicPen" => always(&["strokeLength"]), "sketch.halftonePattern" => always(&["size"]), "sketch.notePaper" => always(&["relief"]), + "sketch.tornEdges" => always(&["smoothness"]), "sketch.waterPaper" => always(&["fiberLength"]), RASTERIZE => always(&["addAround"]), _ => Lengths::default(), @@ -639,6 +640,13 @@ pub fn effect_catalog() -> Vec { "{lightDarkBalance: 0..50 (25; the larger, the more of the object prints black), smoothness: 1..50 (5; rounds the shapes)} simplifies the object to solid black and white, as a rubber stamp would print it. Coverage is kept. Original visual approximation; chosen VectorCraft defaults.", json!({"lightDarkBalance": 25.0, "smoothness": 5.0}), ), + r( + "sketch.tornEdges", + "Torn Edges…", + SKETCH, + "{imageBalance: 0..50 (25; lower leaves more midtones as ink, higher as paper), smoothness: 0..15 (11; softens tones and the ragged edges), contrast: 0..20 (17; 20 is pure black and white)} redraws the object in high-contrast black and white with torn, ragged edges along its tones, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.", + json!({"imageBalance": 25.0, "smoothness": 11.0, "contrast": 17.0}), + ), r( "sketch.waterPaper", "Water Paper…", diff --git a/crates/effects/src/pixel/mod.rs b/crates/effects/src/pixel/mod.rs index 69b9c3953..3e48d032f 100644 --- a/crates/effects/src/pixel/mod.rs +++ b/crates/effects/src/pixel/mod.rs @@ -3,7 +3,7 @@ //! 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 › Bas Relief, Chalk & Charcoal, Charcoal, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, -//! Reticulation and Stamp, Stylize › +//! Reticulation, Stamp and Torn Edges, Stylize › //! Glowing Edges, Texture //! › Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass and Texturizer, and Video › //! De-Interlace and NTSC Colors): filters over premultiplied RGBA8 pixels. @@ -44,7 +44,7 @@ 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; 53] = [ +pub const PIXEL_EFFECTS: [&str; 54] = [ "artistic.coloredPencil", "artistic.cutout", "artistic.dryBrush", @@ -88,6 +88,7 @@ pub const PIXEL_EFFECTS: [&str; 53] = [ "sketch.plaster", "sketch.reticulation", "sketch.stamp", + "sketch.tornEdges", "sketch.waterPaper", "stylize.glowingEdges", "texture.craquelure", @@ -232,6 +233,10 @@ pub enum PixelFx { /// Sketch › Reticulation: black ink on white paper through a high-contrast film-grain screen; /// `density` (0..50), `foreground` and `background` (0..50) pull tones before the grain. Reticulation { density: f64, foreground: f64, background: f64 }, + /// Sketch › Torn Edges: high-contrast black and white with ragged edges; `image_balance` + /// (0..50) shifts ink versus paper, `smoothness` (0..15) softens tones and fraying, `contrast` + /// (0..20) hardens the split. + TornEdges { image_balance: f64, smoothness: f64, contrast: f64 }, /// Sketch › Water Paper: colours flow on damp fibrous paper; `fiber_length` (3..50) sets the /// fibers and daubs, `brightness` and `contrast` (0..100) adjust the wash. Ranges and defaults /// are unverified VectorCraft choices (helpx documents the controls only). @@ -510,6 +515,11 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option { foreground: num(p, "foregroundLevel", 6.0).clamp(0.0, 50.0), background: num(p, "backgroundLevel", 6.0).clamp(0.0, 50.0), }, + "sketch.tornEdges" => PixelFx::TornEdges { + image_balance: num(p, "imageBalance", 25.0).clamp(0.0, 50.0), + smoothness: num(p, "smoothness", 11.0).clamp(0.0, 15.0), + contrast: num(p, "contrast", 17.0).clamp(0.0, 20.0), + }, "sketch.waterPaper" => PixelFx::WaterPaper { fiber_length: num(p, "fiberLength", 15.0).clamp(3.0, 50.0), brightness: num(p, "brightness", 50.0).clamp(0.0, 100.0), @@ -601,7 +611,8 @@ impl PixelFx { | PixelFx::HalftonePattern { .. } | PixelFx::NotePaper { .. } | PixelFx::Plaster { .. } - | PixelFx::Reticulation { .. } => 0.0, + | PixelFx::Reticulation { .. } + | PixelFx::TornEdges { .. } => 0.0, PixelFx::BasRelief { .. } | PixelFx::Charcoal { .. } | PixelFx::Photocopy { .. } | PixelFx::Stamp { .. } => 0.0, PixelFx::WaterPaper { fiber_length, .. } => sketch::water_paper_shift(fiber_length), PixelFx::GlowingEdges { width, smoothness, .. } => width.max(smoothness * 3.0), @@ -682,8 +693,8 @@ impl PixelFx { PixelFx::WaterPaper { fiber_length, .. } => { Some(sketch::water_paper_shift(fiber_length) + 3.0 * (0.06 * fiber_length.clamp(3.0, 50.0)).max(0.35)) } - // Cutout's palette comes from the whole object. - PixelFx::Cutout { .. } => None, + // Cutout's palette and Torn Edges' peak-normalised edges come from the whole object. + PixelFx::Cutout { .. } | PixelFx::TornEdges { .. } => None, // The softened tones around the pixel. PixelFx::NeonGlow { size, .. } => Some(3.0 * artistic::glow_spread(size)), PixelFx::PlasticWrap { smoothness, .. } => Some(artistic::plastic_reach(smoothness)), @@ -764,6 +775,7 @@ impl PixelFx { PixelFx::Stamp { balance, smoothness } => sketch_ink::stamp(px, w, h, space, balance, smoothness), PixelFx::Plaster { image_balance, smoothness, light } => sketch::plaster(px, w, h, space, image_balance, smoothness, light), PixelFx::Reticulation { density, foreground, background } => sketch::reticulation(px, w, h, space, density, foreground, background), + PixelFx::TornEdges { image_balance, smoothness, contrast } => sketch::torn_edges(px, w, h, space, image_balance, smoothness, contrast), PixelFx::WaterPaper { fiber_length, brightness, contrast } => sketch::water_paper(px, w, h, space, fiber_length, brightness, contrast), PixelFx::GlowingEdges { width, brightness, smoothness } => edges::glow(px, w, h, to_px(width), brightness, to_px(smoothness)), PixelFx::ColorHalftone { max_radius, angles } => pixelate::color_halftone(px, w, h, space, max_radius, angles), diff --git a/crates/effects/src/pixel/sketch.rs b/crates/effects/src/pixel/sketch.rs index a1fc37463..d61c51e6e 100644 --- a/crates/effects/src/pixel/sketch.rs +++ b/crates/effects/src/pixel/sketch.rs @@ -1,5 +1,5 @@ //! Effect › Sketch (Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Halftone Pattern, Note Paper, Photocopy, Plaster, -//! Reticulation, Stamp and Water Paper; Graphic Pen lives in +//! Reticulation, Stamp, Torn Edges and Water Paper; Graphic Pen lives in //! [`super::brushstrokes`]). Original filters built from public behaviour documentation: //! (Sketch effects redraw in //! black and white; Water Paper flows colours on damp fibrous paper) and the public Photoshop Elements Artistic filters guide. Visual @@ -17,6 +17,7 @@ use super::{PixelSpace, blur_plane, pick}; const SALT_CHALK: u64 = 216; const SALT_CHROME: u64 = 0xC40_4D45; +const SALT_TORN: u64 = 0x746f726e5f6564; const SALT_WATER: u64 = 208; const EDGE_SOFT: f64 = 0.8; const TONE_SOFT: f64 = 1.0; @@ -361,6 +362,55 @@ pub(super) fn reticulation(px: &mut [[u8; 4]], w: usize, h: usize, space: &Pixel }); } +/// Torn Edges: the object in high-contrast black and white with ragged edges. `image_balance` +/// (0..50) shifts whether midtones fall to ink or paper; `smoothness` (0..15) softens tones and +/// the fraying; `contrast` (0..20) hardens the split. Coverage is kept. +pub(super) fn torn_edges(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, image_balance: f64, smoothness: f64, contrast: f64) { + if !(image_balance.is_finite() && smoothness.is_finite() && contrast.is_finite()) { + return; + } + let Some(place) = Place::new(space, w, h) else { return }; + if w == 0 || h == 0 || w.checked_mul(h) != Some(px.len()) { + return; + } + let mut tone: Vec = px.iter().map(|p| luma(straight(*p)) * f32::from(p[3]) / 255.0).collect(); + let cover: Vec = px.iter().map(|p| f32::from(p[3]) / 255.0).collect(); + let sigma = (0.12 * smoothness.clamp(0.0, 15.0)) / place.px.max(1e-9); + if sigma > 0.0 { + blur_plane(&mut tone, w, h, sigma); + } + let mut edges: Vec = sobel(&tone, w, h).into_iter().zip(sobel(&cover, w, h)).map(|(t, c)| t.max(c)).collect(); + let edge_soft = (0.08 * smoothness.clamp(0.0, 15.0)) / place.px.max(1e-9); + if edge_soft > 0.0 { + blur_plane(&mut edges, w, h, edge_soft); + } + let peak = edges.iter().copied().fold(0.0f32, f32::max); + let balance = image_balance.clamp(0.0, 50.0); + // Lower balance favours ink (darker result); higher balance favours paper. + let balance_tone = 0.25 + balance / 50.0 * 0.75; + let steep = 4.0 + contrast.clamp(0.0, 20.0) * 0.45; + let hard = contrast.clamp(0.0, 20.0) >= 18.0; + let fray = 0.25 + 0.05 * smoothness.clamp(0.0, 15.0); + place.each(px, |x, y, p| { + let a = f32::from(p[3]) / 255.0; + if a <= 0.0 { + *p = [0; 4]; + return; + } + let i = y * w + x; + let t = tone.get(i).copied().unwrap_or(0.0); + let e = if peak > 1e-6 { edges.get(i).copied().unwrap_or(0.0) / peak } else { 0.0 }; + let noise = smooth_noise(SALT_TORN, place.at(x, y), 2.5 + 0.3 * smoothness) as f32; + let local = (1.0 - balance_tone) as f32 + e * fray as f32 * noise; + let mut ink = 1.0 / (1.0 + (steep as f32 * (t - local)).exp()); + if hard { + ink = if ink > 0.5 { 1.0 } else { 0.0 }; + } + let paper = 1.0 - ink.clamp(0.0, 1.0); + *p = premultiply([paper, paper, paper], a); + }); +} + /// How far Water Paper's blotchy displacement can shift content (document units). pub(super) fn water_paper_shift(fiber_length: f64) -> f64 { 0.12 * fiber_length.clamp(3.0, 50.0) diff --git a/crates/effects/src/pixel/tests.rs b/crates/effects/src/pixel/tests.rs index b682d7e62..780cc59a0 100644 --- a/crates/effects/src/pixel/tests.rs +++ b/crates/effects/src/pixel/tests.rs @@ -2071,6 +2071,13 @@ fn halftone(p: serde_json::Value, src: &[u8], w: usize, h: usize, s: &PixelSpace out } +/// Torn Edges of `p` over `w` × `h` pixels of `src`, laid out by `s`. +fn torn(p: serde_json::Value, src: &[u8], w: usize, h: usize, s: &PixelSpace) -> Vec { + let mut out = src.to_vec(); + fx("sketch.tornEdges", p).apply(&mut out, w, h, s); + out +} + /// Water Paper of `p` over `w` × `h` pixels of `src`, laid out by `s`. fn water_paper(p: serde_json::Value, src: &[u8], w: usize, h: usize, s: &PixelSpace) -> Vec { let mut out = src.to_vec(); @@ -2404,6 +2411,63 @@ fn reticulation_density_changes_the_grain() { assert_ne!(low, high); } +#[test] +fn torn_edges_params_take_defaults_and_stay_in_range() { + assert_eq!(fx("sketch.tornEdges", json!({})), PixelFx::TornEdges { image_balance: 25.0, smoothness: 11.0, contrast: 17.0 }); + assert_eq!( + fx("sketch.tornEdges", json!({"imageBalance": 0, "smoothness": 99, "contrast": -1})), + PixelFx::TornEdges { image_balance: 0.0, smoothness: 15.0, contrast: 0.0 } + ); + assert_eq!( + fx("sketch.tornEdges", json!({"imageBalance": 1e9, "smoothness": -3, "contrast": 1e9})), + PixelFx::TornEdges { image_balance: 50.0, smoothness: 0.0, contrast: 20.0 } + ); + for junk in [json!({"imageBalance": "x"}), json!({"smoothness": null})] { + assert_eq!(fx("sketch.tornEdges", junk), fx("sketch.tornEdges", json!({}))); + } + assert_eq!(fx("sketch.tornEdges", json!({})).reach(), None); +} + +#[test] +fn torn_edges_draws_in_black_and_white_and_keeps_coverage() { + let (w, h) = (64, 48); + let src = ramp(w, h); + let out = torn(json!({"imageBalance": 12, "contrast": 20, "smoothness": 5}), &src, w, h, &space(w, h)); + 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] && (p[0] == 0 || p[0] == p[3]), "black or white: {p:?}"); + } + assert!(share(&out, |p| p[0] == 0) > 0.05 && share(&out, |p| p[0] == p[3]) > 0.05, "ink and paper"); +} + +#[test] +fn torn_edges_image_balance_shifts_ink_on_a_grey_ramp() { + let (w, h) = (80, 16); + let src = image(w, h, |x, _| grey((x * 255 / (w - 1)) as u8)); + let params = json!({"imageBalance": 25, "contrast": 20, "smoothness": 5}); + let out = torn(params, &src, w, h, &space(w, h)); + let mut dark_ink = 0usize; + let mut bright_ink = 0usize; + for y in 0..h { + for x in 0..w / 4 { + if at(&out, w, x, y)[0] == 0 { + dark_ink += 1; + } + } + for x in 3 * w / 4..w { + if at(&out, w, x, y)[0] == 0 { + bright_ink += 1; + } + } + } + let dark_n = (w / 4) * h; + let bright_n = (w - 3 * w / 4) * h; + assert!(dark_ink as f64 / dark_n as f64 > bright_ink as f64 / bright_n as f64 + 0.2, "dark end of ramp is inkier than bright end"); + let dark = share(&torn(json!({"imageBalance": 0, "contrast": 20, "smoothness": 5}), &src, w, h, &space(w, h)), |p| p[0] == 0); + let light = share(&torn(json!({"imageBalance": 50, "contrast": 20, "smoothness": 5}), &src, w, h, &space(w, h)), |p| p[0] == 0); + assert!(dark > light + 0.15, "low balance leaves more ink: dark={dark} light={light}"); +} + /// The distinct colours of the covered pixels. fn colours(d: &[u8]) -> std::collections::BTreeSet<[u8; 4]> { d.as_chunks::<4>().0.iter().filter(|p| p[3] > 0).copied().collect() diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 7c6a80fef..bfd7a2d48 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5241,6 +5241,8 @@ theme Appearance Външен вид Sketch Скица Halftone Pattern… Полутонов шаблон… Halftone Pattern Полутонов шаблон + Torn Edges… Разкъсани краища… + Torn Edges Разкъсани краища Pattern Type Тип шаблон Dot Точка # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index c95a03812..d722c042e 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5272,6 +5272,8 @@ mark-style Japanese Japonské Sketch Skica Halftone Pattern… Polotónový vzorek… Halftone Pattern Polotónový vzorek + Torn Edges… Roztržené okraje… + Torn Edges Roztržené okraje Pattern Type Typ vzorku Dot Tečka # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 0a577bb9c..2d0931772 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5133,6 +5133,8 @@ swatch Bright Violet Kraftig violet Sketch Skitse Halftone Pattern… Rastermønster… Halftone Pattern Rastermønster + Torn Edges… Flængede kanter… + Torn Edges Flængede kanter Pattern Type Mønstertype Dot Prik Export Type: Eksporttype: diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 903701e7f..73188d124 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4851,6 +4851,8 @@ effect Soft Weich Sketch Zeichenfilter Halftone Pattern… Rasterungseffekt… Halftone Pattern Rasterungseffekt + Torn Edges… Zerrissene Kanten… + Torn Edges Zerrissene Kanten Pattern Type Musterart Dot Punkt # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 7e6bc122a..a8be3b930 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5224,6 +5224,8 @@ effect Points Σημεία Sketch Σκίτσο Halftone Pattern… Μοτίβο ημιτόνου… Halftone Pattern Μοτίβο ημιτόνου + Torn Edges… Ξεσκισμένες άκρες… + Torn Edges Ξεσκισμένες άκρες Pattern Type Τύπος μοτίβου Dot Κουκκίδα # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index a111d1b82..036502570 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5214,6 +5214,8 @@ mark-style Japanese Japonesas Sketch Bosquejar Halftone Pattern… Trama de semitonos… Halftone Pattern Trama de semitonos + Torn Edges… Bordes desgarrados… + Torn Edges Bordes desgarrados Pattern Type Tipo de trama Dot Punto # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index d77e194ec..6ea8214c2 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -5236,6 +5236,8 @@ mark-style Japanese Japanilainen Sketch Luonnos Halftone Pattern… Rasterikuvio… Halftone Pattern Rasterikuvio + Torn Edges… Repeytyneet reunat… + Torn Edges Repeytyneet reunat Pattern Type Kuviotyyppi Dot Piste # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index a0e8a04ca..4cd89919c 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5271,6 +5271,8 @@ theme Appearance Apparence Sketch Esquisse Halftone Pattern… Trame de demi-teintes… Halftone Pattern Trame de demi-teintes + Torn Edges… Bords déchirés… + Torn Edges Bords déchirés Pattern Type Type de motif Dot Point # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 269d556a7..838657e02 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -5258,6 +5258,8 @@ imageTrace Outline Körvonal Sketch Vázlat Halftone Pattern… Féltónus minta… Halftone Pattern Féltónus minta + Torn Edges… Szaggatott szélek… + Torn Edges Szaggatott szélek Pattern Type Mintatípus Dot Pont # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 13b7e6065..bf6b3d344 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5133,6 +5133,8 @@ swatch Bright Violet Violet cerah Sketch Sketsa Halftone Pattern… Pola halftone… Halftone Pattern Pola halftone + Torn Edges… Tepi robek… + Torn Edges Tepi robek Pattern Type Jenis pola Dot Titik Export Type: Jenis ekspor: diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index e5995fa59..7676be18c 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4886,6 +4886,8 @@ lighting Light Luce Sketch Schizzo Halftone Pattern… Pattern mezzetinte… Halftone Pattern Pattern mezzetinte + Torn Edges… Bordi strappati… + Torn Edges Bordi strappati Pattern Type Tipo di pattern Dot Punto # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 4497638f8..315fe6f8b 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4926,6 +4926,8 @@ lighting Light ライト Sketch スケッチ Halftone Pattern… ハーフトーンパターン… Halftone Pattern ハーフトーンパターン + Torn Edges… 破れたエッジ… + Torn Edges 破れたエッジ Pattern Type パターンタイプ Dot ドット # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 9e8c1055c..357c27ed7 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4988,6 +4988,8 @@ imageTrace Outline კონტური Sketch ესკიზი Halftone Pattern… ნახევარტონური პატერნი… Halftone Pattern ნახევარტონური პატერნი + Torn Edges… დაგვერძნილი კიდეები… + Torn Edges დაგვერძნილი კიდეები Pattern Type პატერნის ტიპი Dot წერტილი Color Scheme ფერის სქემა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index c4eee9b0b..fe22ba7bb 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4849,6 +4849,8 @@ lighting Light 광원 Sketch 스케치 Halftone Pattern… 하프톤 패턴… Halftone Pattern 하프톤 패턴 + Torn Edges… 찢어진 가장자리… + Torn Edges 찢어진 가장자리 Pattern Type 패턴 유형 Dot 점 # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 22d89444a..963e78a13 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5144,6 +5144,8 @@ swatch Bright Violet Felviolet Sketch Schets Halftone Pattern… Rasterpatroon… Halftone Pattern Rasterpatroon + Torn Edges… Gescheurde randen… + Torn Edges Gescheurde randen Pattern Type Patroontype Dot Punt Export Type: Exporttype: diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index e2a767697..3000b9062 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5124,6 +5124,8 @@ mark-style Japanese Japońskie Sketch Szkic Halftone Pattern… Wzór rastra… Halftone Pattern Wzór rastra + Torn Edges… Postrzępione krawędzie… + Torn Edges Postrzępione krawędzie Pattern Type Typ wzoru Dot Kropka # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 5c50d24b8..64888dd6d 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5191,6 +5191,8 @@ mark-style Japanese Japonesas Sketch Esboço Halftone Pattern… Padrão de meio-tom… Halftone Pattern Padrão de meio-tom + Torn Edges… Bordas rasgadas… + Torn Edges Bordas rasgadas Pattern Type Tipo de padrão Dot Ponto # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 23dc4bd63..924c943d9 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4882,6 +4882,8 @@ lighting Light Свет Sketch Эскиз Halftone Pattern… Полутоновый узор… Halftone Pattern Полутоновый узор + Torn Edges… Рваные края… + Torn Edges Рваные края Pattern Type Тип узора Dot Точка # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 3667e0434..b4a993f29 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -5228,6 +5228,8 @@ theme Appearance Utseende Sketch Skiss Halftone Pattern… Rastermönster… Halftone Pattern Rastermönster + Torn Edges… Riviga kanter… + Torn Edges Riviga kanter Pattern Type Mönstertyp Dot Punkt # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 78e65e0d9..acfddaede 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4866,6 +4866,8 @@ lighting Light Світло Sketch Ескіз Halftone Pattern… Напівтоновий візерунок… Halftone Pattern Напівтоновий візерунок + Torn Edges… Порвані краї… + Torn Edges Порвані краї Pattern Type Тип візерунка Dot Крапка # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 8e9c94239..46952739f 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4866,6 +4866,8 @@ theme Appearance Giao diện Sketch Phác thảo Halftone Pattern… Mẫu bán sắc… Halftone Pattern Mẫu bán sắc + Torn Edges… Cạnh rách… + Torn Edges Cạnh rách Pattern Type Loại mẫu Dot Chấm # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 3b613f8f0..71a3dfce5 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5107,6 +5107,8 @@ lighting Light 光源 Sketch 素描 Halftone Pattern… 半调图案… Halftone Pattern 半调图案 + Torn Edges… 撕边… + Torn Edges 撕边 Pattern Type 图案类型 Dot 网点 # User Interface › Color Scheme diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 9f56d96ae..21281caf2 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4917,6 +4917,8 @@ lighting Light 光源 Sketch 素描 Halftone Pattern… 半色調圖樣… Halftone Pattern 半色調圖樣 + Torn Edges… 撕裂邊緣… + Torn Edges 撕裂邊緣 Pattern Type 圖樣類型 Dot 網點 # User Interface › Color Scheme diff --git a/crates/ui-egui/src/tests_labels.rs b/crates/ui-egui/src/tests_labels.rs index 3529257bd..fd00c58f9 100644 --- a/crates/ui-egui/src/tests_labels.rs +++ b/crates/ui-egui/src/tests_labels.rs @@ -227,6 +227,7 @@ fn effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize() { "Plaster…", "Reticulation…", "Stamp…", + "Torn Edges…", "Water Paper…" ] ); @@ -242,14 +243,15 @@ fn effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize() { 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"})); - assert_eq!(sketch[11].params, json!({"effect": "sketch.waterPaper"})); + assert_eq!(sketch[11].params, json!({"effect": "sketch.tornEdges"})); + assert_eq!(sketch[12].params, json!({"effect": "sketch.waterPaper"})); let at = |label: &str| entries.iter().position(|e| e.path[0] == "Effect" && e.label == label).unwrap(); assert!(at("Unsharp Mask…") < at("Bas Relief…") && at("Bas Relief…") < at("Chalk & Charcoal…")); assert!(at("Chalk & Charcoal…") < at("Chrome…")); assert!(at("Chrome…") < at("Graphic Pen…") && at("Graphic Pen…") < at("Halftone Pattern…")); assert!(at("Halftone Pattern…") < at("Note Paper…") && at("Note Paper…") < at("Plaster…")); assert!(at("Plaster…") < at("Reticulation…")); - assert!(at("Reticulation…") < at("Water Paper…") && at("Water Paper…") < at("Glowing Edges…")); + assert!(at("Reticulation…") < at("Torn Edges…") && at("Torn Edges…") < at("Water Paper…") && at("Water Paper…") < 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 53fbb0811..510489e6a 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 | 48 | 84% | 8–14 | | Photoshop effects (raster, Effect Gallery) | 57 | 52 | 91% | 4–6 | | 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 +45,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 | 12/14 (approximate Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Water Paper; Chalk & Charcoal, Graphic Pen, Note Paper and Water Paper defaults unverified) | Conté Crayon, Torn Edges | +| Sketch | Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Conté Crayon, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Torn Edges, Water Paper | 13/14 (approximate Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Torn Edges, Water Paper; Chalk & Charcoal, Graphic Pen, Note Paper and Water Paper defaults unverified) | Conté Crayon | | Stylize | Glowing Edges | 1/1 | — | | Texture | Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass, Texturizer | 6/6 | — | | Video | De-Interlace, NTSC Colors | 2/2 | — | @@ -52,6 +53,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 9 missing filters are ~8–14 h and the Effect (see [calibration](target-app-parity.md#calibration)), so the 5 missing filters are ~4–6 h and the Effect Gallery dialog another 4–6 h. These parallelize well across agents. diff --git a/docs/gaps.md b/docs/gaps.md index 62de3c1f1..08a35c855 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -236,6 +236,8 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall - **Done:** Effect › Texture › Texturizer › Load Texture… (stored grayscale `textureImage`, Loaded Texture in the Texture menu), matching Glass. - **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 46 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, Torn Edges; 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 diff --git a/docs/roadmap.md b/docs/roadmap.md index f4f60415f..76df4d4f3 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -60,6 +60,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V | M6 | Path operations (Pathfinder, Shape Builder, offset…) | ✅ mostly done (single-result Pathfinder names and SVG IDs retained (#1293); Divide open-cutting-path geometry, appearance and history verified (#1307; existing segment cap remains); Simplify preview/counts and rewritten-path component selection verified (#1272; auto/slider/advanced UI remains open); Compound appearance and member fill-rule edits verified (#1197); Boolean precision on almost-horizontal edges fixed and the property tests made deterministic; Shape Builder gap detection, large-offset bug open) | 3–6 | | M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | 🟡 Fullwidth `!` / `?` keep an upright fallback in vertical type; Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles (explicit Auto-leading resets and local leading overrides), per-paragraph paragraph attributes, mixed paragraph alignment indication (headless regressions, full integration CI and visual review pass), Area Type Options (with Width/Height, Auto Size and Shrink Text to Fit; overflow reported to agents and vertical Align), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel, Find Font, installed system fonts in every font menu (fonts registered with Windows outside its font folders, the XDG and Flatpak host font folders on Linux, and fonts installed while the app runs, listed when it comes back to the front; searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import (full-name aliases and Missing Fonts searches verified, #1258; Windows font-service QA pending), variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects and Options with draggable start/centre/end brackets (slide, flip), tab stops + Tabs panel (Above Text floats it over the selected text, its ruler at the canvas zoom so stops line up with the columns, #729), inline symbols in text (beyond parity), Hebrew/Arabic bidirectional text, Paragraph Direction, Every-line/Single-line Composer per object (ragged text too) done; tab leaders, spell check, vertical type pending | 45–60 | | M8 | Transform & distort (Puppet Warp, Liquify tools, Envelopes, Blends, Perspective Grid) | ✅ done (M8.1–M8.20 on 2026-10-06, fidelity pass): Liquify Tool Options with one shared brush, pen pressure, hold-to-apply, a safe scope and incremental strokes; tool options that last; Puppet Warp rotation, multi-select, Control bar and a rest-shape session that follows Undo; Width tool on compound paths; Envelope dialogs with preview, Reset with Warp/Mesh, full Envelope Options, type/images/symbols/appearance/gradients/patterns distorted on canvas and in every export, warps that keep their frame, Edit Contents, mesh handles and Control bar; Blend Options, anchor-targeted Blend tool, editable spine, keys picked on the canvas, faithful interpolation and knockout; Anchor Point spine conversion/handle editing and selection priority (#2544), with #2425 Shift reshape preserved; Perspective Grid with Define Grid, presets, lock/snap/rulers, widgets, the 1–4 plane switch, movable planes, Perspective Selection scale/copy/perpendicular/Transform Again, and type and symbols truly projected with Edit Text. Left: Anti-Alias and Preserve Shape output, warp-envelope point editing, brush interpolation in blends, Free Transform/Scale/Reflect along a rotated box | 2–4 | +| M9 | Live effects (+ 3D & Materials) | 🟡 2D effects done incl. Effect → Pathfinder, Color Adjustments and Document Raster Effects Settings; PDF export warns when the pixel cap lowers raster-effect resolution; Artistic › Cutout, Film Grain, Neon Glow, Poster Edges, Smudge Stick, Sponge and Watercolor and Sketch › Halftone Pattern, Bas Relief, Charcoal, Photocopy, Stamp, Torn Edges and Water Paper done (48/57 Photoshop filters); Revolve with automatic smooth vector shading, default-on circular caps and continuous rims, Extrude & Bevel, Inflate and Rotate with one to ten colored directional lights and four original lighting presets; SVG Filters, remaining raster effects and 3D materials and shadows pending | 81–130 | | M9 | Live effects (+ 3D & Materials) | 🟡 2D effects done incl. Effect → Pathfinder, Color Adjustments and Document Raster Effects Settings; PDF export warns when the pixel cap lowers raster-effect resolution; Artistic › Colored Pencil, Cutout, Dry Brush, Film Grain, Neon Glow, Paint Daubs, Palette Knife, Poster Edges, Rough Pastels, Smudge Stick, Sponge, Underpainting and Watercolor and Sketch › Halftone Pattern, Bas Relief, Charcoal, Photocopy and Stamp done (52/57 Photoshop filters); Revolve with automatic smooth vector shading, default-on circular caps and continuous rims, Extrude & Bevel, Inflate and Rotate with one to ten colored directional lights and four original lighting presets; SVG Filters, remaining raster effects and 3D materials and shadows pending | 81–130 | | M10 | Brushes, symbols, patterns, Repeat | 🟡 pattern swatches (5 tile types, Pattern Options, editing mode, SVG ``/PDF export) and live Repeat (radial/grid/mirror) done; interactive selected mirror axis implemented (focused and native checks passed; full CI blocked by an unrelated text test); brushes/symbols in progress | 23–37 | | M11 | Artboards & views (selected-set deletion shared by Artboards menu, trash and Properties, retaining artboard 1 when all are selected; Artboard deletion keys keep the first of an all-selected set and protect artwork; history prunes invalid indices while preserving valid multiselection (history tests and full workspace CI pass); click-to-fit objects with the Artboard tool (#1227), artboard escape routing and document setup modal handoff done; artboard panel/tool with Move Artwork for X/Y fields and Artboard Options, Trim View, middle-button pan, print tiling and the status bar's artboard navigator with synchronized Artboard tool selection done; multiple windows, presentation polish) (cached canvas reprojection preserves scale during resize, rotation and DPI changes; regression-tested and contributor-confirmed 2026-10-19; estimates unchanged) (cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; regression-tested; estimates unchanged) | 🟡 | 15–25 |