Effects: Sketch › Bas Relief, Charcoal, Photocopy and Stamp (#2527)

Squashed from #2533 onto #2525 (Smudge Stick, Sponge, Watercolor): both sets in PIXEL_EFFECTS (45), the catalogs and the docs, whose counts now read 45/57.
This commit is contained in:
Martin Vlach
2026-10-20 06:43:50 +07:00
committed by bflatastic
parent d14b67e015
commit f664564e97
40 changed files with 801 additions and 33 deletions
+1 -1
View File
@@ -246,7 +246,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);
- about half of the raster effects (21 of 57 still missing; Cutout, Film Grain, Neon Glow, Poster Edges, Smudge Stick, Sponge and Watercolor are available under Artistic, Halftone Pattern under Sketch) and the Effect Gallery;
- most of the raster effects (12 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, Photocopy, Plaster, Reticulation and Stamp 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.
+3 -3
View File
@@ -29,7 +29,7 @@ blocking it ([Alpha gate](docs/roadmap.md#alpha-gate)).
| | Value | Kind |
|---|---|---|
| **Feature breadth** (Illustrator's menu items, tools, effects, formats exist) | **~88%** | measured in part: menu items 348/375 wired (92.8%), tools 89/92, Illustrator effects 46/50, Photoshop effects 42/57, 3D 4/4 (initial types, no materials) |
| **Feature breadth** (Illustrator's menu items, tools, effects, formats exist) | **~88%** | measured in part: menu items 348/375 wired (92.8%), tools 89/92, Illustrator effects 46/50, Photoshop effects 45/57, 3D 4/4 (initial types, no materials) |
| **Feature depth** (weighted by use, scored by behaviour) | **~75%** | estimated |
| **To beta** | **~170–270 h** one agent · ~50–80 h with 4–6 agents | estimated |
| **To full parity** | **~360–590 h** one agent · ~95–165 h with 4–6 agents | estimated |
@@ -76,7 +76,7 @@ The dimension hours overlap (feature rows hold some UI, format and AI work), so
| Strokes, brushes, width profiles | 88% | 8–15 |
| Appearance, transparency, graphic styles, masks | 97% | 1–2 |
| Live vector effects | 85% | 6–10 |
| Raster effects (Effect Gallery) — [effects-parity.md](docs/effects-parity.md) | 49% | 14–24 |
| Raster effects (Effect Gallery) — [effects-parity.md](docs/effects-parity.md) | 79% | 8–14 |
| 3D and Materials | ~25% | 30–55 |
| Type core — [type-parity.md](docs/type-parity.md) | 80% | 14–19 |
| Type advanced (CJK, spelling, Touch Type) | 50% | 20–28 |
@@ -123,7 +123,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap
2. **`.ai` files from real users** (G9): layer structure, guides, speed: 10–20 h.
3. **Launch and stability on real machines** (G10): 20–35 h.
4. **Core-tool correctness** (G11: Shape Builder regions, Asset Export borders): 8–15 h.
5. **Raster effects** (G2) alongside: 15 filters (7 Artistic, 8 Sketch) and the Effect Gallery, 14–24 h.
5. **Raster effects** (G2) alongside: the 12 remaining filters (7 Artistic, 4 Sketch, 1 more) and the Effect Gallery, 8–14 h.
6. **Then:** type core and advanced type (G4), the Illustrator 2026 additions (G13), 3D (G3).
## Progress log
+28
View File
@@ -496,6 +496,13 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
"{amount: % 1..500 (50), radius: pt 0.1..250 (1; σ of the blur edges are found against), threshold: levels 0..255 (0; smaller differences are left alone)}",
json!({"amount": 50.0, "radius": 1.0, "threshold": 0.0}),
),
r(
"sketch.basRelief",
"Bas Relief…",
SKETCH,
"{detail: 1..15 (13; the larger, the finer the features that stay), smoothness: 1..15 (3; rounds the carving), lightDirection: \"bottom\"|\"bottomLeft\"|\"left\"|\"topLeft\"|\"top\"|\"topRight\"|\"right\"|\"bottomRight\" (\"bottom\"; where the light comes from)} carves the object in low relief in black and white: surfaces facing the light turn white, those turned away black, flat areas grey. Coverage is kept. Original visual approximation; chosen VectorCraft defaults.",
json!({"detail": 13.0, "smoothness": 3.0, "lightDirection": "bottom"}),
),
r(
"sketch.chalkCharcoal",
"Chalk & Charcoal…",
@@ -503,6 +510,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.charcoal",
"Charcoal…",
SKETCH,
"{thickness: 1..7 (1; width of the bold edges and of the strokes' spacing), detail: 0..5 (1; the larger, the finer the edges), lightDarkBalance: 0..100 (50; how far into the lights the charcoal reaches)} redraws the object in black charcoal on white paper: bold edges and diagonal strokes over the midtones. Coverage is kept. Original visual approximation; chosen VectorCraft defaults.",
json!({"thickness": 1.0, "detail": 1.0, "lightDarkBalance": 50.0}),
),
r(
"sketch.chrome",
"Chrome…",
@@ -524,6 +538,13 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
"{size: pt 1..12 (1; how far apart the screen's dots, rings or lines are), contrast: 0..50 (5; 50 draws pure black and white), patternType: \"circle\"|\"dot\"|\"line\" (\"dot\"; rings round the object's centre, dots on a square grid, horizontal lines)} redraws the object in black ink on white paper through a halftone screen whose ink covers as much as the colour is dark, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.",
json!({"size": 1.0, "contrast": 5.0, "patternType": "dot"}),
),
r(
"sketch.photocopy",
"Photocopy…",
SKETCH,
"{detail: 1..24 (7; the larger, the finer the features that copy), darkness: 1..50 (25; the larger, the more of the dark copies)} copies the object in black on white: large dark areas copy only around their edges and midtones go to black or white. Coverage is kept. Original visual approximation; chosen VectorCraft defaults.",
json!({"detail": 7.0, "darkness": 25.0}),
),
r(
"sketch.plaster",
"Plaster…",
@@ -538,6 +559,13 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
"{density: 0..50 (12; finer grain at higher values), foregroundLevel: 0..50 (6; pulls shadows toward black ink), backgroundLevel: 0..50 (6; pulls highlights toward white paper)} redraws the object in black ink on white paper through a high-contrast film-grain screen, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.",
json!({"density": 12.0, "foregroundLevel": 6.0, "backgroundLevel": 6.0}),
),
r(
"sketch.stamp",
"Stamp…",
SKETCH,
"{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(
"stylize.glowingEdges",
"Glowing Edges…",
+45 -1
View File
@@ -25,6 +25,7 @@ mod paint;
mod pixelate;
mod sharpen;
mod sketch;
mod sketch_ink;
mod texture;
mod video;
@@ -42,7 +43,7 @@ pub use sketch::{HALFTONE_TYPES, HalftonePattern};
pub use texture::{GRAIN_TYPES, Grain, LIGHT_DIRECTIONS, Light, TEXTURES, Texture};
/// The Photoshop-style effect ids (all raster effects, see [`crate::is_raster`]).
pub const PIXEL_EFFECTS: [&str; 41] = [
pub const PIXEL_EFFECTS: [&str; 45] = [
"artistic.cutout",
"artistic.filmGrain",
"artistic.neonGlow",
@@ -69,12 +70,16 @@ pub const PIXEL_EFFECTS: [&str; 41] = [
"pixelate.mezzotint",
"pixelate.pointillize",
"sharpen.unsharpMask",
"sketch.basRelief",
"sketch.chalkCharcoal",
"sketch.charcoal",
"sketch.chrome",
"sketch.graphicPen",
"sketch.halftonePattern",
"sketch.photocopy",
"sketch.plaster",
"sketch.reticulation",
"sketch.stamp",
"stylize.glowingEdges",
"texture.craquelure",
"texture.grain",
@@ -175,6 +180,18 @@ pub enum PixelFx {
/// Sketch › Halftone Pattern: black ink on white paper through a `kind` screen of cells `size`
/// (1..12) apart, its edges as hard as `contrast` (0..50) says.
HalftonePattern { size: f64, contrast: f64, kind: HalftonePattern },
/// Sketch › Bas Relief: low relief lit from `light`, keeping the finer features as `detail`
/// (1..15) says and rounded by `smoothness` (1..15).
BasRelief { detail: f64, smoothness: f64, light: Light },
/// Sketch › Charcoal: bold edges `thickness` (1..7) wide, as fine as `detail` (0..5) says, and
/// diagonal strokes over the midtones; `balance` (0..100) sets how far into the lights it reaches.
Charcoal { thickness: f64, detail: f64, balance: f64 },
/// Sketch › Photocopy: black only where a pixel is darker than its surroundings, as fine as
/// `detail` (1..24) says, and as readily as `darkness` (1..50) says.
Photocopy { detail: f64, darkness: f64 },
/// Sketch › Stamp: solid black and white, printing black as much as `balance` (0..50) says,
/// its shapes rounded by `smoothness` (1..50).
Stamp { balance: f64, smoothness: f64 },
/// Sketch › Plaster: relief plaster lit from `light`; dark areas raised, light recessed;
/// `imageBalance` (1..50) shifts more of the image lower at higher values; `smoothness` (1..15)
/// softens the relief.
@@ -387,6 +404,22 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option<PixelFx> {
contrast: num(p, "contrast", 5.0).clamp(0.0, 50.0),
kind: HalftonePattern::parse(text(p, "patternType", "dot")),
},
"sketch.basRelief" => PixelFx::BasRelief {
detail: num(p, "detail", 13.0).clamp(1.0, 15.0),
smoothness: num(p, "smoothness", 3.0).clamp(1.0, 15.0),
light: Light::parse(text(p, "lightDirection", "bottom")),
},
"sketch.charcoal" => PixelFx::Charcoal {
thickness: num(p, "thickness", 1.0).clamp(1.0, 7.0),
detail: num(p, "detail", 1.0).clamp(0.0, 5.0),
balance: num(p, "lightDarkBalance", 50.0).clamp(0.0, 100.0),
},
"sketch.photocopy" => {
PixelFx::Photocopy { detail: num(p, "detail", 7.0).clamp(1.0, 24.0), darkness: num(p, "darkness", 25.0).clamp(1.0, 50.0) }
}
"sketch.stamp" => {
PixelFx::Stamp { balance: num(p, "lightDarkBalance", 25.0).clamp(0.0, 50.0), smoothness: num(p, "smoothness", 5.0).clamp(1.0, 50.0) }
}
"sketch.plaster" => PixelFx::Plaster {
image_balance: num(p, "imageBalance", 20.0).clamp(1.0, 50.0),
smoothness: num(p, "smoothness", 8.0).clamp(1.0, 15.0),
@@ -481,6 +514,7 @@ impl PixelFx {
| PixelFx::HalftonePattern { .. }
| PixelFx::Plaster { .. }
| PixelFx::Reticulation { .. } => 0.0,
PixelFx::BasRelief { .. } | PixelFx::Charcoal { .. } | PixelFx::Photocopy { .. } | PixelFx::Stamp { .. } => 0.0,
PixelFx::GlowingEdges { width, smoothness, .. } => width.max(smoothness * 3.0),
// These three keep the object's coverage.
PixelFx::SmudgeStick { .. } | PixelFx::Sponge { .. } | PixelFx::Watercolor { .. } => 0.0,
@@ -531,6 +565,10 @@ impl PixelFx {
PixelFx::Glass { distortion, .. } => Some(distort::glass_shift(distortion)),
PixelFx::OceanRipple { size, magnitude } => Some(distort::ripple_shift(size, magnitude)),
PixelFx::UnsharpMask { radius, .. } => Some(3.0 * radius),
PixelFx::BasRelief { detail, smoothness, .. } => Some(sketch_ink::relief_reach(detail, smoothness)),
PixelFx::Charcoal { thickness, detail, .. } => Some(sketch_ink::charcoal_reach(thickness, detail)),
PixelFx::Photocopy { detail, .. } => Some(sketch_ink::copy_reach(detail)),
PixelFx::Stamp { smoothness, .. } => Some(sketch_ink::stamp_reach(smoothness)),
PixelFx::GlowingEdges { width, smoothness, .. } => Some(width.max(smoothness * 3.0)),
// 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),
@@ -601,6 +639,10 @@ impl PixelFx {
PixelFx::Chrome { detail, smoothness } => sketch::chrome(px, w, h, space, detail, smoothness),
PixelFx::GraphicPen { length, balance, direction } => brushstrokes::graphic_pen(px, w, h, space, length, balance, direction),
PixelFx::HalftonePattern { size, contrast, kind } => sketch::halftone_pattern(px, w, h, space, size, contrast, kind),
PixelFx::BasRelief { detail, smoothness, light } => sketch_ink::bas_relief(px, w, h, space, detail, smoothness, light),
PixelFx::Charcoal { thickness, detail, balance } => sketch_ink::charcoal(px, w, h, space, thickness, detail, balance),
PixelFx::Photocopy { detail, darkness } => sketch_ink::photocopy(px, w, h, space, detail, darkness),
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::GlowingEdges { width, brightness, smoothness } => edges::glow(px, w, h, to_px(width), brightness, to_px(smoothness)),
@@ -853,3 +895,5 @@ pub fn gaussian_rgba(data: &mut [u8], w: usize, h: usize, sigma: f64) {
mod tests;
#[cfg(test)]
mod tests_paint;
#[cfg(test)]
mod tests_sketch;
+209
View File
@@ -0,0 +1,209 @@
//! Effect › Sketch › Bas Relief, Charcoal, Photocopy and Stamp. Original filters built from public
//! behaviour documentation: <https://helpx.adobe.com/illustrator/using/summary-effects.html> and
//! <https://helpx.adobe.com/photoshop-elements/using/sketch-filters.html> (the Sketch effects
//! redraw the object in the foreground and background colours, black on white here; Bas Relief
//! carves low relief lit from a direction, Charcoal draws bold edges and sketches the midtones in
//! diagonal strokes, Photocopy copies only the edges of large dark areas and takes the midtones to
//! black or white, Stamp simplifies the image to solid black and white as a rubber stamp would).
//! Visual approximations, not reproductions of a proprietary algorithm; the ranges and defaults
//! are chosen for VectorCraft.
//!
//! All four keep the object's coverage, so they paint nothing outside it. Their sizes are in
//! document points, whatever the raster's resolution.
use std::f64::consts::FRAC_1_SQRT_2;
use super::pixelate::{Place, cell, value_noise};
use super::texture::{Light, Tones, premultiply};
use super::{PixelSpace, blur_plane};
const SALT_STROKES: u64 = 440;
/// A pixel's size (points) at 72 ppi: how far the neighbours of a gradient add to a filter's reach.
const MARGIN: f64 = 1.0;
/// Redraw `p` as ink `ink` (0..1) of black on white paper, keeping its coverage.
fn paint(p: &mut [u8; 4], ink: f64) {
let v = if ink.is_finite() { (1.0 - ink).clamp(0.0, 1.0) as f32 } else { 1.0 };
*p = premultiply([v; 3], f32::from(p[3]) / 255.0);
}
/// A smooth step from 0 at `t` ≤ 0 to 1 at `t` ≥ 1 (0 when `t` is not a number).
fn ramp(t: f64) -> f64 {
let t = if t.is_finite() { t.clamp(0.0, 1.0) } else { 0.0 };
t * t * (3.0 - 2.0 * t)
}
/// The tone at (`x`, `y`) and its gradient per pixel, from central differences.
fn gradient(tones: &Tones, x: usize, y: usize) -> (f64, f64, f64) {
let gx = 0.5 * (tones.get(x + 1, y) - tones.get(x.saturating_sub(1), y));
let gy = 0.5 * (tones.get(x, y + 1) - tones.get(x, y.saturating_sub(1)));
(tones.get(x, y), gx, gy)
}
/// The σ (points) Bas Relief softens the object by, for Detail `detail` (1..15) and Smoothness
/// `smoothness` (1..15).
fn relief_softness(detail: f64, smoothness: f64) -> f64 {
0.2 + 0.15 * smoothness.clamp(1.0, 15.0) + 0.1 * (15.0 - detail.clamp(1.0, 15.0))
}
/// How far (document units) Bas Relief reaches: the softened tones and their neighbours.
pub(super) fn relief_reach(detail: f64, smoothness: f64) -> f64 {
3.0 * relief_softness(detail, smoothness) + MARGIN
}
/// Bas Relief: the object carved in low relief and lit from `light`. Surfaces facing the light turn
/// white, those turned away black, flat areas grey with a hint of the object's own tones; `detail`
/// (1..15) keeps finer features and `smoothness` (1..15) rounds the carving.
pub(super) fn bas_relief(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, detail: f64, smoothness: f64, light: Light) {
if !(detail.is_finite() && smoothness.is_finite()) {
return;
}
let Some(place) = Place::new(space, w, h) else { return };
let sigma = relief_softness(detail, smoothness);
let tones = Tones::new(px, w, h, sigma / place.px);
let toward = light.toward();
place.each(px, |x, y, p| {
if p[3] == 0 {
return;
}
let (tone, slope) = tones.slope(x, y, &place);
// A step from black to white, softened by σ, rises 0.4 per σ at its steepest.
let lit = -slope.dot(toward) * sigma;
paint(p, 1.0 - (0.5 + 0.2 * (tone - 0.5) + 2.5 * lit));
});
}
/// The σ (points) Charcoal softens the object by to find its edges, for Detail `detail` (0..5).
fn charcoal_softness(detail: f64) -> f64 {
0.25 + 0.25 * (5.0 - detail.clamp(0.0, 5.0)) + 0.25
}
/// The σ (points) that thickens Charcoal's edges, for Thickness `thickness` (1..7).
fn charcoal_thickening(thickness: f64) -> f64 {
0.35 * thickness.clamp(1.0, 7.0)
}
/// The distance (points) between the strokes sketching Charcoal's midtones.
fn charcoal_spacing(thickness: f64) -> f64 {
1.5 + 0.5 * thickness.clamp(1.0, 7.0)
}
/// How far (document units) Charcoal reaches: the edges, their thickening and the strokes' tones.
pub(super) fn charcoal_reach(thickness: f64, detail: f64) -> f64 {
(3.0 * (charcoal_softness(detail) + charcoal_thickening(thickness)) + 2.0 * MARGIN).max(2.0 * charcoal_spacing(thickness) + MARGIN)
}
/// Charcoal: bold, `thickness` (1..7) wide edges, as fine as `detail` (0..5) says, and the
/// midtones sketched in diagonal strokes; `balance` (0..100) sets how far into the lights the
/// charcoal reaches (50 is even).
pub(super) fn charcoal(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, thickness: f64, detail: f64, balance: f64) {
if !(thickness.is_finite() && detail.is_finite() && balance.is_finite()) {
return;
}
let Some(place) = Place::new(space, w, h) else { return };
let sigma = charcoal_softness(detail);
let spacing = charcoal_spacing(thickness);
let bias = (balance.clamp(0.0, 100.0) - 50.0) / 100.0 * 0.8;
// The edges: how much the softened tones change across σ, 1 for a step from black to white.
let edge_tones = Tones::new(px, w, h, sigma / place.px);
let mut edge: Vec<f32> = (0..w * h)
.map(|k| {
let (_, gx, gy) = gradient(&edge_tones, k % w, k / w);
let contrast = gx.hypot(gy) / place.px * sigma / 0.4;
let weakest = 0.35 - 0.2 * bias / 0.4;
ramp((contrast - weakest) / 0.3) as f32
})
.collect();
blur_plane(&mut edge, w, h, charcoal_thickening(thickness) / place.px);
// The strokes' tones are softer than the edges', so the strokes don't follow every speck.
let tones = Tones::new(px, w, h, 0.5 * spacing / place.px);
place.each(px, |x, y, p| {
if p[3] == 0 {
return;
}
let bold = edge.get(y * w + x).map_or(0.0, |e| ((f64::from(*e) - 0.3) / 0.3).clamp(0.0, 1.0));
let dark = 1.0 - tones.get(x, y);
let share = ((dark + bias - 0.2) / 0.6).clamp(0.0, 1.0);
let q = place.at(x, y);
// Across the strokes, which run rising to the right, and along them.
let (across, along) = ((q.x + q.y) * FRAC_1_SQRT_2 / spacing, (q.x - q.y) * FRAC_1_SQRT_2 / spacing);
let stroke = match (share > 0.0, cell(across)) {
(true, Some(line)) => {
let f = across - line as f64;
// Each stroke breaks and swells along its length.
let swell = (0.6 + 0.8 * value_noise(SALT_STROKES, along / 6.0, line as f64)).clamp(0.0, 1.2);
let reach = (share * swell).min(1.0);
let soft = 2.0 * place.px / spacing;
(((reach - (2.0 * f - 1.0).abs()) / soft + 0.5).clamp(0.0, 1.0)) * f64::from(reach > 0.0)
}
_ => 0.0,
};
paint(p, bold.max(stroke));
});
}
/// The σ (points) of Photocopy's narrow softening for Detail `detail` (1..24): the less detail,
/// the wider.
fn copy_softness(detail: f64) -> f64 {
0.15 * (25.0 - detail.clamp(1.0, 24.0))
}
/// How much wider Photocopy's second softening is than its first.
const COPY_WIDER: f64 = 3.0;
/// How far (document units) Photocopy reaches: the wide softening.
pub(super) fn copy_reach(detail: f64) -> f64 {
3.0 * COPY_WIDER * copy_softness(detail) + MARGIN
}
/// Photocopy: black where a pixel is darker than its surroundings, white elsewhere. Large dark
/// areas copy only around their edges and midtones go to black or white. `detail` (1..24) keeps
/// finer features and `darkness` (1..50) copies more of the dark.
pub(super) fn photocopy(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, detail: f64, darkness: f64) {
if !(detail.is_finite() && darkness.is_finite()) {
return;
}
let Some(place) = Place::new(space, w, h) else { return };
let sigma = copy_softness(detail) / place.px;
let narrow = Tones::new(px, w, h, sigma);
let wide = Tones::new(px, w, h, COPY_WIDER * sigma);
// How much darker than its surroundings a pixel must be to print.
let threshold = 0.02 + 0.10 * (1.0 - (darkness.clamp(1.0, 50.0) - 1.0) / 49.0);
let soft = 0.02;
place.each(px, |x, y, p| {
if p[3] == 0 {
return;
}
paint(p, ((wide.get(x, y) - narrow.get(x, y) - threshold) / soft + 0.5).clamp(0.0, 1.0));
});
}
/// The σ (points) Stamp softens the object by for Smoothness `smoothness` (1..50).
fn stamp_softness(smoothness: f64) -> f64 {
0.1 * smoothness.clamp(1.0, 50.0)
}
/// How far (document units) Stamp reaches: the softened tones and their neighbours.
pub(super) fn stamp_reach(smoothness: f64) -> f64 {
3.0 * stamp_softness(smoothness) + MARGIN
}
/// Stamp: the object as solid black and white, like a rubber stamp. `balance` (0..50) sets how much
/// of the object prints black and `smoothness` (1..50) rounds the shapes.
pub(super) fn stamp(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, balance: f64, smoothness: f64) {
if !(balance.is_finite() && smoothness.is_finite()) {
return;
}
let Some(place) = Place::new(space, w, h) else { return };
let tones = Tones::new(px, w, h, stamp_softness(smoothness) / place.px);
let threshold = 0.2 + 0.6 * balance.clamp(0.0, 50.0) / 50.0;
place.each(px, |x, y, p| {
if p[3] == 0 {
return;
}
let (tone, gx, gy) = gradient(&tones, x, y);
// A one pixel wide edge, whatever the softening.
paint(p, ((threshold - tone) / gx.hypot(gy).max(0.01) + 0.5).clamp(0.0, 1.0));
});
}
+181
View File
@@ -0,0 +1,181 @@
//! Sketch › Bas Relief, Charcoal, Photocopy and Stamp.
use serde_json::json;
use vectorcraft_doc::Effect;
use vectorcraft_geom::{Affine, Point, Rect};
use super::*;
use crate::{RasterFx, raster_effects};
/// A `w` × `h` raster whose pixels are document points (origin top-left), of an object that fills it.
fn space(w: usize, h: usize) -> PixelSpace {
let (w, h) = (w as f64, h as f64);
PixelSpace {
to_doc: Affine::IDENTITY,
px: 1.0,
center: Point::new(w / 2.0, h / 2.0),
channels: Channels::Rgb,
line: 1.0,
extent: 0.5 * w.hypot(h),
}
}
fn image(w: usize, h: usize, f: impl Fn(usize, usize) -> [u8; 4]) -> Vec<u8> {
(0..h).flat_map(|y| (0..w).map(move |x| (x, y))).flat_map(|(x, y)| f(x, y)).collect()
}
fn fx(id: &str, p: serde_json::Value) -> PixelFx {
let e = Effect { id: id.into(), params: p, visible: true };
match raster_effects(&[e]).first() {
Some(RasterFx::Pixel(p)) => p.clone(),
other => panic!("{id}: {other:?}"),
}
}
fn run(id: &str, p: serde_json::Value, src: &[u8], w: usize, h: usize) -> Vec<u8> {
let mut out = src.to_vec();
fx(id, p).apply(&mut out, w, h, &space(w, h));
out
}
fn grey(v: u8) -> [u8; 4] {
[v, v, v, 255]
}
/// Channel 0 of pixel (`x`, `y`).
fn at(d: &[u8], w: usize, x: usize, y: usize) -> u8 {
d[(y * w + x) * 4]
}
/// How many pixels of channel 0 are darker than 128.
fn black(d: &[u8]) -> usize {
d.as_chunks::<4>().0.iter().filter(|p| p[0] < 128).count()
}
/// No panic and the coverage kept on tiny, huge and non-finite spaces and parameters.
fn survives(id: &str, params: &[serde_json::Value]) {
let (w, h) = (9, 7);
let src = image(w, h, |x, y| [(x * 25) as u8, (y * 30) as u8, 90, if x == 0 { 0 } else { 255 }]);
let tiny = PixelSpace { px: 1e-9, ..space(w, h) };
let huge = PixelSpace { px: 1e9, center: Point::new(f64::MAX, -1e300), ..space(w, h) };
let nan = PixelSpace { px: f64::NAN, ..space(w, h) };
for p in params {
for s in [tiny, huge, nan, space(w, h)] {
let mut out = src.clone();
fx(id, p.clone()).apply(&mut out, w, h, &s);
assert_eq!(out.len(), src.len());
let alphas = |d: &[u8]| d.as_chunks::<4>().0.iter().map(|p| p[3]).collect::<Vec<_>>();
if s.px == 1.0 {
assert_eq!(alphas(&out), alphas(&src), "{id}: coverage kept");
}
}
}
// A size that doesn't match, and an empty raster, do nothing.
let mut odd = vec![1u8; 10];
fx(id, json!({})).apply(&mut odd, 3, 3, &space(3, 3));
assert_eq!(odd, vec![1u8; 10]);
fx(id, json!({})).apply(&mut [], 0, 0, &space(1, 1));
}
#[test]
fn photocopy_copies_the_edges_of_dark_areas() {
assert_eq!(fx("sketch.photocopy", json!({})), PixelFx::Photocopy { detail: 7.0, darkness: 25.0 });
assert_eq!(fx("sketch.photocopy", json!({"detail": 1e308, "darkness": -5})), PixelFx::Photocopy { detail: 24.0, darkness: 1.0 });
let (w, h) = (64, 16);
let src = image(w, h, |x, _| grey(if x < 32 { 30 } else { 230 }));
let out = run("sketch.photocopy", json!({"detail": 20}), &src, w, h);
// Black just on the dark side of the edge; white in the dark area's middle and in the light.
assert!(at(&out, w, 30, 8) < 64, "edge {}", at(&out, w, 30, 8));
assert!(at(&out, w, 6, 8) > 192, "dark area {}", at(&out, w, 6, 8));
assert!(at(&out, w, 50, 8) > 192, "light area {}", at(&out, w, 50, 8));
assert_eq!(out, run("sketch.photocopy", json!({"detail": 20}), &src, w, h), "deterministic");
// A higher Darkness copies more.
let more = run("sketch.photocopy", json!({"detail": 20, "darkness": 50}), &src, w, h);
let less = run("sketch.photocopy", json!({"detail": 20, "darkness": 1}), &src, w, h);
assert!(black(&more) >= black(&less));
survives("sketch.photocopy", &[json!({}), json!({"detail": 1e308, "darkness": "NaN"}), json!({"detail": -1e308, "darkness": [1]})]);
}
#[test]
fn stamp_prints_solid_black_and_white() {
assert_eq!(fx("sketch.stamp", json!({})), PixelFx::Stamp { balance: 25.0, smoothness: 5.0 });
assert_eq!(fx("sketch.stamp", json!({"lightDarkBalance": 1e308, "smoothness": -1})), PixelFx::Stamp { balance: 50.0, smoothness: 1.0 });
let (w, h) = (64, 8);
let src = image(w, h, |x, _| grey((x * 4) as u8));
let few = run("sketch.stamp", json!({"lightDarkBalance": 0}), &src, w, h);
let many = run("sketch.stamp", json!({"lightDarkBalance": 50}), &src, w, h);
assert!(black(&many) > black(&few), "{} vs {}", black(&many), black(&few));
// Pure black and white, but for the one pixel wide edges.
let mixed = many.as_chunks::<4>().0.iter().filter(|p| p[0] != 0 && p[0] != 255).count();
assert!(mixed * 10 < many.len() / 4, "{mixed} grey pixels");
assert_eq!(at(&many, w, 0, 4), 0);
assert_eq!(at(&many, w, 63, 4), 255);
survives(
"sketch.stamp",
&[json!({}), json!({"lightDarkBalance": "NaN", "smoothness": 1e308}), json!({"lightDarkBalance": -1e308, "smoothness": null})],
);
}
#[test]
fn charcoal_draws_bold_edges_and_sketches_the_midtones() {
assert_eq!(fx("sketch.charcoal", json!({})), PixelFx::Charcoal { thickness: 1.0, detail: 1.0, balance: 50.0 });
assert_eq!(
fx("sketch.charcoal", json!({"thickness": 1e308, "detail": -5, "lightDarkBalance": "x"})),
PixelFx::Charcoal { thickness: 7.0, detail: 0.0, balance: 50.0 }
);
let (w, h) = (64, 48);
// Black on the left, white on the right: a bold line at the edge, nothing in the white.
let step = image(w, h, |x, _| grey(if x < 32 { 0 } else { 255 }));
let out = run("sketch.charcoal", json!({}), &step, w, h);
assert!((28..36).any(|x| at(&out, w, x, 24) < 64), "edge");
assert!((48..64).all(|x| at(&out, w, x, 24) == 255), "white stays white");
// A midtone is sketched in strokes, more of it the more the balance reaches into the lights.
let mid = image(w, h, |_, _| grey(128));
let sketched = run("sketch.charcoal", json!({}), &mid, w, h);
let (n, total) = (black(&sketched), w * h);
assert!(n > total / 50 && n < total * 9 / 10, "{n} of {total}");
let darker = run("sketch.charcoal", json!({"lightDarkBalance": 100}), &mid, w, h);
let lighter = run("sketch.charcoal", json!({"lightDarkBalance": 0}), &mid, w, h);
assert!(black(&darker) > black(&lighter));
assert_eq!(sketched, run("sketch.charcoal", json!({}), &mid, w, h), "deterministic");
let white = image(w, h, |_, _| grey(255));
assert_eq!(black(&run("sketch.charcoal", json!({"lightDarkBalance": 0}), &white, w, h)), 0);
survives(
"sketch.charcoal",
&[json!({}), json!({"thickness": 1e308, "detail": "NaN", "lightDarkBalance": [1]}), json!({"thickness": -1e308, "detail": 1e308})],
);
}
#[test]
fn bas_relief_lights_the_surfaces_facing_the_light() {
assert_eq!(fx("sketch.basRelief", json!({})), PixelFx::BasRelief { detail: 13.0, smoothness: 3.0, light: Light::Bottom });
assert_eq!(
fx("sketch.basRelief", json!({"detail": 0, "smoothness": 1e308, "lightDirection": "Top"})),
PixelFx::BasRelief { detail: 1.0, smoothness: 15.0, light: Light::Top }
);
// Dark above, light below: the edge is lit from one side and shaded from the other.
let (w, h) = (16, 48);
let src = image(w, h, |_, y| grey(if y < 24 { 20 } else { 235 }));
let below = run("sketch.basRelief", json!({"lightDirection": "bottom"}), &src, w, h);
let above = run("sketch.basRelief", json!({"lightDirection": "top"}), &src, w, h);
let edge = (0..h).map(|y| (y, i32::from(at(&below, w, 8, y)) - i32::from(at(&above, w, 8, y)))).max_by_key(|(_, d)| d.abs());
assert!(edge.is_some_and(|(_, d)| d.abs() > 100), "{edge:?}");
// Flat areas are grey.
for y in [2, 45] {
let v = at(&below, w, 8, y);
assert!((90..190).contains(&v), "flat {v}");
}
survives(
"sketch.basRelief",
&[json!({}), json!({"detail": "NaN", "smoothness": 1e308, "lightDirection": 3}), json!({"detail": -1e308, "smoothness": null})],
);
}
#[test]
fn sketch_filters_reach_beyond_nothing_they_do_not_paint() {
for id in ["sketch.basRelief", "sketch.charcoal", "sketch.photocopy", "sketch.stamp"] {
let f = fx(id, json!({}));
assert_eq!(f.outset(Rect::new(0.0, 0.0, 50.0, 50.0)), 0.0, "{id} keeps the object's coverage");
assert!(f.reach().is_some_and(|r| r.is_finite() && r > 0.0), "{id} reach");
}
}
+1 -1
View File
@@ -269,7 +269,7 @@ impl Tones {
}
/// The tone at pixel (`x`, `y`) and its slope per document unit along the document's axes.
fn slope(&self, x: usize, y: usize, place: &Place) -> (f64, Vec2) {
pub(super) fn slope(&self, x: usize, y: usize, place: &Place) -> (f64, Vec2) {
let gx = 0.5 * (self.get(x + 1, y) - self.get(x.saturating_sub(1), y));
let gy = 0.5 * (self.get(x, y + 1) - self.get(x, y.saturating_sub(1)));
// Per pixel → per document unit: through the transpose of the document → pixel map.
+5 -1
View File
@@ -52,13 +52,17 @@ const PIXELATE: [(&str, &str); 4] =
[("pixelate.colorHalftone", "{}"), ("pixelate.crystallize", "{}"), ("pixelate.mezzotint", "{}"), ("pixelate.pointillize", "{}")];
/// Effect › Sketch, at their defaults.
const SKETCH: [(&str, &str); 6] = [
const SKETCH: [(&str, &str); 10] = [
("sketch.basRelief", "{}"),
("sketch.chalkCharcoal", "{}"),
("sketch.charcoal", "{}"),
("sketch.chrome", "{}"),
("sketch.graphicPen", "{}"),
("sketch.halftonePattern", "{}"),
("sketch.photocopy", "{}"),
("sketch.plaster", "{}"),
("sketch.reticulation", "{}"),
("sketch.stamp", "{}"),
];
/// Effect › Texture, at their defaults.
+4
View File
@@ -340,6 +340,10 @@ fn structured_junk() {
("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.basRelief", "params": {"detail": "NaN", "smoothness": 1e308, "lightDirection": 3}})),
("effect.apply", json!({"effect": "sketch.charcoal", "params": {"thickness": 1e308, "detail": -1e308, "lightDarkBalance": [1]}})),
("effect.apply", json!({"effect": "sketch.photocopy", "params": {"detail": "1e999", "darkness": null}})),
("effect.apply", json!({"effect": "sketch.stamp", "params": {"lightDarkBalance": -1e308, "smoothness": "NaN"}})),
("effect.apply", json!({"effect": "sketch.reticulation", "params": {"density": "NaN", "foregroundLevel": -1e308, "backgroundLevel": 1e308}})),
("effect.apply", json!({"effect": "texture.craquelure", "params": {"crackSpacing": -1e308, "crackDepth": "NaN", "crackBrightness": 1e308}})),
("effect.apply", json!({"effect": "texture.grain", "params": {"intensity": 1e308, "contrast": [1], "grainType": 7}})),
+1 -1
View File
@@ -288,7 +288,7 @@ fn choices(effect: &str, key: &str) -> Option<form::Choices> {
("sketch.plaster", "lightDirection") => Some(&vectorcraft_effects::pixel::LIGHT_DIRECTIONS),
("texture.grain", "grainType") => Some(&vectorcraft_effects::pixel::GRAIN_TYPES),
("texture.texturizer", "texture") => Some(&vectorcraft_effects::pixel::TEXTURES),
("texture.texturizer", "lightDirection") => Some(&vectorcraft_effects::pixel::LIGHT_DIRECTIONS),
("sketch.basRelief" | "texture.texturizer", "lightDirection") => Some(&vectorcraft_effects::pixel::LIGHT_DIRECTIONS),
("video.deinterlace", "eliminate") => Some(&[("Odd Fields", "odd"), ("Even Fields", "even")]),
("video.deinterlace", "create") => Some(&[("Duplication", "duplication"), ("Interpolation", "interpolation")]),
("stylize.innerGlow", "source") => Some(&[("Center", "center"), ("Edge", "edge")]),
+1
View File
@@ -453,6 +453,7 @@ pub(crate) fn humanize(k: &str) -> String {
"Channel2" => "Channel 2:".into(),
"Channel3" => "Channel 3:".into(),
"Channel4" => "Channel 4:".into(),
"Light Dark Balance" => "Light/Dark Balance:".into(),
"Include Cmy Blacks" => "Include Blacks with CMY:".into(),
"Align To Path" => "Align to Path:".into(),
"Overlap Variation" | "Curviness Variation" | "Spacing Variation" => "Variation:".into(),
+4
View File
@@ -213,7 +213,11 @@ fn raster_filter_dialogs_preview_and_apply() {
("pixelate.crystallize", "cellSize", "20"),
("pixelate.mezzotint", "type", "longLines"),
("pixelate.pointillize", "cellSize", "12"),
("sketch.basRelief", "lightDirection", "left"),
("sketch.charcoal", "thickness", "5"),
("sketch.halftonePattern", "patternType", "line"),
("sketch.photocopy", "darkness", "40"),
("sketch.stamp", "lightDarkBalance", "10"),
("sketch.plaster", "lightDirection", "bottomLeft"),
("texture.craquelure", "crackDepth", "9"),
("texture.grain", "grainType", "stippled"),
+11
View File
@@ -5175,6 +5175,17 @@ theme Appearance Външен вид
Definition Дефиниция
Brush Detail Детайл на четката
Shadow Intensity Интензивност на сенките
Bas Relief… Барелеф…
Bas Relief Барелеф
Charcoal… Въглен…
Charcoal Въглен
Photocopy… Фотокопие…
Photocopy Фотокопие
Stamp… Печат…
Stamp Печат
Thickness Дебелина
Detail Детайл
Light/Dark Balance Баланс светло/тъмно
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one „Редактиране на оригинал“ отваря само файлове с изображения и документи; „{name}“ не е такъв файл
@msg only http, https and mailto URLs open in the browser, not “{url}” в браузъра се отварят само URL адреси с http, https и mailto, не „{url}“
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5202,6 +5202,17 @@ mark-style Japanese Japonské
Definition Definice
Brush Detail Detail štětce
Shadow Intensity Intenzita stínů
Bas Relief… Basreliéf…
Bas Relief Basreliéf
Charcoal… Uhel…
Charcoal Uhel
Photocopy… Fotokopie…
Photocopy Fotokopie
Stamp… Razítko…
Stamp Razítko
Thickness Tloušťka
Detail Detail
Light/Dark Balance Vyvážení světlé/tmavé
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Upravit originál otevírá pouze soubory obrázků a dokumentů; „{name}“ mezi ně nepatří
@msg only http, https and mailto URLs open in the browser, not “{url}” v prohlížeči se otevírají pouze adresy URL http, https a mailto, ne „{url}“
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5221,6 +5221,17 @@ swatch Bright Violet Kraftig violet
Definition Definition
Brush Detail Penseldetalje
Shadow Intensity Skyggeintensitet
Bas Relief… Basrelief…
Bas Relief Basrelief
Charcoal… Kul…
Charcoal Kul
Photocopy… Fotokopi…
Photocopy Fotokopi
Stamp… Stempel…
Stamp Stempel
Thickness Tykkelse
Detail Detalje
Light/Dark Balance Lys/mørk-balance
Move Anchor Point Flyt ankerpunkt
Chalk & Charcoal… Kridt og kul…
Chalk & Charcoal Kridt og kul
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -4781,6 +4781,18 @@ effect Soft Weich
Definition Definition
Brush Detail Pinseldetail
Shadow Intensity Schattenintensität
Bas Relief… Basrelief…
Bas Relief Basrelief
Charcoal… Holzkohle…
Charcoal Holzkohle
Photocopy… Fotokopie…
Photocopy Fotokopie
Stamp… Stempel…
Stamp Stempel
Thickness Stärke
Detail Detail
Light/Dark Balance Hell-Dunkel-Balance
Light Direction Lichtrichtung
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Original bearbeiten öffnet nur Bild- und Dokumentdateien; „{name}“ gehört nicht dazu
@msg only http, https and mailto URLs open in the browser, not “{url}” nur http-, https- und mailto-URLs werden im Browser geöffnet, nicht „{url}“
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5154,6 +5154,17 @@ effect Points Σημεία
Definition Ευκρίνεια
Brush Detail Λεπτομέρεια πινέλου
Shadow Intensity Ένταση σκιών
Bas Relief… Ανάγλυφο…
Bas Relief Ανάγλυφο
Charcoal… Κάρβουνο…
Charcoal Κάρβουνο
Photocopy… Φωτοαντίγραφο…
Photocopy Φωτοαντίγραφο
Stamp… Σφραγίδα…
Stamp Σφραγίδα
Thickness Πάχος
Detail Λεπτομέρεια
Light/Dark Balance Ισορροπία φωτεινού/σκούρου
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one η Επεξεργασία πρωτοτύπου ανοίγει μόνο αρχεία εικόνων και εγγράφων· το «{name}» δεν είναι τέτοιο αρχείο
@msg only http, https and mailto URLs open in the browser, not “{url}” στο πρόγραμμα περιήγησης ανοίγουν μόνο URL http, https και mailto, όχι το «{url}»
Can't render this file because it contains an unexpected character in line 2016 and column 19.
+11
View File
@@ -5144,6 +5144,17 @@ mark-style Japanese Japonesas
Definition Definición
Brush Detail Detalle del pincel
Shadow Intensity Intensidad de sombras
Bas Relief… Bajorrelieve…
Bas Relief Bajorrelieve
Charcoal… Carboncillo…
Charcoal Carboncillo
Photocopy… Fotocopia…
Photocopy Fotocopia
Stamp… Sello…
Stamp Sello
Thickness Grosor
Detail Detalle
Light/Dark Balance Equilibrio claro/oscuro
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Editar original solo abre archivos de imagen y de documento; “{name}” no lo es
@msg only http, https and mailto URLs open in the browser, not “{url}” solo se abren en el navegador las URL http, https y mailto, no “{url}”
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5166,6 +5166,17 @@ mark-style Japanese Japanilainen
Definition Terävyys
Brush Detail Siveltimen yksityiskohta
Shadow Intensity Varjojen voimakkuus
Bas Relief… Matala korkokuva…
Bas Relief Matala korkokuva
Charcoal… Hiili…
Charcoal Hiili
Photocopy… Valokopio…
Photocopy Valokopio
Stamp… Leima…
Stamp Leima
Thickness Paksuus
Detail Yksityiskohta
Light/Dark Balance Vaalean ja tumman tasapaino
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Muokkaa alkuperäistä avaa vain kuva- ja asiakirjatiedostoja; “{name}” ei ole sellainen
@msg only http, https and mailto URLs open in the browser, not “{url}” selaimessa avataan vain http-, https- ja mailto-URL-osoitteet, ei “{url}”
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5201,6 +5201,17 @@ theme Appearance Apparence
Definition Définition
Brush Detail Détail du pinceau
Shadow Intensity Intensité des ombres
Bas Relief… Bas-relief…
Bas Relief Bas-relief
Charcoal… Fusain…
Charcoal Fusain
Photocopy… Photocopie…
Photocopy Photocopie
Stamp… Tampon…
Stamp Tampon
Thickness Épaisseur
Detail Détail
Light/Dark Balance Équilibre clair/foncé
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Modifier l’original n’ouvre que les fichiers d’image et de document ; « {name} » n’en est pas un
@msg only http, https and mailto URLs open in the browser, not “{url}” seules les URL http, https et mailto s’ouvrent dans le navigateur, pas « {url} »
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5188,6 +5188,17 @@ imageTrace Outline Körvonal
Definition Kidolgozottság
Brush Detail Ecsetrészletesség
Shadow Intensity Árnyék intenzitása
Bas Relief… Dombormű…
Bas Relief Dombormű
Charcoal… Szén…
Charcoal Szén
Photocopy… Fénymásolat…
Photocopy Fénymásolat
Stamp… Bélyegző…
Stamp Bélyegző
Thickness Vastagság
Detail Részletesség
Light/Dark Balance Világos/sötét egyensúly
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one az Eredeti szerkesztése csak kép- és dokumentumfájlokat nyit meg; ez nem ilyen: „{name}”
@msg only http, https and mailto URLs open in the browser, not “{url}” a böngészőben csak a http-, https- és mailto-URL-ek nyílnak meg; ez nem ilyen: „{url}”
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5221,6 +5221,17 @@ swatch Bright Violet Violet cerah
Definition Definisi
Brush Detail Detail kuas
Shadow Intensity Intensitas bayangan
Bas Relief… Relief dangkal…
Bas Relief Relief dangkal
Charcoal… Arang…
Charcoal Arang
Photocopy… Fotokopi…
Photocopy Fotokopi
Stamp… Stempel…
Stamp Stempel
Thickness Ketebalan
Detail Detail
Light/Dark Balance Keseimbangan terang/gelap
Move Anchor Point Pindahkan titik jangkar
Chalk & Charcoal… Kapur dan arang…
Chalk & Charcoal Kapur dan arang
Can't render this file because it contains an unexpected character in line 3772 and column 19.
+11
View File
@@ -4815,6 +4815,17 @@ lighting Light Luce
Definition Definizione
Brush Detail Dettaglio pennello
Shadow Intensity Intensità ombre
Bas Relief… Bassorilievo…
Bas Relief Bassorilievo
Charcoal… Carboncino…
Charcoal Carboncino
Photocopy… Fotocopia…
Photocopy Fotocopia
Stamp… Timbro…
Stamp Timbro
Thickness Spessore
Detail Dettaglio
Light/Dark Balance Equilibrio chiaro/scuro
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Modifica originale apre solo file di immagini e documenti; “{name}” non lo è
@msg only http, https and mailto URLs open in the browser, not “{url}” nel browser si aprono solo URL http, https e mailto, non “{url}”
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -4855,6 +4855,17 @@ lighting Light ライト
Definition 精細度
Brush Detail ブラシの細かさ
Shadow Intensity シャドウの強さ
Bas Relief… レリーフ…
Bas Relief レリーフ
Charcoal… 木炭…
Charcoal 木炭
Photocopy… コピー…
Photocopy コピー
Stamp… スタンプ…
Stamp スタンプ
Thickness 太さ
Detail ディテール
Light/Dark Balance 明暗のバランス
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one 「オリジナルを編集」で開けるのは画像ファイルとドキュメントファイルだけです: 「{name}」はどちらでもありません
@msg only http, https and mailto URLs open in the browser, not “{url}” ブラウザーで開けるのは http、https、mailto の URL だけです: 「{url}」は開けません
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5147,6 +5147,17 @@ imageTrace Outline კონტური
Definition განსაზღვრულობა
Brush Detail ფუნჯის დეტალი
Shadow Intensity ჩრდილის ინტენსივობა
Bas Relief… დაბალი რელიეფი…
Bas Relief დაბალი რელიეფი
Charcoal… ნახშირი…
Charcoal ნახშირი
Photocopy… ფოტოასლი…
Photocopy ფოტოასლი
Stamp… ბეჭედი…
Stamp ბეჭედი
Thickness სისქე
Detail დეტალი
Light/Dark Balance ნათელი/მუქი ბალანსი
Move Anchor Point საყრდენი წერტილის გადაადგილება
Alt key toggles to Smooth Tool Alt კლავიში ჩართავს გაგლუვების ხელსაწყოს
@msg {label} keeps 1 of the document's {n} artboards: the file reopens with one. Turn on Preserve Editing Capabilities to keep them all. {label} ინახავს დოკუმენტის {n} სამუშაო დაფიდან 1-ს: ფაილი ხელახლა ერთით გაიხსნება. ყველას შესანარჩუნებლად ჩართეთ „რედაქტირების შესაძლებლობების შენარჩუნება“.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -4778,6 +4778,17 @@ lighting Light 광원
Definition 선명도
Brush Detail 브러시 세부 묘사
Shadow Intensity 그림자 강도
Bas Relief… 부조…
Bas Relief 부조
Charcoal… 목탄…
Charcoal 목탄
Photocopy… 복사…
Photocopy 복사
Stamp… 스탬프…
Stamp 스탬프
Thickness 두께
Detail 세부
Light/Dark Balance 밝음/어두움 균형
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one 원본 편집은 이미지 및 문서 파일만 엽니다. “{name}”은(는) 해당하지 않습니다
@msg only http, https and mailto URLs open in the browser, not “{url}” 브라우저에서는 http, https, mailto URL만 열립니다. “{url}”은(는) 열 수 없습니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5263,6 +5263,17 @@ swatch Bright Violet Felviolet
Definition Definitie
Brush Detail Penseeldetail
Shadow Intensity Schaduwintensiteit
Bas Relief… Basreliëf…
Bas Relief Basreliëf
Charcoal… Houtskool…
Charcoal Houtskool
Photocopy… Fotokopie…
Photocopy Fotokopie
Stamp… Stempel…
Stamp Stempel
Thickness Dikte
Detail Detail
Light/Dark Balance Licht/donker-balans
Move Anchor Point Ankerpunt verplaatsen
Chalk & Charcoal… Krijt en houtskool…
Chalk & Charcoal Krijt en houtskool
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5054,6 +5054,17 @@ mark-style Japanese Japońskie
Definition Wyrazistość
Brush Detail Szczegółowość pędzla
Shadow Intensity Intensywność cieni
Bas Relief… Płaskorzeźba…
Bas Relief Płaskorzeźba
Charcoal… Węgiel…
Charcoal Węgiel
Photocopy… Fotokopia…
Photocopy Fotokopia
Stamp… Stempel…
Stamp Stempel
Thickness Grubość
Detail Szczegóły
Light/Dark Balance Równowaga jasne/ciemne
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Edytuj oryginał otwiera tylko pliki obrazów i dokumentów; „{name}” do nich nie należy
@msg only http, https and mailto URLs open in the browser, not “{url}” w przeglądarce otwierają się tylko adresy URL http, https i mailto, a nie „{url}”
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5119,6 +5119,17 @@ mark-style Japanese Japonesas
Definition Definição
Brush Detail Detalhe do pincel
Shadow Intensity Intensidade da sombra
Bas Relief… Baixo-relevo…
Bas Relief Baixo-relevo
Charcoal… Carvão…
Charcoal Carvão
Photocopy… Fotocópia…
Photocopy Fotocópia
Stamp… Carimbo…
Stamp Carimbo
Thickness Espessura
Detail Detalhe
Light/Dark Balance Equilíbrio claro/escuro
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Editar Original abre apenas arquivos de imagem e de documento; “{name}” não é um deles
@msg only http, https and mailto URLs open in the browser, not “{url}” apenas URLs http, https e mailto abrem no navegador, não “{url}”
Can't render this file because it contains an unexpected character in line 6 and column 47.
+11
View File
@@ -4811,6 +4811,17 @@ lighting Light Свет
Definition Чёткость
Brush Detail Детализация кисти
Shadow Intensity Интенсивность теней
Bas Relief… Барельеф…
Bas Relief Барельеф
Charcoal… Уголь…
Charcoal Уголь
Photocopy… Фотокопия…
Photocopy Фотокопия
Stamp… Штамп…
Stamp Штамп
Thickness Толщина
Detail Детали
Light/Dark Balance Баланс светлого и тёмного
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one «Редактировать оригинал» открывает только файлы изображений и документов; «{name}» к ним не относится
@msg only http, https and mailto URLs open in the browser, not “{url}” в браузере открываются только URL с http, https и mailto, а не «{url}»
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -5158,6 +5158,17 @@ theme Appearance Utseende
Definition Skärpa
Brush Detail Penseldetalj
Shadow Intensity Skuggintensitet
Bas Relief… Basrelief…
Bas Relief Basrelief
Charcoal… Kol…
Charcoal Kol
Photocopy… Fotokopia…
Photocopy Fotokopia
Stamp… Stämpel…
Stamp Stämpel
Thickness Tjocklek
Detail Detalj
Light/Dark Balance Balans ljust/mörkt
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Redigera original öppnar bara bild- och dokumentfiler; ”{name}” är ingen sådan
@msg only http, https and mailto URLs open in the browser, not “{url}” bara http-, https- och mailto-URL:er öppnas i webbläsaren, inte ”{url}”
Can't render this file because it contains an unexpected character in line 5 and column 25.
+11
View File
@@ -4795,6 +4795,17 @@ lighting Light Світло
Definition Чіткість
Brush Detail Деталізація пензля
Shadow Intensity Інтенсивність тіней
Bas Relief… Барельєф…
Bas Relief Барельєф
Charcoal… Вугілля…
Charcoal Вугілля
Photocopy… Фотокопія…
Photocopy Фотокопія
Stamp… Штамп…
Stamp Штамп
Thickness Товщина
Detail Деталі
Light/Dark Balance Баланс світлого й темного
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one «Редагувати оригінал» відкриває лише файли зображень і документів; «{name}» до них не належить
@msg only http, https and mailto URLs open in the browser, not “{url}” у браузері відкриваються лише URL з http, https і mailto, а не «{url}»
Can't render this file because it contains an unexpected character in line 725 and column 40.
+11
View File
@@ -4795,6 +4795,17 @@ theme Appearance Giao diện
Definition Độ nét
Brush Detail Chi tiết cọ
Shadow Intensity Cường độ bóng
Bas Relief… Chạm nổi…
Bas Relief Chạm nổi
Charcoal… Than củi…
Charcoal Than củi
Photocopy… Bản photocopy…
Photocopy Bản photocopy
Stamp… Con dấu…
Stamp Con dấu
Thickness Độ dày
Detail Chi tiết
Light/Dark Balance Cân bằng sáng/tối
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one Chỉnh sửa bản gốc chỉ mở tệp hình ảnh và tài liệu; “{name}” không thuộc loại đó
@msg only http, https and mailto URLs open in the browser, not “{url}” chỉ URL http, https và mailto mới được mở trong trình duyệt, không mở “{url}”
Can't render this file because it contains an unexpected character in line 3962 and column 10.
+11
View File
@@ -5035,6 +5035,17 @@ lighting Light 光源
Definition 清晰度
Brush Detail 画笔细节
Shadow Intensity 阴影强度
Bas Relief… 浅浮雕…
Bas Relief 浅浮雕
Charcoal… 炭笔…
Charcoal 炭笔
Photocopy… 影印…
Photocopy 影印
Stamp… 图章…
Stamp 图章
Thickness 粗细
Detail 细节
Light/Dark Balance 明暗平衡
Enable Guides for 9-Slice Scaling 启用 9-slice 缩放参考线
@msg cancelled; nothing was changed 已取消;未做任何更改
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it 保存会写入 {_1},而不是 {_2}:要写入该格式请使用 file.saveAsTemplate
Can't render this file because it contains an unexpected character in line 2 and column 73.
+11
View File
@@ -4845,6 +4845,17 @@ lighting Light 光源
Definition 清晰度
Brush Detail 筆刷細節
Shadow Intensity 陰影強度
Bas Relief… 淺浮雕…
Bas Relief 淺浮雕
Charcoal… 炭筆…
Charcoal 炭筆
Photocopy… 影印…
Photocopy 影印
Stamp… 圖章…
Stamp 圖章
Thickness 粗細
Detail 細節
Light/Dark Balance 明暗平衡
Enable Guides for 9-Slice Scaling 啟用 9-slice 縮放參考線
Persistent 持久
Library Panel Options 資料庫面板選項
Can't render this file because it contains an unexpected character in line 6 and column 47.
+25 -7
View File
@@ -178,14 +178,32 @@ fn effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize() {
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…", "Chrome…", "Graphic Pen…", "Halftone Pattern…", "Plaster…", "Reticulation…"]);
assert_eq!(
labels,
[
"Bas Relief…",
"Chalk & Charcoal…",
"Charcoal…",
"Chrome…",
"Graphic Pen…",
"Halftone Pattern…",
"Photocopy…",
"Plaster…",
"Reticulation…",
"Stamp…"
]
);
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.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.plaster"}));
assert_eq!(sketch[5].params, json!({"effect": "sketch.reticulation"}));
assert_eq!(sketch[0].params, json!({"effect": "sketch.basRelief"}));
assert_eq!(sketch[1].params, json!({"effect": "sketch.chalkCharcoal"}));
assert_eq!(sketch[2].params, json!({"effect": "sketch.charcoal"}));
assert_eq!(sketch[3].params, json!({"effect": "sketch.chrome"}));
assert_eq!(sketch[4].params, json!({"effect": "sketch.graphicPen"}));
assert_eq!(sketch[5].params, json!({"effect": "sketch.halftonePattern"}));
assert_eq!(sketch[6].params, json!({"effect": "sketch.photocopy"}));
assert_eq!(sketch[7].params, json!({"effect": "sketch.plaster"}));
assert_eq!(sketch[8].params, json!({"effect": "sketch.reticulation"}));
assert_eq!(sketch[9].params, json!({"effect": "sketch.stamp"}));
let at = |label: &str| entries.iter().position(|e| e.path[0] == "Effect" && e.label == label).unwrap();
assert!(at("Unsharp Mask…") < at("Chalk & Charcoal…") && at("Chalk & Charcoal…") < at("Chrome…"));
assert!(at("Chrome…") < at("Graphic Pen…") && at("Graphic Pen…") < at("Halftone Pattern…"));
+21 -4
View File
@@ -1,6 +1,6 @@
# Effects parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Photoshop filters 42/57, 15 remaining (14–24 h); G2: Sketch › Chrome; Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 37/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 42/57, 15 remaining (14–24 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); G2: Sketch › Chrome; Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 37/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x)
Every entry of Illustrator's Effect menu, by its public feature name, against VectorCraft's effects catalogue
(`effect_catalog()` in `crates/effects/src/lib.rs`) and the Effect menu built from it
@@ -12,7 +12,7 @@ Part of [target-app-parity.md](target-app-parity.md); open work is in [gaps.md](
| Group | Illustrator | VectorCraft | % | Remaining (h) |
|---|---:|---:|---:|---:|
| Illustrator effects (vector and stylize) | 50 | 46 | 92% | 6–10 |
| Photoshop effects (raster, Effect Gallery) | 57 | 42 | 74% | 14–24 |
| Photoshop effects (raster, Effect Gallery) | 57 | 45 | 79% | 8–14 |
| 3D and Materials | 4 (+3 Classic) | 4 (initial: Extrude & Bevel, Revolve, Inflate, Rotate) | ~25% | 25–45 |
| Beyond Illustrator: Effect › Color Adjustments (6), effect plug-ins (WebAssembly) | — | 6 + plug-ins | — | — |
@@ -44,7 +44,7 @@ its resolution, PDF export warns once per object with the requested and the lowe
| Distort | Diffuse Glow, Glass, Ocean Ripple | 3/3 | — |
| Pixelate | Color Halftone, Crystallize, Mezzotint, Pointillize | 4/4 | — |
| Sharpen | Unsharp Mask | 1/1 | — |
| Sketch | Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Conté Crayon, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Torn Edges, Water Paper | 6/14 (approximate Chalk & Charcoal, Chrome, Graphic Pen, Halftone Pattern, Plaster, Reticulation; Chalk & Charcoal and Graphic Pen defaults unverified) | Bas Relief, Charcoal, Conté Crayon, Note Paper, Photocopy, 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 | 10/14 (approximate Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Graphic Pen, Halftone Pattern, Photocopy, Plaster, Reticulation, Stamp; Chalk & Charcoal and Graphic Pen defaults unverified) | Conté Crayon, Note Paper, Torn Edges, Water Paper |
| 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 | — |
@@ -52,7 +52,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 15 missing filters are ~10–18 h and the Effect
(see [calibration](target-app-parity.md#calibration)), so the 12 missing filters are ~8–14 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
@@ -133,6 +133,22 @@ image in SVG and PDF. Ranges and defaults are chosen for VectorCraft, not assert
Tests: `pixel::tests_paint::*`, `tests_rasterfilters`, `dialogs::tests::raster_filter_dialogs_*`,
`tests_labels::effect_menu_lists_the_artistic_filters_*` and the `command_sweep` structured junk.
Bas Relief, Charcoal, Photocopy and Stamp (`sketch.basRelief`, `sketch.charcoal`, `sketch.photocopy`,
`sketch.stamp`, Effect › Sketch; `pixel/sketch_ink.rs`) redraw the object in black ink on white paper
and keep its coverage. Bas Relief carves low relief lit from one of eight directions (Detail, Smoothness,
Light Direction); Charcoal draws bold edges and sketches the midtones in diagonal strokes (Thickness,
Detail, Light/Dark Balance); Photocopy prints only where a pixel is darker than its surroundings, so large
dark areas copy around their edges (Detail, Darkness); Stamp reduces the object to solid black and
white (Light/Dark Balance, Smoothness). Sizes are in document points at any Document Raster Effects
resolution, strokes and noise lie in document space around the object's centre, and all four preview
live. Behaviour is an original visual approximation based on
[Illustrator's public effects reference](https://helpx.adobe.com/illustrator/using/summary-effects.html)
and the [public Sketch filters guide](https://helpx.adobe.com/photoshop-elements/using/sketch-filters.html),
not a pixel-identical proprietary implementation; the parameter ranges and defaults are chosen for
VectorCraft, not asserted as measured proprietary defaults. Tests: `pixel::tests_sketch::*`,
`tests_rasterfilters`, `command_sweep`, `dialogs::tests::raster_filter_dialogs_preview_and_apply` and
`tests_labels::effect_menu_lists_the_sketch_filters_*`.
## 3D and Materials
| Item | Ours |
@@ -155,6 +171,7 @@ angular cut caps, exact visibility, 3D Classic, Turntable, 3D export) is 25–45
| 2026-10-19 | minor | Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap landed (#2972, #2967, #2976, #3034): 36/57 Photoshop effects (estimated; defaults unverified) |
| 2026-10-16 | minor | Zig Zag with Smooth points draws a continuous wave: every peak and valley is a smooth point with handles along the path, a third of the way to the next ridge, so each span follows a half cosine through the path instead of a straight diagonal with rounded tips, also at the ends of open paths and for ridges much taller than they are apart (#2343); score unchanged (estimated) |
| 2026-10-16 | minor | G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor (live, with their documented options; original approximations); measured Photoshop filters 36/57 with Cutout, Neon Glow and Poster Edges, 21 remaining (19–31 h). |
| 2026-10-16 | minor | G2: Effect › Sketch › Bas Relief, Charcoal, Photocopy and Stamp (black ink on white paper, sizes in points at any Document Raster Effects resolution, live preview); measured Photoshop filters 37/57 with Cutout, Neon Glow and Poster Edges, 20 remaining (18–30 h). |
| 2026-10-16 | minor | G2: Effect › Sketch › Halftone Pattern (Size, Contrast, Pattern Type Circle, Dot or Line; black ink on white paper, Size in points at any Document Raster Effects resolution); measured Photoshop filters 30/57, 27 remaining (22–38 h). |
| 2026-10-17 | minor | Colored Inflate: shared membrane normals and smooth RGB vector ramps replace flat polygon bands; channel groups are batched per solid-fill surface. White-light Inflate retains its original polygons, paint composition and seam joins; the 0.05 seam minimum is restored. Scores and hour estimates unchanged. |
| 2026-10-16 | minor | 3D lighting: one to ten directional lights, a selectable light list, Add Light and Delete Light, independent settings and artboard gestures; ambient applied once and smooth RGB vector export; scores and hour estimates unchanged |
+7 -6
View File
File diff suppressed because one or more lines are too long
+4 -3
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long