Merge pull request #2985 from nrq6993-a11y/stream/vc-feat-g2-sketch-chrome

G2: Effect › Sketch › Chrome raster filter
This commit is contained in:
BFlat
2026-10-20 04:32:36 +07:00
committed by GitHub
32 changed files with 195 additions and 21 deletions
+7
View File
@@ -482,6 +482,13 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
"{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…",
+12 -2
View File
@@ -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,7 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option<PixelFx> {
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 +436,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 +491,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 +549,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),
+68 -3
View File
@@ -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:
//! <https://helpx.adobe.com/illustrator/using/summary-effects.html> (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,66 @@ 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<f32> = Vec::with_capacity(w * h);
let mut alpha: Vec<f32> = 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<f64> = 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
+39
View File
@@ -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<u8> {
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<u8> {
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 });
+8 -3
View File
@@ -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] = [
+1
View File
@@ -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}})),
+2
View File
@@ -5201,3 +5201,5 @@ theme Appearance Външен вид
Reticulation Ретикулация
Plastic Wrap… Найлоново фолио…
Plastic Wrap Найлоново фолио
Chrome… Хром…
Chrome Хром
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5240,3 +5240,5 @@ mark-style Japanese Japonské
Reticulation Síťování
Plastic Wrap… Plastová fólie…
Plastic Wrap Plastová fólie
Chrome… Chrom…
Chrome Chrom
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5219,3 +5219,5 @@ swatch Bright Violet Kraftig violet
Reticulation Retikulering
Plastic Wrap… Plastfolie…
Plastic Wrap Plastfolie
Chrome… Krom…
Chrome Krom
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4820,3 +4820,5 @@ effect Soft Weich
Reticulation Retikulation
Plastic Wrap… Kunststofffolie…
Plastic Wrap Kunststofffolie
Chrome… Chrom…
Chrome Chrom
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5192,3 +5192,5 @@ effect Points Σημεία
Reticulation Δικτύωση
Plastic Wrap… Πλαστική μεμβράνη…
Plastic Wrap Πλαστική μεμβράνη
Chrome… Χρώμιο…
Chrome Χρώμιο
Can't render this file because it contains an unexpected character in line 2016 and column 19.
+2
View File
@@ -5182,3 +5182,5 @@ mark-style Japanese Japonesas
Reticulation Reticulación
Plastic Wrap… Envoltura plástica…
Plastic Wrap Envoltura plástica
Chrome… Cromo…
Chrome Cromo
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5271,3 +5271,5 @@ swatch Bright Violet Kirkas violetti
Reticulation Retikulaatio
Plastic Wrap… Muovikalvo…
Plastic Wrap Muovikalvo
Chrome… Kromi…
Chrome Kromi
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5239,3 +5239,5 @@ theme Appearance Apparence
Reticulation Réticulation
Plastic Wrap… Emballage plastique…
Plastic Wrap Emballage plastique
Chrome… Chrome…
Chrome Chrome
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5226,3 +5226,5 @@ imageTrace Outline Körvonal
Reticulation Recézés
Plastic Wrap… Fóliázás…
Plastic Wrap Fóliázás
Chrome… Króm…
Chrome Króm
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5219,3 +5219,5 @@ swatch Bright Violet Violet cerah
Reticulation Retikulasi
Plastic Wrap… Pembungkus plastik…
Plastic Wrap Pembungkus plastik
Chrome… Krom…
Chrome Krom
Can't render this file because it contains an unexpected character in line 3771 and column 19.
+2
View File
@@ -4854,3 +4854,5 @@ lighting Light Luce
Reticulation Reticolo
Plastic Wrap… Pellicola plastica…
Plastic Wrap Pellicola plastica
Chrome… Cromo…
Chrome Cromo
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4894,3 +4894,5 @@ lighting Light ライト
Reticulation 網目
Plastic Wrap… ラップ…
Plastic Wrap ラップ
Chrome… クロム…
Chrome クロム
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5147,3 +5147,5 @@ imageTrace Outline კონტური
Reticulation რეტიკულაცია
Plastic Wrap… პლასტიკური აფსკი…
Plastic Wrap პლასტიკური აფსკი
Chrome… ქრომი…
Chrome ქრომი
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4817,3 +4817,5 @@ lighting Light 광원
Reticulation 망사 효과
Plastic Wrap… 비닐 랩…
Plastic Wrap 비닐 랩
Chrome… 크롬…
Chrome 크롬
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5261,3 +5261,5 @@ swatch Bright Violet Felviolet
Reticulation Reticulatie
Plastic Wrap… Plasticfolie…
Plastic Wrap Plasticfolie
Chrome… Chroom…
Chrome Chroom
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5092,3 +5092,5 @@ mark-style Japanese Japońskie
Reticulation Siateczkowanie
Plastic Wrap… Folia plastikowa…
Plastic Wrap Folia plastikowa
Chrome… Chrom…
Chrome Chrom
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5157,3 +5157,5 @@ mark-style Japanese Japonesas
Reticulation Reticulação
Plastic Wrap… Envoltório plástico…
Plastic Wrap Envoltório plástico
Chrome… Cromo…
Chrome Cromo
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2
View File
@@ -4850,3 +4850,5 @@ lighting Light Свет
Reticulation Ретикуляция
Plastic Wrap… Целлофановая упаковка…
Plastic Wrap Целлофановая упаковка
Chrome… Хром…
Chrome Хром
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5196,3 +5196,5 @@ theme Appearance Utseende
Reticulation Retikulering
Plastic Wrap… Plastfolie…
Plastic Wrap Plastfolie
Chrome… Krom…
Chrome Krom
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -4834,3 +4834,5 @@ lighting Light Світло
Reticulation Ретикуляція
Plastic Wrap… Целофанова плівка…
Plastic Wrap Целофанова плівка
Chrome… Хром…
Chrome Хром
Can't render this file because it contains an unexpected character in line 725 and column 40.
+2
View File
@@ -4834,3 +4834,5 @@ theme Appearance Giao diện
Reticulation Lưới hạt
Plastic Wrap… Màng bọc nhựa…
Plastic Wrap Màng bọc nhựa
Chrome… Crôm…
Chrome Crôm
Can't render this file because it contains an unexpected character in line 3961 and column 10.
+2
View File
@@ -5077,3 +5077,5 @@ lighting Light 光源
Reticulation 网状
Plastic Wrap… 塑料包装…
Plastic Wrap 塑料包装
Chrome… 铬黄…
Chrome 铬黄
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4872,3 +4872,5 @@ lighting Light 光源
Reticulation 網狀
Plastic Wrap… 塑膠包裝…
Plastic Wrap 塑膠包裝
Chrome… 鉻黃…
Chrome 鉻黃
Can't render this file because it contains an unexpected character in line 6 and column 47.
+10 -9
View File
@@ -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
+2 -2
View File
@@ -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 | — |
+2 -2
View File
@@ -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.