mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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…",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}})),
|
||||
|
||||
@@ -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")]),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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
@@ -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
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