mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Effects: Effect › Sketch › Halftone Pattern redraws an object through a halftone screen of dots, rings or lines
This commit is contained in:
committed by
bflatastic
parent
ddfd08f321
commit
403b335039
@@ -245,7 +245,7 @@ The scores are self-assessed from our code against Illustrator's public document
|
||||
- reliable exchange of Illustrator's own `.ai` files (layer structure in real files; Illustrator's 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 Photoshop-style raster effects (28 of 57 still missing; Film Grain is available under Artistic) and the Effect Gallery;
|
||||
- about half of the Photoshop-style raster effects (27 of 57 still missing; Film Grain is available under Artistic, Halftone Pattern 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.
|
||||
|
||||
+1
-1
@@ -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 44/50, Photoshop effects 29/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 44/50, Photoshop effects 30/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 |
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! - **Geometry effects** (Distort & Transform, Path, Convert to Shape, Round Corners, Scribble,
|
||||
//! Warp) rewrite a path: [`apply_geometry`] evaluates them in stack order.
|
||||
//! - **Raster effects** (Drop Shadow, Inner/Outer Glow, Feather, Gaussian Blur, and the
|
||||
//! Photoshop-style filters of [`pixel`]: Radial Blur, Smart Blur, Color Halftone, Crystallize, Mezzotint, Pointillize,
|
||||
//! Photoshop-style filters of [`pixel`]: Radial Blur, Smart Blur, Color Halftone, Crystallize, Mezzotint, Pointillize, Halftone Pattern,
|
||||
//! Unsharp Mask and Glowing Edges) are described by
|
||||
//! [`raster_effects`] and painted by the renderer; [`outset`] says how far they reach beyond
|
||||
//! the geometry.
|
||||
@@ -149,6 +149,7 @@ fn lengths_of(id: &str) -> Lengths {
|
||||
"texture.mosaicTiles" => always(&["tileSize"]),
|
||||
"texture.patchwork" => always(&["squareSize"]),
|
||||
"texture.stainedGlass" => always(&["cellSize"]),
|
||||
"sketch.halftonePattern" => always(&["size"]),
|
||||
_ => Lengths::default(),
|
||||
}
|
||||
}
|
||||
@@ -165,6 +166,7 @@ const ARTISTIC: &[&str] = &["Effect", "Artistic"];
|
||||
const BRUSH_STROKES: &[&str] = &["Effect", "Brush Strokes"];
|
||||
const DISTORT: &[&str] = &["Effect", "Distort"];
|
||||
const SHARPEN: &[&str] = &["Effect", "Sharpen"];
|
||||
const SKETCH: &[&str] = &["Effect", "Sketch"];
|
||||
const PIXELATE: &[&str] = &["Effect", "Pixelate"];
|
||||
const TEXTURE: &[&str] = &["Effect", "Texture"];
|
||||
const VIDEO: &[&str] = &["Effect", "Video"];
|
||||
@@ -432,6 +434,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.halftonePattern",
|
||||
"Halftone Pattern…",
|
||||
SKETCH,
|
||||
"{size: pt 1..12 (1; how far apart the screen's dots, rings or lines are), contrast: 0..50 (5; 50 draws pure black and white), patternType: \"circle\"|\"dot\"|\"line\" (\"dot\"; rings round the object's centre, dots on a square grid, horizontal lines)} redraws the object in black ink on white paper through a halftone screen whose ink covers as much as the colour is dark, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.",
|
||||
json!({"size": 1.0, "contrast": 5.0, "patternType": "dot"}),
|
||||
),
|
||||
r(
|
||||
"stylize.glowingEdges",
|
||||
"Glowing Edges…",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Photoshop-style raster effects (Effect › Artistic › Film Grain, Blur › Radial Blur and Smart Blur, Brush Strokes ›
|
||||
//! Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes
|
||||
//! and Sumi-e, Distort › Diffuse Glow, Glass and Ocean Ripple, Pixelate › Color Halftone,
|
||||
//! Crystallize, Mezzotint and Pointillize, Sharpen › Unsharp Mask, Stylize › Glowing Edges, Texture
|
||||
//! Crystallize, Mezzotint and Pointillize, Sharpen › Unsharp Mask, Sketch › Halftone Pattern, Stylize ›
|
||||
//! Glowing Edges, Texture
|
||||
//! › Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass and Texturizer, and Video ›
|
||||
//! De-Interlace and NTSC Colors): filters over premultiplied RGBA8 pixels.
|
||||
//!
|
||||
@@ -20,6 +21,7 @@ mod edges;
|
||||
mod loaded;
|
||||
mod pixelate;
|
||||
mod sharpen;
|
||||
mod sketch;
|
||||
mod texture;
|
||||
mod video;
|
||||
|
||||
@@ -33,10 +35,11 @@ pub use brushstrokes::{STROKE_DIRECTIONS, StrokeDirection};
|
||||
pub use distort::{GLASS_TEXTURES, GLASS_TEXTURES_LOADED, GlassTexture};
|
||||
pub use loaded::{LOADED, LoadedTexture, TEXTURE_IMAGE, texture};
|
||||
pub use pixelate::{MEZZOTINT_TYPES, Mezzotint};
|
||||
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; 28] = [
|
||||
pub const PIXEL_EFFECTS: [&str; 29] = [
|
||||
"artistic.filmGrain",
|
||||
"blur.radial",
|
||||
"blur.smart",
|
||||
@@ -56,6 +59,7 @@ pub const PIXEL_EFFECTS: [&str; 28] = [
|
||||
"pixelate.mezzotint",
|
||||
"pixelate.pointillize",
|
||||
"sharpen.unsharpMask",
|
||||
"sketch.halftonePattern",
|
||||
"stylize.glowingEdges",
|
||||
"texture.craquelure",
|
||||
"texture.grain",
|
||||
@@ -123,6 +127,9 @@ pub enum PixelFx {
|
||||
/// Sharpen › Unsharp Mask: colours pushed away from a Gaussian blur of σ = `radius` by `amount`
|
||||
/// (1 = 100 %), where they differ from it by at least `threshold` levels.
|
||||
UnsharpMask { amount: f64, radius: f64, threshold: f64 },
|
||||
/// 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 },
|
||||
/// Stylize › Glowing Edges: bright coloured Sobel outlines on black.
|
||||
GlowingEdges { width: f64, brightness: f64, smoothness: f64 },
|
||||
/// Pixelate › Color Halftone: each colour channel screened at its angle (`angles`° of channels
|
||||
@@ -277,6 +284,11 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option<PixelFx> {
|
||||
radius: num(p, "radius", 1.0).clamp(0.1, 250.0),
|
||||
threshold: num(p, "threshold", 0.0).clamp(0.0, 255.0),
|
||||
},
|
||||
"sketch.halftonePattern" => PixelFx::HalftonePattern {
|
||||
size: num(p, "size", 1.0).clamp(1.0, 12.0),
|
||||
contrast: num(p, "contrast", 5.0).clamp(0.0, 50.0),
|
||||
kind: HalftonePattern::parse(text(p, "patternType", "dot")),
|
||||
},
|
||||
"stylize.glowingEdges" => PixelFx::GlowingEdges {
|
||||
width: num(p, "edgeWidth", 2.0).clamp(1.0, 14.0),
|
||||
brightness: num(p, "edgeBrightness", 6.0).clamp(0.0, 20.0),
|
||||
@@ -353,7 +365,7 @@ 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 { .. } => 0.0,
|
||||
PixelFx::UnsharpMask { .. } | PixelFx::HalftonePattern { .. } => 0.0,
|
||||
PixelFx::GlowingEdges { width, smoothness, .. } => width.max(smoothness * 3.0),
|
||||
PixelFx::FilmGrain { .. }
|
||||
| PixelFx::ColorHalftone { .. }
|
||||
@@ -396,7 +408,7 @@ impl PixelFx {
|
||||
// The cells around the pixel's, and the cells around theirs.
|
||||
PixelFx::ColorHalftone { max_radius, .. } => Some(3.0 * std::f64::consts::SQRT_2 * max_radius),
|
||||
PixelFx::Crystallize { cell } => Some(1.5 * cell),
|
||||
PixelFx::FilmGrain { .. } | PixelFx::Mezzotint { .. } => Some(0.0),
|
||||
PixelFx::FilmGrain { .. } | PixelFx::Mezzotint { .. } | PixelFx::HalftonePattern { .. } => Some(0.0),
|
||||
// The dots' points, and the softened colour around them.
|
||||
PixelFx::Pointillize { cell } => Some(2.5 * cell),
|
||||
// The softened tones around the pixel.
|
||||
@@ -439,6 +451,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::HalftonePattern { size, contrast, kind } => sketch::halftone_pattern(px, w, h, space, size, contrast, kind),
|
||||
PixelFx::GlowingEdges { width, brightness, smoothness } => edges::glow(px, w, h, to_px(width), brightness, to_px(smoothness)),
|
||||
PixelFx::ColorHalftone { max_radius, angles } => pixelate::color_halftone(px, w, h, space, max_radius, angles),
|
||||
PixelFx::Crystallize { cell } => pixelate::crystallize(px, w, h, space, cell),
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
//! Effect › Sketch › Halftone Pattern. Original filter built from public behaviour documentation:
|
||||
//! <https://helpx.adobe.com/illustrator/using/summary-effects.html> (Sketch effects redraw in
|
||||
//! black and white; Halftone Pattern simulates a halftone screen while keeping the range of tones)
|
||||
//! and <https://helpx.adobe.com/illustrator/using/effects.html> (only Size depends on the raster
|
||||
//! resolution). A visual approximation, not a reproduction of a proprietary algorithm.
|
||||
|
||||
use super::pixelate::Place;
|
||||
use super::texture::{luma, premultiply, straight};
|
||||
use super::{PixelSpace, pick};
|
||||
|
||||
/// Halftone Pattern's screens, in its Pattern Type menu's order: (label, parameter value).
|
||||
pub const HALFTONE_TYPES: [(&str, &str); 3] = [("Circle", "circle"), ("Dot", "dot"), ("Line", "line")];
|
||||
|
||||
/// A Halftone Pattern screen ([`HALFTONE_TYPES`]).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum HalftonePattern {
|
||||
/// Rings round the object's centre.
|
||||
Circle,
|
||||
/// Dots on a square grid.
|
||||
Dot,
|
||||
/// Horizontal lines.
|
||||
Line,
|
||||
}
|
||||
|
||||
impl HalftonePattern {
|
||||
const ALL: [HalftonePattern; 3] = [HalftonePattern::Circle, HalftonePattern::Dot, HalftonePattern::Line];
|
||||
|
||||
/// The screen of Pattern Type `value` (a [`HALFTONE_TYPES`] value, any case); Dot otherwise.
|
||||
pub fn parse(value: &str) -> Self {
|
||||
pick(&HALFTONE_TYPES, &Self::ALL, value, HalftonePattern::Dot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Halftone Pattern: the object redrawn in black ink on white paper through a `kind` screen whose
|
||||
/// cells are `size` points apart, anchored at the object's centre. The ink covers as much of a
|
||||
/// cell as the colour is dark; `contrast` (0..50) hardens the screen's soft edges, to pure black
|
||||
/// and white at 50. Coverage is kept.
|
||||
pub(super) fn halftone_pattern(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, size: f64, contrast: f64, kind: HalftonePattern) {
|
||||
if !(size.is_finite() && size > 0.0 && contrast.is_finite()) {
|
||||
return;
|
||||
}
|
||||
let Some(place) = Place::new(space, w, h) else { return };
|
||||
// The width of the screen's soft edge, in tones: 0 is a hard threshold.
|
||||
let soft = (0.5 * (1.0 - contrast / 50.0)).clamp(0.0, 0.5);
|
||||
place.each(px, |x, y, p| {
|
||||
if p[3] == 0 {
|
||||
return;
|
||||
}
|
||||
let q = place.at(x, y);
|
||||
let band = |v: f64| (2.0 * ((v / size).rem_euclid(1.0) - 0.5)).abs();
|
||||
// Where in its cell the pixel lies, 0 where the ink starts to 1 where it ends.
|
||||
let screen = match kind {
|
||||
HalftonePattern::Line => band(q.y),
|
||||
HalftonePattern::Circle => band(q.x.hypot(q.y)),
|
||||
HalftonePattern::Dot => 0.5 * (band(q.x).powi(2) + band(q.y).powi(2)),
|
||||
};
|
||||
let dark = f64::from(1.0 - luma(straight(*p))).clamp(0.0, 1.0);
|
||||
let ink = if dark >= 1.0 {
|
||||
1.0
|
||||
} else if soft > 0.0 {
|
||||
// The threshold kept inside [soft / 2, 1 - soft / 2], so white stays white and black black.
|
||||
let threshold = 0.5 * soft + screen * (1.0 - soft);
|
||||
((dark - threshold) / soft + 0.5).clamp(0.0, 1.0)
|
||||
} else if dark > screen {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
*p = premultiply([(1.0 - ink) as f32; 3], f32::from(p[3]) / 255.0);
|
||||
});
|
||||
}
|
||||
@@ -1893,3 +1893,119 @@ fn brush_strokes_follow_the_object_at_any_resolution() {
|
||||
assert_eq!(shifted, one, "{id}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Halftone Pattern of `p` over `w` × `h` pixels of `src`, laid out by `s`.
|
||||
fn halftone(p: serde_json::Value, src: &[u8], w: usize, h: usize, s: &PixelSpace) -> Vec<u8> {
|
||||
let mut out = src.to_vec();
|
||||
fx("sketch.halftonePattern", p).apply(&mut out, w, h, s);
|
||||
out
|
||||
}
|
||||
|
||||
/// The smallest period of `values` (their length when they don't repeat).
|
||||
fn period(values: &[u8]) -> usize {
|
||||
(1..values.len()).find(|p| values.iter().zip(&values[*p..]).all(|(a, b)| a == b)).unwrap_or(values.len())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn halftone_pattern_params_take_defaults_and_stay_in_range() {
|
||||
assert_eq!(fx("sketch.halftonePattern", json!({})), PixelFx::HalftonePattern { size: 1.0, contrast: 5.0, kind: HalftonePattern::Dot });
|
||||
assert_eq!(
|
||||
fx("sketch.halftonePattern", json!({"size": 1e9, "contrast": -3, "patternType": "LINE"})),
|
||||
PixelFx::HalftonePattern { size: 12.0, contrast: 0.0, kind: HalftonePattern::Line }
|
||||
);
|
||||
assert_eq!(
|
||||
fx("sketch.halftonePattern", json!({"size": 0, "contrast": 1e9, "patternType": "circle"})),
|
||||
PixelFx::HalftonePattern { size: 1.0, contrast: 50.0, kind: HalftonePattern::Circle }
|
||||
);
|
||||
for junk in [json!({"patternType": 2}), json!({"patternType": "nonsense"})] {
|
||||
assert_eq!(fx("sketch.halftonePattern", junk), fx("sketch.halftonePattern", json!({})));
|
||||
}
|
||||
let doc = crate::effect_info("sketch.halftonePattern").unwrap().params;
|
||||
let kinds: Vec<HalftonePattern> = HALFTONE_TYPES.iter().map(|(_, v)| HalftonePattern::parse(v)).collect();
|
||||
assert_eq!(kinds, [HalftonePattern::Circle, HalftonePattern::Dot, HalftonePattern::Line]);
|
||||
assert!(HALFTONE_TYPES.iter().all(|(_, v)| doc.contains(&format!("\"{v}\""))), "{doc}");
|
||||
let f = fx("sketch.halftonePattern", json!({"size": 12}));
|
||||
assert_eq!(f.outset(Rect::new(0.0, 0.0, 100.0, 50.0)), 0.0);
|
||||
assert_eq!(f.reach(), Some(0.0));
|
||||
}
|
||||
|
||||
/// Line's rows repeat every Size points: Size pixels at 72 ppi, twice as many at 144 ppi.
|
||||
#[test]
|
||||
fn halftone_line_period_is_the_size_at_any_resolution() {
|
||||
let p = json!({"size": 4, "contrast": 50, "patternType": "line"});
|
||||
let (w, h) = (48, 48);
|
||||
let out = halftone(p.clone(), &image(w, h, |_, _| grey(128)), w, h, &space(w, h));
|
||||
let column: Vec<u8> = (0..h).map(|y| at(&out, w, 0, y)[0]).collect();
|
||||
assert!(column.contains(&0) && column.contains(&255), "{column:?}");
|
||||
assert_eq!(period(&column), 4, "{column:?}");
|
||||
let fine = PixelSpace { to_doc: Affine::scale(0.5), px: 0.5, ..space(w, h) };
|
||||
let out = halftone(p, &image(2 * w, 2 * h, |_, _| grey(128)), 2 * w, 2 * h, &fine);
|
||||
let column: Vec<u8> = (0..2 * h).map(|y| at(&out, 2 * w, 0, y)[0]).collect();
|
||||
assert_eq!(period(&column), 8, "{column:?}");
|
||||
}
|
||||
|
||||
/// Circle draws rings round the object's centre, Line rows that don't change along them, and
|
||||
/// Dot neither.
|
||||
#[test]
|
||||
fn halftone_pattern_type_changes_the_pattern() {
|
||||
let (w, h) = (64, 64);
|
||||
let src = image(w, h, |_, _| grey(100));
|
||||
// The centre on a pixel's centre: pixel (32 + dx, 32 + dy) lies at (dx, dy) from it.
|
||||
let s = PixelSpace { center: Point::new(32.5, 32.5), ..space(w, h) };
|
||||
let draw = |kind: &str| halftone(json!({"size": 8, "contrast": 50, "patternType": kind}), &src, w, h, &s);
|
||||
let (circle, dot, line) = (draw("circle"), draw("dot"), draw("line"));
|
||||
assert!(circle != dot && dot != line && line != circle);
|
||||
// Pixels at the same distance from the centre in different directions.
|
||||
let same_radius: [&[(i64, i64)]; 6] = [
|
||||
&[(5, 0), (3, 4), (-4, 3), (0, -5), (-3, -4)],
|
||||
&[(10, 0), (6, 8), (-8, -6), (0, 10)],
|
||||
&[(13, 0), (5, 12), (-12, 5), (0, -13)],
|
||||
&[(15, 0), (9, 12), (-12, 9)],
|
||||
&[(17, 0), (8, 15), (-15, -8)],
|
||||
&[(25, 0), (7, 24), (-15, 20), (-20, -15), (24, -7)],
|
||||
];
|
||||
let value = |d: &[u8], (dx, dy): (i64, i64)| at(d, w, (32 + dx) as usize, (32 + dy) as usize)[0];
|
||||
let radial = |d: &[u8]| same_radius.iter().all(|ring| ring.iter().all(|q| value(d, *q) == value(d, ring[0])));
|
||||
assert!(radial(&circle), "Circle is the same all round");
|
||||
assert!(!radial(&dot) && !radial(&line));
|
||||
for y in 0..h {
|
||||
assert!((0..w).all(|x| at(&line, w, x, y) == at(&line, w, 0, y)), "row {y} of Line is uniform");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn halftone_contrast_sharpens_the_screen() {
|
||||
let (w, h) = (64, 32);
|
||||
let src = image(w, h, |x, _| grey((x * 4) as u8));
|
||||
let mid = |d: &[u8]| share(d, |p| p[0] != 0 && p[0] != 255);
|
||||
let soft = halftone(json!({"size": 6, "contrast": 0}), &src, w, h, &space(w, h));
|
||||
let hard = halftone(json!({"size": 6, "contrast": 50}), &src, w, h, &space(w, h));
|
||||
assert!(mid(&soft) > 0.3, "{}", mid(&soft));
|
||||
assert_eq!(mid(&hard), 0.0);
|
||||
}
|
||||
|
||||
/// Halftone Pattern redraws in black ink on white paper, whatever the object's colours, and
|
||||
/// keeps its coverage.
|
||||
#[test]
|
||||
fn halftone_draws_in_black_and_white_only() {
|
||||
let (w, h) = (48, 48);
|
||||
let src = image(w, h, |x, y| {
|
||||
let c = [[230u8, 20, 20], [20, 200, 40], [30, 60, 220], [240, 220, 30]][(x / 12) % 4];
|
||||
let a: u16 = if y < 24 { 255 } else { 128 };
|
||||
[c[0], c[1], c[2]].map(|v| (u16::from(v) * a / 255) as u8).into_iter().chain([a as u8]).collect::<Vec<_>>().try_into().unwrap()
|
||||
});
|
||||
for kind in ["circle", "dot", "line"] {
|
||||
let out = halftone(json!({"size": 6, "contrast": 50, "patternType": kind}), &src, w, h, &space(w, h));
|
||||
for (p, q) in out.as_chunks::<4>().0.iter().zip(src.as_chunks::<4>().0) {
|
||||
assert_eq!(p[3], q[3], "{kind}: coverage kept");
|
||||
assert!(p[0] == p[1] && p[1] == p[2] && (p[0] == 0 || p[0] == p[3]), "{kind}: {p:?} from {q:?}");
|
||||
}
|
||||
assert!(share(&out, |p| p[0] == 0) > 0.1 && share(&out, |p| p[0] == p[3]) > 0.1, "{kind}: ink and paper");
|
||||
let soft = halftone(json!({"size": 6, "contrast": 0, "patternType": kind}), &src, w, h, &space(w, h));
|
||||
assert!(soft.as_chunks::<4>().0.iter().all(|p| p[0] == p[1] && p[1] == p[2]), "{kind}: greys only");
|
||||
for (tone, expect) in [(0, 0), (255, 255)] {
|
||||
let out = halftone(json!({"size": 6, "contrast": 0, "patternType": kind}), &image(w, h, |_, _| grey(tone)), w, h, &space(w, h));
|
||||
assert!(out.as_chunks::<4>().0.iter().all(|p| p[0] == expect), "{kind}: {tone} stays {expect}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The Photoshop-style raster effects (Effect › 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, Texture ›
|
||||
//! Distort › Diffuse Glow, Glass and Ocean Ripple, Pixelate › Color Halftone, Crystallize, Mezzotint and Pointillize, Sharpen › Unsharp Mask, Sketch › Halftone Pattern, 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.
|
||||
@@ -42,6 +42,9 @@ const DISTORT: [(&str, &str); 3] =
|
||||
const PIXELATE: [(&str, &str); 4] =
|
||||
[("pixelate.colorHalftone", "{}"), ("pixelate.crystallize", "{}"), ("pixelate.mezzotint", "{}"), ("pixelate.pointillize", "{}")];
|
||||
|
||||
/// Effect › Sketch, at their defaults.
|
||||
const SKETCH: [(&str, &str); 1] = [("sketch.halftonePattern", "{}")];
|
||||
|
||||
/// Effect › Texture, at their defaults.
|
||||
const TEXTURE: [(&str, &str); 6] = [
|
||||
("texture.craquelure", "{}"),
|
||||
@@ -97,6 +100,7 @@ fn listed_applied_edited_and_undone() {
|
||||
(BRUSH_STROKES.as_slice(), "Brush Strokes"),
|
||||
(DISTORT.as_slice(), "Distort"),
|
||||
(PIXELATE.as_slice(), "Pixelate"),
|
||||
(SKETCH.as_slice(), "Sketch"),
|
||||
(TEXTURE.as_slice(), "Texture"),
|
||||
(VIDEO.as_slice(), "Video"),
|
||||
([("video.ntscColors", "{}")].as_slice(), "Video"),
|
||||
@@ -134,7 +138,9 @@ fn listed_applied_edited_and_undone() {
|
||||
|
||||
#[test]
|
||||
fn each_effect_changes_the_canvas_and_draws_the_same_twice() {
|
||||
for (id, params) in EFFECTS.into_iter().chain(ARTISTIC).chain(BRUSH_STROKES).chain(DISTORT).chain(PIXELATE).chain(TEXTURE).chain(VIDEO) {
|
||||
for (id, params) in
|
||||
EFFECTS.into_iter().chain(ARTISTIC).chain(BRUSH_STROKES).chain(DISTORT).chain(PIXELATE).chain(SKETCH).chain(TEXTURE).chain(VIDEO)
|
||||
{
|
||||
let (mut s, g) = striped();
|
||||
let plain = render(&s);
|
||||
let params: Value = serde_json::from_str(params).unwrap();
|
||||
@@ -150,6 +156,25 @@ fn each_effect_changes_the_canvas_and_draws_the_same_twice() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Halftone Pattern's Size, Contrast and Pattern Type are saved with the document and draw the same
|
||||
/// when it opens again.
|
||||
#[test]
|
||||
fn halftone_pattern_settings_survive_a_save_and_open() {
|
||||
let (mut s, g) = striped();
|
||||
let set = json!({"size": 7.0, "contrast": 20.0, "patternType": "line"});
|
||||
s.execute("effect.apply", &json!({"effect": "sketch.halftonePattern", "ids": [g], "params": set})).unwrap();
|
||||
let drawn = render(&s);
|
||||
let doc = vectorcraft_format::load(&vectorcraft_format::save(&s.doc().unwrap().doc, false)).unwrap();
|
||||
assert_eq!(doc.node(NodeId(g)).unwrap().appearance.effects[0].params, set);
|
||||
assert_eq!(Renderer::new().render_region(&doc, Rect::new(0.0, 0.0, 200.0, 200.0), 1.0, true).pixels, drawn.pixels);
|
||||
// Each setting shows: changing any one draws differently.
|
||||
for change in [json!({"size": 3.0}), json!({"contrast": 50.0}), json!({"patternType": "dot"})] {
|
||||
s.execute("effect.setParams", &json!({"index": 0, "ids": [g], "params": change})).unwrap();
|
||||
assert!(mean_diff(&drawn, &render(&s)) > 0.001, "{change}");
|
||||
s.execute("effect.setParams", &json!({"index": 0, "ids": [g], "params": set})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// A PNG of rings around its center, 12 × 10 pixels.
|
||||
fn rings_png() -> Vec<u8> {
|
||||
let img = image::RgbaImage::from_fn(12, 10, |x, y| {
|
||||
@@ -251,7 +276,9 @@ fn spin_blur_reaches_past_the_corners() {
|
||||
|
||||
#[test]
|
||||
fn pdf_and_svg_write_the_effected_object_as_an_image() {
|
||||
for (id, params) in EFFECTS.into_iter().chain(ARTISTIC).chain(BRUSH_STROKES).chain(DISTORT).chain(PIXELATE).chain(TEXTURE).chain(VIDEO) {
|
||||
for (id, params) in
|
||||
EFFECTS.into_iter().chain(ARTISTIC).chain(BRUSH_STROKES).chain(DISTORT).chain(PIXELATE).chain(SKETCH).chain(TEXTURE).chain(VIDEO)
|
||||
{
|
||||
let (mut s, g) = striped();
|
||||
let params: Value = serde_json::from_str(params).unwrap();
|
||||
s.execute("effect.apply", &json!({"effect": id, "ids": [g], "params": params})).unwrap();
|
||||
|
||||
@@ -322,6 +322,7 @@ fn structured_junk() {
|
||||
("effect.apply", json!({"effect": "pixelate.crystallize", "params": {"cellSize": -1e308}})),
|
||||
("effect.apply", json!({"effect": "pixelate.mezzotint", "params": {"type": 5}})),
|
||||
("effect.apply", json!({"effect": "pixelate.pointillize", "params": {"cellSize": "NaN"}})),
|
||||
("effect.apply", json!({"effect": "sketch.halftonePattern", "params": {"size": -1e308, "contrast": "NaN", "patternType": 3}})),
|
||||
("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}})),
|
||||
("effect.apply", json!({"effect": "texture.mosaicTiles", "params": {"tileSize": "1e999", "groutWidth": -1, "lightenGrout": null}})),
|
||||
|
||||
@@ -266,6 +266,7 @@ fn choices(effect: &str, key: &str) -> Option<form::Choices> {
|
||||
("brushStrokes.sprayedStrokes", "strokeDirection") => Some(&vectorcraft_effects::pixel::STROKE_DIRECTIONS),
|
||||
("distort.glass", "texture") => Some(&vectorcraft_effects::pixel::GLASS_TEXTURES),
|
||||
("pixelate.mezzotint", "type") => Some(&vectorcraft_effects::pixel::MEZZOTINT_TYPES),
|
||||
("sketch.halftonePattern", "patternType") => Some(&vectorcraft_effects::pixel::HALFTONE_TYPES),
|
||||
("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),
|
||||
|
||||
@@ -185,6 +185,7 @@ fn raster_filter_dialogs_preview_and_apply() {
|
||||
("pixelate.crystallize", "cellSize", "20"),
|
||||
("pixelate.mezzotint", "type", "longLines"),
|
||||
("pixelate.pointillize", "cellSize", "12"),
|
||||
("sketch.halftonePattern", "patternType", "line"),
|
||||
("texture.craquelure", "crackDepth", "9"),
|
||||
("texture.grain", "grainType", "stippled"),
|
||||
("texture.mosaicTiles", "tileSize", "20"),
|
||||
|
||||
@@ -4925,7 +4925,11 @@ theme Appearance Външен вид
|
||||
Amber Кехлибарен
|
||||
Coral Коралов
|
||||
Violet Виолетов
|
||||
|
||||
Sketch Скица
|
||||
Halftone Pattern… Полутонов шаблон…
|
||||
Halftone Pattern Полутонов шаблон
|
||||
Pattern Type Тип шаблон
|
||||
Dot Точка
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Цветова схема
|
||||
Modern Модерна
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4943,7 +4943,11 @@ mark-style Japanese Japonské
|
||||
Amber Jantarová
|
||||
Coral Korálová
|
||||
Violet Fialová
|
||||
|
||||
Sketch Skica
|
||||
Halftone Pattern… Polotónový vzorek…
|
||||
Halftone Pattern Polotónový vzorek
|
||||
Pattern Type Typ vzorku
|
||||
Dot Tečka
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Barevné schéma
|
||||
Modern Moderní
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4480,7 +4480,11 @@ theme Appearance Erscheinungsbild
|
||||
Coral Koralle
|
||||
Violet Violett
|
||||
Pink Pink
|
||||
|
||||
Sketch Zeichenfilter
|
||||
Halftone Pattern… Rasterungseffekt…
|
||||
Halftone Pattern Rasterungseffekt
|
||||
Pattern Type Musterart
|
||||
Dot Punkt
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Farbschema
|
||||
Modern Modern
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4895,7 +4895,11 @@ effect Points Σημεία
|
||||
Amber Κεχριμπαρί
|
||||
Coral Κοραλλί
|
||||
Violet Βιολετί
|
||||
|
||||
Sketch Σκίτσο
|
||||
Halftone Pattern… Μοτίβο ημιτόνου…
|
||||
Halftone Pattern Μοτίβο ημιτόνου
|
||||
Pattern Type Τύπος μοτίβου
|
||||
Dot Κουκκίδα
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Χρωματικός συνδυασμός
|
||||
Modern Σύγχρονο
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 1859 and column 19.
|
@@ -4885,7 +4885,11 @@ mark-style Japanese Japonesas
|
||||
Amber Ámbar
|
||||
Coral Coral
|
||||
Violet Violeta
|
||||
|
||||
Sketch Bosquejar
|
||||
Halftone Pattern… Trama de semitonos…
|
||||
Halftone Pattern Trama de semitonos
|
||||
Pattern Type Tipo de trama
|
||||
Dot Punto
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Esquema de color
|
||||
Modern Moderno
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4907,7 +4907,11 @@ mark-style Japanese Japanilainen
|
||||
Amber Meripihka
|
||||
Coral Koralli
|
||||
Violet Violetti
|
||||
|
||||
Sketch Luonnos
|
||||
Halftone Pattern… Rasterikuvio…
|
||||
Halftone Pattern Rasterikuvio
|
||||
Pattern Type Kuviotyyppi
|
||||
Dot Piste
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Värimalli
|
||||
Modern Moderni
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4942,7 +4942,11 @@ theme Appearance Apparence
|
||||
Amber Ambre
|
||||
Coral Corail
|
||||
Violet Violet
|
||||
|
||||
Sketch Esquisse
|
||||
Halftone Pattern… Trame de demi-teintes…
|
||||
Halftone Pattern Trame de demi-teintes
|
||||
Pattern Type Type de motif
|
||||
Dot Point
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Jeu de couleurs
|
||||
Modern Moderne
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4910,7 +4910,11 @@ imageTrace Outline Körvonal
|
||||
Amber Borostyán
|
||||
Coral Korall
|
||||
Violet Ibolya
|
||||
|
||||
Sketch Vázlat
|
||||
Halftone Pattern… Féltónus minta…
|
||||
Halftone Pattern Féltónus minta
|
||||
Pattern Type Mintatípus
|
||||
Dot Pont
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Színséma
|
||||
Modern Modern
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4520,7 +4520,11 @@ lighting Light Luce
|
||||
Coral Corallo
|
||||
Violet Viola
|
||||
Pink Rosa
|
||||
|
||||
Sketch Schizzo
|
||||
Halftone Pattern… Pattern mezzetinte…
|
||||
Halftone Pattern Pattern mezzetinte
|
||||
Pattern Type Tipo di pattern
|
||||
Dot Punto
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Combinazione colori
|
||||
Modern Moderno
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4561,7 +4561,11 @@ lighting Light ライト
|
||||
Amber アンバー
|
||||
Violet バイオレット
|
||||
Pink ピンク
|
||||
|
||||
Sketch スケッチ
|
||||
Halftone Pattern… ハーフトーンパターン…
|
||||
Halftone Pattern ハーフトーンパターン
|
||||
Pattern Type パターンタイプ
|
||||
Dot ドット
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme 配色
|
||||
Modern モダン
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4484,7 +4484,11 @@ lighting Light 광원
|
||||
Coral 코럴
|
||||
Violet 보라
|
||||
Pink 분홍
|
||||
|
||||
Sketch 스케치
|
||||
Halftone Pattern… 하프톤 패턴…
|
||||
Halftone Pattern 하프톤 패턴
|
||||
Pattern Type 패턴 유형
|
||||
Dot 점
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme 색 구성표
|
||||
Modern 모던
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4788,7 +4788,11 @@ mark-style Japanese Japońskie
|
||||
Coral Koralowy
|
||||
Violet Fioletowy
|
||||
Pink Różowy
|
||||
|
||||
Sketch Szkic
|
||||
Halftone Pattern… Wzór rastra…
|
||||
Halftone Pattern Wzór rastra
|
||||
Pattern Type Typ wzoru
|
||||
Dot Kropka
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Schemat kolorów
|
||||
Modern Nowoczesny
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4862,7 +4862,11 @@ mark-style Japanese Japonesas
|
||||
Amber Âmbar
|
||||
Coral Coral
|
||||
Violet Violeta
|
||||
|
||||
Sketch Esboço
|
||||
Halftone Pattern… Padrão de meio-tom…
|
||||
Halftone Pattern Padrão de meio-tom
|
||||
Pattern Type Tipo de padrão
|
||||
Dot Ponto
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Esquema de cores
|
||||
Modern Moderno
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -4516,7 +4516,11 @@ lighting Light Свет
|
||||
Coral Коралловый
|
||||
Violet Фиолетовый
|
||||
Pink Розовый
|
||||
|
||||
Sketch Эскиз
|
||||
Halftone Pattern… Полутоновый узор…
|
||||
Halftone Pattern Полутоновый узор
|
||||
Pattern Type Тип узора
|
||||
Dot Точка
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Цветовая схема
|
||||
Modern Современная
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4899,7 +4899,11 @@ theme Appearance Utseende
|
||||
Amber Bärnsten
|
||||
Coral Korall
|
||||
Violet Violett
|
||||
|
||||
Sketch Skiss
|
||||
Halftone Pattern… Rastermönster…
|
||||
Halftone Pattern Rastermönster
|
||||
Pattern Type Mönstertyp
|
||||
Dot Punkt
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Färgschema
|
||||
Modern Modern
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4500,7 +4500,11 @@ lighting Light Світло
|
||||
Coral Кораловий
|
||||
Violet Фіолетовий
|
||||
Pink Рожевий
|
||||
|
||||
Sketch Ескіз
|
||||
Halftone Pattern… Напівтоновий візерунок…
|
||||
Halftone Pattern Напівтоновий візерунок
|
||||
Pattern Type Тип візерунка
|
||||
Dot Крапка
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Колірна схема
|
||||
Modern Сучасна
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 718 and column 40.
|
@@ -4500,7 +4500,11 @@ theme Appearance Giao diện
|
||||
Coral San hô
|
||||
Violet Tím
|
||||
Pink Hồng
|
||||
|
||||
Sketch Phác thảo
|
||||
Halftone Pattern… Mẫu bán sắc…
|
||||
Halftone Pattern Mẫu bán sắc
|
||||
Pattern Type Loại mẫu
|
||||
Dot Chấm
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme Bảng phối màu
|
||||
Modern Hiện đại
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3734 and column 10.
|
@@ -4051,7 +4051,11 @@ lighting Light 光源
|
||||
Amber 琥珀色
|
||||
Coral 珊瑚色
|
||||
Violet 紫罗兰色
|
||||
|
||||
Sketch 素描
|
||||
Halftone Pattern… 半调图案…
|
||||
Halftone Pattern 半调图案
|
||||
Pattern Type 图案类型
|
||||
Dot 网点
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme 配色方案
|
||||
Modern 现代
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3839,7 +3839,11 @@ lighting Light 光源
|
||||
Coral 珊瑚色
|
||||
Violet 紫羅蘭色
|
||||
Pink 粉紅色
|
||||
|
||||
Sketch 素描
|
||||
Halftone Pattern… 半色調圖樣…
|
||||
Halftone Pattern 半色調圖樣
|
||||
Pattern Type 圖樣類型
|
||||
Dot 網點
|
||||
# User Interface › Color Scheme
|
||||
Color Scheme 配色方案
|
||||
Modern 現代
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3679,13 +3679,14 @@ fn font_items(english_names: bool) -> Vec<Item> {
|
||||
/// vector effects): (submenu, the catalogue's menu path of its effects).
|
||||
/// The raster Stylize submenu shares its path with the vector one and lists only the effects at
|
||||
/// that path that [`vectorcraft_effects::PIXEL_EFFECTS`] names.
|
||||
const RASTER_MENUS: [(&str, &[&str]); 9] = [
|
||||
const RASTER_MENUS: [(&str, &[&str]); 10] = [
|
||||
("Artistic", &["Effect", "Artistic"]),
|
||||
("Blur", &["Effect", "Blur"]),
|
||||
("Brush Strokes", &["Effect", "Brush Strokes"]),
|
||||
("Distort", &["Effect", "Distort"]),
|
||||
("Pixelate", &["Effect", "Pixelate"]),
|
||||
("Sharpen", &["Effect", "Sharpen"]),
|
||||
("Sketch", &["Effect", "Sketch"]),
|
||||
("Stylize", &["Effect", "Stylize"]),
|
||||
("Texture", &["Effect", "Texture"]),
|
||||
("Video", &["Effect", "Video"]),
|
||||
|
||||
@@ -140,6 +140,20 @@ 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: Halftone Pattern opens its dialog.
|
||||
#[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, ["Halftone Pattern…"]);
|
||||
assert_eq!(sketch[0].command.as_deref(), Some("effect.dialog"));
|
||||
assert_eq!(sketch[0].params, json!({"effect": "sketch.halftonePattern"}));
|
||||
let at = |label: &str| entries.iter().position(|e| e.path[0] == "Effect" && e.label == label).unwrap();
|
||||
assert!(at("Unsharp Mask…") < at("Halftone Pattern…") && at("Halftone Pattern…") < at("Glowing Edges…"));
|
||||
}
|
||||
|
||||
/// Effect › Stylize under Raster Effects, between Sharpen and Texture: Glowing Edges opens its
|
||||
/// dialog.
|
||||
#[test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Effects parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (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-16 · **Change:** minor (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
|
||||
@@ -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 | 44 | 88% | 6–10 |
|
||||
| Photoshop effects (raster, Effect Gallery) | 57 | 29 | 51% | 23–39 |
|
||||
| Photoshop effects (raster, Effect Gallery) | 57 | 30 | 53% | 22–38 |
|
||||
| 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 | 0/14 | all |
|
||||
| Sketch | Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Conté Crayon, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster, Reticulation, Stamp, Torn Edges, Water Paper | 1/14 | Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Conté Crayon, Graphic Pen, Note Paper, Photocopy, Plaster, Reticulation, 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 | — |
|
||||
@@ -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 28 missing filters are ~19–34 h and the Effect
|
||||
(see [calibration](target-app-parity.md#calibration)), so the 27 missing filters are ~18–33 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
|
||||
@@ -99,6 +99,7 @@ angular cut caps, exact visibility, 3D Classic, Turntable, 3D export) is 25–45
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 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-15 | minor | Round Corners rounds corners whose anchor carries a handle (where a curve meets a line, or two curves meet) with the round cut Live Corners uses (#1144), reaching `radius` along each side; before, they were left sharp. Corners between handle-less anchors are rounded as before; score unchanged (estimated) |
|
||||
| 2026-10-14 | minor | Revolve: automatic smooth vector shading, default-on Cap with continuous curved rims, and bounded seam joins; angular cut sides remain open. Scores and hour estimates unchanged at this rounding |
|
||||
| 2026-10-14 | minor | Wide drop shadows and glows at high zoom render within the raster budget (#1706); stacked Transform effects are bounded (#1705); 3D effects keep the area a nonzero compound path fills (#1724); pattern tiles redraw when a symbol, pattern or image their art shows changes (#1723) (all #1864). Scores unchanged (estimated) |
|
||||
|
||||
+7
-6
File diff suppressed because one or more lines are too long
+4
-3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user