mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
G2: Effect › Artistic › Dry Brush raster filter
Squashed from #3026 onto main.
This commit is contained in:
@@ -354,6 +354,13 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
|
||||
"{levels: 2..8 (4; steps per colour channel: more levels, more flat colours), edgeSimplicity: 0..10 (4; simpler, smoother edges), edgeFidelity: 1..3 (2; keeps more of the edges' detail)} redraws the object as flat pieces of coloured paper, each piece painted in the average colour that falls into it, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.",
|
||||
json!({"levels": 4.0, "edgeSimplicity": 4.0, "edgeFidelity": 2.0}),
|
||||
),
|
||||
r(
|
||||
"artistic.dryBrush",
|
||||
"Dry Brush…",
|
||||
ARTISTIC,
|
||||
"{brushSize: 1..10 (2; length of the brush strokes), brushDetail: 0..10 (10; more detail keeps finer structure), texture: 1..3 (1; stronger edges and bristle grain at 3)} repaints the object with short strokes along its form and darkens its edges, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.",
|
||||
json!({"brushSize": 2.0, "brushDetail": 10.0, "texture": 1.0}),
|
||||
),
|
||||
r(
|
||||
"artistic.filmGrain",
|
||||
"Film Grain…",
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
//! Effect › Artistic (Cutout, Film Grain, Neon Glow, Plastic Wrap, Poster Edges). Original filters built from
|
||||
//! Effect › Artistic (Cutout, Dry Brush, Film Grain, Neon Glow, Plastic Wrap, Poster Edges, Smudge Stick, Sponge and
|
||||
//! Watercolor). Original filters built from
|
||||
//! public behaviour documentation:
|
||||
//! <https://helpx.adobe.com/illustrator/using/summary-effects.html> and
|
||||
//! <https://helpx.adobe.com/photoshop-elements/using/artistic-filters.html>.
|
||||
//! These are visual approximations, not reproductions of a proprietary algorithm.
|
||||
|
||||
use super::brushstrokes::stroke_reach;
|
||||
use super::edges::sobel;
|
||||
use super::pixelate::{Place, cell, rand};
|
||||
|
||||
const SALT_PLASTIC: u64 = 512;
|
||||
use super::pixelate::{Place, cell, rand, value_noise};
|
||||
use super::texture::{luma, premultiply, straight};
|
||||
use super::{PixelSpace, blur_plane};
|
||||
use vectorcraft_geom::Point;
|
||||
use vectorcraft_geom::{Point, Vec2};
|
||||
|
||||
const SALT_DRY: u64 = 264;
|
||||
const MARGIN: f64 = 1.0;
|
||||
const SALT_PLASTIC: u64 = 512;
|
||||
|
||||
const SALT_FILM: u64 = 256;
|
||||
|
||||
@@ -314,6 +318,21 @@ pub(super) fn plastic_wrap(px: &mut [[u8; 4]], w: usize, h: usize, space: &Pixel
|
||||
});
|
||||
}
|
||||
|
||||
/// Stroke length (document units) for Dry Brush at Brush Size `size` (1..10).
|
||||
fn dry_brush_length(size: f64) -> f64 {
|
||||
2.0 + 2.8 * (size.clamp(1.0, 10.0) - 1.0)
|
||||
}
|
||||
|
||||
/// How much Dry Brush softens before smearing at Brush Detail `detail` (0..10; 10 keeps the most detail).
|
||||
fn dry_brush_sigma(detail: f64) -> f64 {
|
||||
0.22 * (10.0 - detail.clamp(0.0, 10.0))
|
||||
}
|
||||
|
||||
/// How far (document units) Dry Brush reads from a pixel.
|
||||
pub(super) fn dry_brush_reach(size: f64, detail: f64) -> f64 {
|
||||
stroke_reach(dry_brush_length(size)) + 3.0 * dry_brush_sigma(detail) + MARGIN
|
||||
}
|
||||
|
||||
/// How far (document units) Neon Glow's glow spreads at `size` (-24..24).
|
||||
pub(super) fn glow_spread(size: f64) -> f64 {
|
||||
0.25 * size.abs().min(24.0)
|
||||
@@ -343,3 +362,117 @@ pub(super) fn neon_glow(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpa
|
||||
*p = premultiply(c, f32::from(p[3]) / 255.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// The tone at `(x, y)` in `tone`, `w` × `h`, or 0 outside.
|
||||
fn tone_at(tone: &[f32], w: usize, h: usize, x: isize, y: isize) -> f32 {
|
||||
if x < 0 || y < 0 {
|
||||
return 0.0;
|
||||
}
|
||||
let (x, y) = (x as usize, y as usize);
|
||||
if x >= w || y >= h {
|
||||
return 0.0;
|
||||
}
|
||||
tone[y * w + x]
|
||||
}
|
||||
|
||||
/// A unit vector along the image gradient's tangent at `(x, y)`, or horizontal when flat.
|
||||
fn dry_brush_dir(tone: &[f32], w: usize, h: usize, x: usize, y: usize) -> Vec2 {
|
||||
let gx = 0.5 * (tone_at(tone, w, h, x as isize + 1, y as isize) - tone_at(tone, w, h, x as isize - 1, y as isize));
|
||||
let gy = 0.5 * (tone_at(tone, w, h, x as isize, y as isize + 1) - tone_at(tone, w, h, x as isize, y as isize - 1));
|
||||
let v = Vec2::new(-f64::from(gy), f64::from(gx));
|
||||
let len = v.hypot();
|
||||
if len > 1e-6 { v / len } else { Vec2::new(1.0, 0.0) }
|
||||
}
|
||||
|
||||
/// Premultiplied RGBA (0..1) at document `q`, bilinearly from `src`.
|
||||
fn sample_pm(src: &[[f32; 4]], w: usize, h: usize, place: &Place, q: Point) -> [f32; 4] {
|
||||
let p = place.to_px * q;
|
||||
if !(p.x.is_finite() && p.y.is_finite()) {
|
||||
return [0.0; 4];
|
||||
}
|
||||
let (x, y) = (p.x - 0.5, p.y - 0.5);
|
||||
let (i, j) = (x.floor() as isize, y.floor() as isize);
|
||||
let (fx, fy) = (x - i as f64, y - j as f64);
|
||||
let mut acc = [0.0f32; 4];
|
||||
for (row, wy) in [(j, 1.0 - fy), (j + 1, fy)] {
|
||||
let Some(row) = usize::try_from(row).ok().filter(|r| *r < h) else { continue };
|
||||
for (col, wx) in [(i, 1.0 - fx), (i + 1, fx)] {
|
||||
let Some(col) = usize::try_from(col).ok().filter(|c| *c < w) else { continue };
|
||||
let k = (wx * wy) as f32;
|
||||
if let Some(p) = src.get(row * w + col) {
|
||||
for (a, v) in acc.iter_mut().zip(p) {
|
||||
*a += *v * k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
/// Dry Brush: the object repainted with short strokes along its form, edges darkened. `size`
|
||||
/// (1..10) sets the stroke length, `detail` (0..10) how much is softened first (10 keeps the most
|
||||
/// detail), and `texture` (1..3) how strongly edges and bristle grain contrast. Coverage is kept.
|
||||
pub(super) fn dry_brush(px: &mut [[u8; 4]], w: usize, h: usize, space: &PixelSpace, size: f64, detail: f64, texture: f64) {
|
||||
if !([size, detail, texture].iter().all(|v| v.is_finite()) && space.px.is_finite() && space.px > 0.0) {
|
||||
return;
|
||||
}
|
||||
let len = dry_brush_length(size);
|
||||
let sigma = dry_brush_sigma(detail);
|
||||
let texture = texture.clamp(1.0, 3.0);
|
||||
let edge_gain = (0.25 + 0.2 * (texture - 1.0)) as f32;
|
||||
let bristle = (0.04 + 0.05 * (texture - 1.0)) as f32;
|
||||
let (mut colour, alpha) = planes(px);
|
||||
if sigma > 0.0 {
|
||||
soften(&mut colour, &alpha, w, h, sigma);
|
||||
}
|
||||
let at = |k: usize| [0, 1, 2].map(|i| colour[i].get(k).copied().unwrap_or(0.0));
|
||||
let tone: Vec<f32> = (0..w * h)
|
||||
.map(|k| {
|
||||
let c = at(k);
|
||||
let a = alpha.get(k).copied().unwrap_or(0.0);
|
||||
(0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2]) * a
|
||||
})
|
||||
.collect();
|
||||
let edges = sobel(&tone, w, h);
|
||||
let src: Vec<[f32; 4]> = (0..w * h)
|
||||
.map(|k| {
|
||||
let c = at(k);
|
||||
let a = alpha.get(k).copied().unwrap_or(0.0);
|
||||
[c[0] * a, c[1] * a, c[2] * a, a]
|
||||
})
|
||||
.collect();
|
||||
let Some(place) = Place::new(space, w, h) else { return };
|
||||
const SAMPLES: usize = 5;
|
||||
let mut out = px.to_vec();
|
||||
for (k, p) in out.iter_mut().enumerate() {
|
||||
if p[3] == 0 {
|
||||
continue;
|
||||
}
|
||||
let (x, y) = (k % w, k / w);
|
||||
let q = place.at(x, y);
|
||||
let dir = dry_brush_dir(&tone, w, h, x, y);
|
||||
let mut acc = [0.0f32; 4];
|
||||
for i in 0..SAMPLES {
|
||||
let t = (i as f64 / (SAMPLES - 1) as f64 - 0.5) * len;
|
||||
let s = sample_pm(&src, w, h, &place, q + dir * t);
|
||||
for (a, v) in acc.iter_mut().zip(s) {
|
||||
*a += v;
|
||||
}
|
||||
}
|
||||
for a in &mut acc {
|
||||
*a /= SAMPLES as f32;
|
||||
}
|
||||
let a = acc[3].clamp(0.0, 1.0);
|
||||
let c = if a > 1e-6 {
|
||||
[0, 1, 2].map(|i| (acc[i] / a).clamp(0.0, 1.0))
|
||||
} else {
|
||||
straight(*p)
|
||||
};
|
||||
let edge = edges[k].min(1.0);
|
||||
let dark = (edge_gain * edge).clamp(0.0, 0.75);
|
||||
let grain = bristle * (2.0 * value_noise(SALT_DRY, q.x / (5.0 * space.px), q.y / (5.0 * space.px)) - 1.0) as f32;
|
||||
let c = c.map(|v| (v * (1.0 - dark) + grain).clamp(0.0, 1.0));
|
||||
*p = premultiply(c, f32::from(p[3]) / 255.0);
|
||||
}
|
||||
px.copy_from_slice(&out);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Photoshop-style raster effects (Effect › Artistic › Cutout, Film Grain, Neon Glow, Plastic Wrap and
|
||||
//! Photoshop-style raster effects (Effect › Artistic › Cutout, Dry Brush, Film Grain, Neon Glow, Plastic Wrap and
|
||||
//! Poster Edges, Smudge Stick, Sponge and Watercolor, 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, Sketch › Chalk & Charcoal, Graphic Pen, Halftone Pattern, Note Paper, Plaster and
|
||||
//! Reticulation, Stylize ›
|
||||
//! Sharpen › Unsharp Mask, Sketch › Bas Relief, Chalk & Charcoal, Charcoal, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster,
|
||||
//! Reticulation and Stamp, 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.
|
||||
@@ -43,8 +43,9 @@ 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; 47] = [
|
||||
pub const PIXEL_EFFECTS: [&str; 48] = [
|
||||
"artistic.cutout",
|
||||
"artistic.dryBrush",
|
||||
"artistic.filmGrain",
|
||||
"artistic.neonGlow",
|
||||
"artistic.plasticWrap",
|
||||
@@ -100,6 +101,10 @@ pub enum PixelFx {
|
||||
/// Artistic › Cutout: flat pieces of colour in `levels` (2..8) steps per channel, their
|
||||
/// edges as simple as `simplicity` (0..10) and as faithful as `fidelity` (1..3) say.
|
||||
Cutout { levels: f64, simplicity: f64, fidelity: f64 },
|
||||
/// Artistic › Dry Brush: short strokes along the form with darkened edges; `size` (1..10) is the
|
||||
/// brush length, `detail` (0..10) how much detail is kept, `texture` (1..3) the edge and bristle
|
||||
/// contrast.
|
||||
DryBrush { size: f64, detail: f64, texture: f64 },
|
||||
/// Artistic › Film Grain: monochrome grain in shadows and midtones, a wider bright
|
||||
/// highlight area (0..20), and tonal intensity (0..10), preserving coverage.
|
||||
FilmGrain { grain: f64, highlight: f64, intensity: f64 },
|
||||
@@ -292,6 +297,11 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option<PixelFx> {
|
||||
simplicity: num(p, "edgeSimplicity", 4.0).clamp(0.0, 10.0),
|
||||
fidelity: num(p, "edgeFidelity", 2.0).round().clamp(1.0, 3.0),
|
||||
},
|
||||
"artistic.dryBrush" => PixelFx::DryBrush {
|
||||
size: num(p, "brushSize", 2.0).clamp(1.0, 10.0),
|
||||
detail: num(p, "brushDetail", 10.0).clamp(0.0, 10.0),
|
||||
texture: num(p, "texture", 1.0).round().clamp(1.0, 3.0),
|
||||
},
|
||||
"artistic.neonGlow" => PixelFx::NeonGlow {
|
||||
size: num(p, "glowSize", 5.0).clamp(-24.0, 24.0),
|
||||
brightness: num(p, "glowBrightness", 15.0).clamp(0.0, 50.0),
|
||||
@@ -540,6 +550,7 @@ impl PixelFx {
|
||||
PixelFx::SmudgeStick { .. } | PixelFx::Sponge { .. } | PixelFx::Watercolor { .. } => 0.0,
|
||||
PixelFx::FilmGrain { .. }
|
||||
| PixelFx::Cutout { .. }
|
||||
| PixelFx::DryBrush { .. }
|
||||
| PixelFx::NeonGlow { .. }
|
||||
| PixelFx::PlasticWrap { .. }
|
||||
| PixelFx::PosterEdges { .. }
|
||||
@@ -600,6 +611,7 @@ impl PixelFx {
|
||||
| PixelFx::HalftonePattern { .. }
|
||||
| PixelFx::NotePaper { .. }
|
||||
| PixelFx::Reticulation { .. } => Some(0.0),
|
||||
PixelFx::DryBrush { size, detail, .. } => Some(artistic::dry_brush_reach(size, detail)),
|
||||
PixelFx::Plaster { smoothness, .. } => Some(sketch::plaster_reach(smoothness)),
|
||||
PixelFx::WaterPaper { fiber_length, .. } => {
|
||||
Some(sketch::water_paper_shift(fiber_length) + 3.0 * (0.06 * fiber_length.clamp(3.0, 50.0)).max(0.35))
|
||||
@@ -632,6 +644,7 @@ impl PixelFx {
|
||||
let to_px = |len: f64| len / space.px.max(1e-9);
|
||||
match *self {
|
||||
PixelFx::Cutout { levels, simplicity, fidelity } => artistic::cutout(px, w, h, space, levels, simplicity, fidelity),
|
||||
PixelFx::DryBrush { size, detail, texture } => artistic::dry_brush(px, w, h, space, size, detail, texture),
|
||||
PixelFx::FilmGrain { grain, highlight, intensity } => artistic::film_grain(px, w, h, space, grain, highlight, intensity),
|
||||
PixelFx::SmudgeStick { stroke, highlight, intensity } => paint::smudge_stick(px, w, h, space, stroke, highlight, intensity),
|
||||
PixelFx::Sponge { brush, definition, smoothness } => paint::sponge(px, w, h, space, brush, definition, smoothness),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Effect › Sketch (Chalk & Charcoal, Chrome, Halftone Pattern, Note Paper, Plaster, Reticulation and Water Paper; Graphic Pen lives in
|
||||
//! Effect › Sketch (Bas Relief, Chalk & Charcoal, Charcoal, Chrome, Halftone Pattern, Note Paper, Photocopy, Plaster,
|
||||
//! Reticulation, Stamp and Water Paper; Graphic Pen lives in
|
||||
//! [`super::brushstrokes`]). Original filters built from public behaviour documentation:
|
||||
//! <https://helpx.adobe.com/illustrator/using/summary-effects.html> (Sketch effects redraw in
|
||||
//! black and white; Water Paper flows colours on damp fibrous paper) and the public Photoshop Elements Artistic filters guide. Visual
|
||||
|
||||
@@ -2401,6 +2401,11 @@ fn artistic_params_take_defaults_and_stay_in_range() {
|
||||
fx("artistic.cutout", json!({"levels": 1e308, "edgeSimplicity": -5, "edgeFidelity": "NaN"})),
|
||||
PixelFx::Cutout { levels: 8.0, simplicity: 0.0, fidelity: 2.0 }
|
||||
);
|
||||
assert_eq!(fx("artistic.dryBrush", json!({})), PixelFx::DryBrush { size: 2.0, detail: 10.0, texture: 1.0 });
|
||||
assert_eq!(
|
||||
fx("artistic.dryBrush", json!({"brushSize": 1e308, "brushDetail": 0, "texture": -2})),
|
||||
PixelFx::DryBrush { size: 10.0, detail: 0.0, texture: 1.0 }
|
||||
);
|
||||
assert_eq!(fx("artistic.posterEdges", json!({})), PixelFx::PosterEdges { thickness: 2.0, intensity: 1.0, posterization: 2.0 });
|
||||
assert_eq!(
|
||||
fx("artistic.posterEdges", json!({"edgeThickness": 99, "edgeIntensity": -1, "posterization": 6.4})),
|
||||
@@ -2418,7 +2423,7 @@ fn artistic_params_take_defaults_and_stay_in_range() {
|
||||
fx("artistic.plasticWrap", json!({"highlightStrength": -1, "detail": 99, "smoothness": 0})),
|
||||
PixelFx::PlasticWrap { highlight: 0.0, detail: 15.0, smoothness: 1.0 }
|
||||
);
|
||||
for id in ["artistic.cutout", "artistic.neonGlow", "artistic.plasticWrap", "artistic.posterEdges"] {
|
||||
for id in ["artistic.cutout", "artistic.dryBrush", "artistic.neonGlow", "artistic.plasticWrap", "artistic.posterEdges"] {
|
||||
assert_eq!(fx(id, json!({})).outset(Rect::new(0.0, 0.0, 40.0, 30.0)), 0.0, "{id} keeps the object's shape");
|
||||
}
|
||||
}
|
||||
@@ -2435,6 +2440,7 @@ fn artistic_filters_leave_invalid_coordinate_spaces_alone() {
|
||||
let colour = [0.3, 0.65, 1.0];
|
||||
let filters = [
|
||||
PixelFx::Cutout { levels: 4.0, simplicity: 4.0, fidelity: 2.0 },
|
||||
PixelFx::DryBrush { size: 2.0, detail: 10.0, texture: 1.0 },
|
||||
PixelFx::NeonGlow { size: 5.0, brightness: 15.0, colour },
|
||||
PixelFx::PlasticWrap { highlight: 20.0, detail: 12.0, smoothness: 15.0 },
|
||||
PixelFx::PosterEdges { thickness: 2.0, intensity: 1.0, posterization: 2.0 },
|
||||
@@ -2548,6 +2554,24 @@ fn poster_edges_thickness_widens_the_lines() {
|
||||
assert!(dark(10.0) > dark(0.0), "{} vs {}", dark(10.0), dark(0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_brush_repaints_and_preserves_coverage() {
|
||||
let (w, h) = (48, 24);
|
||||
let src = ramp(w, h);
|
||||
let mut out = src.clone();
|
||||
fx("artistic.dryBrush", json!({})).apply(&mut out, w, h, &space(w, h));
|
||||
assert_ne!(out, src);
|
||||
for (a, b) in src.as_chunks::<4>().0.iter().zip(out.as_chunks::<4>().0) {
|
||||
assert_eq!(a[3], b[3], "coverage preserved");
|
||||
}
|
||||
let mut clear = vec![0u8; w * h * 4];
|
||||
let before = clear.clone();
|
||||
fx("artistic.dryBrush", json!({})).apply(&mut clear, w, h, &space(w, h));
|
||||
assert_eq!(clear, before);
|
||||
let reach = fx("artistic.dryBrush", json!({"brushSize": 10, "brushDetail": 1})).reach();
|
||||
assert!(reach.is_some_and(|r| r > 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neon_glow_lights_the_bright_or_the_dark_areas_in_its_colour() {
|
||||
let (w, h) = (40, 20);
|
||||
|
||||
@@ -31,8 +31,9 @@ const BRUSH_STROKES: [(&str, &str); 8] = [
|
||||
];
|
||||
|
||||
/// Effect › Artistic, at their defaults.
|
||||
const ARTISTIC: [(&str, &str); 8] = [
|
||||
const ARTISTIC: [(&str, &str); 9] = [
|
||||
("artistic.cutout", "{}"),
|
||||
("artistic.dryBrush", "{}"),
|
||||
("artistic.filmGrain", "{}"),
|
||||
("artistic.neonGlow", "{}"),
|
||||
("artistic.plasticWrap", "{}"),
|
||||
|
||||
@@ -370,6 +370,7 @@ fn structured_junk() {
|
||||
("effect.apply", json!({"effect": "artistic.sponge", "params": {"brushSize": -1e308, "definition": 1e308, "smoothness": null}})),
|
||||
("effect.apply", json!({"effect": "artistic.watercolor", "params": {"brushDetail": "1e999", "shadowIntensity": -1, "texture": 1e308}})),
|
||||
("effect.apply", json!({"effect": "artistic.cutout", "params": {"levels": 1e308, "edgeSimplicity": "NaN", "edgeFidelity": -1e308}})),
|
||||
("effect.apply", json!({"effect": "artistic.dryBrush", "params": {"brushSize": -1e308, "brushDetail": "NaN", "texture": 1e308}})),
|
||||
("effect.apply", json!({"effect": "artistic.neonGlow", "params": {"glowSize": -1e308, "glowBrightness": "1e999", "color": [1]}})),
|
||||
("effect.apply", json!({"effect": "artistic.posterEdges", "params": {"edgeThickness": 1e308, "edgeIntensity": [], "posterization": -7}})),
|
||||
("effect.apply", json!({"effect": "sketch.chrome", "params": {"detail": -1e308, "smoothness": "NaN"}})),
|
||||
|
||||
@@ -4547,6 +4547,8 @@ lighting Light Светлина
|
||||
Film Grain… Филмова зърнистост…
|
||||
Cutout… Апликация…
|
||||
Cutout Апликация
|
||||
Dry Brush… Суха четка…
|
||||
Dry Brush Суха четка
|
||||
Neon Glow… Неоново сияние…
|
||||
Neon Glow Неоново сияние
|
||||
Poster Edges… Плакатни ръбове…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5046,6 +5046,8 @@ imageTrace Outline Obrys
|
||||
Film Grain… Filmové zrno…
|
||||
Cutout… Výřez…
|
||||
Cutout Výřez
|
||||
Dry Brush… Suchý štětec…
|
||||
Dry Brush Suchý štětec
|
||||
Neon Glow… Neonová záře…
|
||||
Neon Glow Neonová záře
|
||||
Poster Edges… Plakátové okraje…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5165,9 +5165,11 @@ swatch Bright Violet Kraftig violet
|
||||
Unlock Variables Lås variabler op
|
||||
Change to Artboard Rulers Skift til kunstpladelinealer
|
||||
Cutout… Udklip…
|
||||
Dry Brush… Tør pensel…
|
||||
Neon Glow… Neonglød…
|
||||
Poster Edges… Plakatkanter…
|
||||
Cutout Udklip
|
||||
Dry Brush Tør pensel
|
||||
Neon Glow Neonglød
|
||||
Poster Edges Plakatkanter
|
||||
Apply Data Set Anvend datasæt
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4634,6 +4634,8 @@ lighting Light Licht
|
||||
Film Grain… Filmkörnung…
|
||||
Cutout… Ausschnitt…
|
||||
Cutout Ausschnitt
|
||||
Dry Brush… Trockenpinsel…
|
||||
Dry Brush Trockenpinsel
|
||||
Neon Glow… Neonglühen…
|
||||
Neon Glow Neonglühen
|
||||
Poster Edges… Posterkanten…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2259,6 +2259,8 @@ lighting Light Φως
|
||||
Film Grain… Κόκκος φιλμ…
|
||||
Cutout… Αποκοπή…
|
||||
Cutout Αποκοπή
|
||||
Dry Brush… Ξηρό πινέλο…
|
||||
Dry Brush Ξηρό πινέλο
|
||||
Neon Glow… Λάμψη νέον…
|
||||
Neon Glow Λάμψη νέον
|
||||
Poster Edges… Άκρες αφίσας…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2029 and column 19.
|
@@ -4634,6 +4634,8 @@ lighting Light Luz
|
||||
Film Grain… Granulado de película…
|
||||
Cutout… Recorte…
|
||||
Cutout Recorte
|
||||
Dry Brush… Pincel seco…
|
||||
Dry Brush Pincel seco
|
||||
Neon Glow… Resplandor de neón…
|
||||
Neon Glow Resplandor de neón
|
||||
Poster Edges… Bordes de póster…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4941,6 +4941,8 @@ imageTrace Outline Ääriviiva
|
||||
Film Grain… Filmin rakeisuus…
|
||||
Cutout… Leikkaus…
|
||||
Cutout Leikkaus
|
||||
Dry Brush… Kuiva sivellin…
|
||||
Dry Brush Kuiva sivellin
|
||||
Neon Glow… Neonhehku…
|
||||
Neon Glow Neonhehku
|
||||
Poster Edges… Julisteen reunat…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4644,6 +4644,8 @@ lighting Light Lumière
|
||||
Film Grain… Grain photographique…
|
||||
Cutout… Découpage…
|
||||
Cutout Découpage
|
||||
Dry Brush… Pinceau sec…
|
||||
Dry Brush Pinceau sec
|
||||
Neon Glow… Néon…
|
||||
Neon Glow Néon
|
||||
Poster Edges… Bords d’affiche…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1581,6 +1581,8 @@ file-filter Templates Sablonok
|
||||
Film Grain… Filmszemcse…
|
||||
Cutout… Kivágás…
|
||||
Cutout Kivágás
|
||||
Dry Brush… Száraz ecset…
|
||||
Dry Brush Száraz ecset
|
||||
Neon Glow… Neonfény…
|
||||
Neon Glow Neonfény
|
||||
Poster Edges… Poszterszélek…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5165,9 +5165,11 @@ swatch Bright Violet Violet cerah
|
||||
Unlock Variables Buka kunci variabel
|
||||
Change to Artboard Rulers Ubah ke penggaris artboard
|
||||
Cutout… Potongan…
|
||||
Dry Brush… Kuas kering…
|
||||
Neon Glow… Pendar neon…
|
||||
Poster Edges… Tepi poster…
|
||||
Cutout Potongan
|
||||
Dry Brush Kuas kering
|
||||
Neon Glow Pendar neon
|
||||
Poster Edges Tepi poster
|
||||
Apply Data Set Terapkan set data
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3797 and column 19.
|
@@ -4676,6 +4676,8 @@ lighting Light Luce
|
||||
Film Grain… Grana della pellicola…
|
||||
Cutout… Ritaglio…
|
||||
Cutout Ritaglio
|
||||
Dry Brush… Pennello secco…
|
||||
Dry Brush Pennello secco
|
||||
Neon Glow… Bagliore neon…
|
||||
Neon Glow Bagliore neon
|
||||
Poster Edges… Bordi poster…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4715,6 +4715,8 @@ lighting Light ライト
|
||||
Film Grain… フィルム粒子…
|
||||
Cutout… カットアウト…
|
||||
Cutout カットアウト
|
||||
Dry Brush… ドライブラシ…
|
||||
Dry Brush ドライブラシ
|
||||
Neon Glow… ネオングロー…
|
||||
Neon Glow ネオングロー
|
||||
Poster Edges… ポスターエッジ…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5002,7 +5002,9 @@ imageTrace Outline კონტური
|
||||
Create Slice from Selection ნაჭრის შექმნა მონიშნულიდან
|
||||
Create Slices from Guides ნაჭრების შექმნა დამხმარე ხაზებიდან
|
||||
Cutout აპლიკაცია
|
||||
Dry Brush მშრალი ფერწანი
|
||||
Cutout… აპლიკაცია…
|
||||
Dry Brush… მშრალი ფერწანი…
|
||||
Delete All Slices ყველა ნაჭრის წაშლა
|
||||
Delete Data Sets მონაცემთა ნაკრებების წაშლა
|
||||
Delete Graphic Styles გრაფიკული სტილების წაშლა
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4639,6 +4639,8 @@ lighting Light 광원
|
||||
Film Grain… 필름 그레인…
|
||||
Cutout… 오려내기…
|
||||
Cutout 오려내기
|
||||
Dry Brush… 드라이 브러시…
|
||||
Dry Brush 드라이 브러시
|
||||
Neon Glow… 네온 글로…
|
||||
Neon Glow 네온 글로
|
||||
Poster Edges… 포스터 가장자리…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5177,9 +5177,11 @@ swatch Bright Violet Felviolet
|
||||
Unlock Variables Variabelen ontgrendelen
|
||||
Change to Artboard Rulers Wijzigen in tekenbordlinialen
|
||||
Cutout… Knipsel…
|
||||
Dry Brush… Droog penseel…
|
||||
Neon Glow… Neongloed…
|
||||
Poster Edges… Posterranden…
|
||||
Cutout Knipsel
|
||||
Dry Brush Droog penseel
|
||||
Neon Glow Neongloed
|
||||
Poster Edges Posterranden
|
||||
Apply Data Set Gegevensset toepassen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4879,6 +4879,8 @@ imageTrace Outline Kontur
|
||||
Film Grain… Ziarno filmu…
|
||||
Cutout… Wycinanka…
|
||||
Cutout Wycinanka
|
||||
Dry Brush… Suchy pędzel…
|
||||
Dry Brush Suchy pędzel
|
||||
Neon Glow… Neonowa poświata…
|
||||
Neon Glow Neonowa poświata
|
||||
Poster Edges… Krawędzie plakatu…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4035,6 +4035,8 @@ axis Both Ambos
|
||||
Film Grain… Granulação de filme…
|
||||
Cutout… Recorte…
|
||||
Cutout Recorte
|
||||
Dry Brush… Pincel seco…
|
||||
Dry Brush Pincel seco
|
||||
Neon Glow… Brilho neon…
|
||||
Neon Glow Brilho neon
|
||||
Poster Edges… Bordas de pôster…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -4672,6 +4672,8 @@ lighting Light Свет
|
||||
Film Grain… Зернистость плёнки…
|
||||
Cutout… Аппликация…
|
||||
Cutout Аппликация
|
||||
Dry Brush… Сухая кисть…
|
||||
Dry Brush Сухая кисть
|
||||
Neon Glow… Неоновое свечение…
|
||||
Neon Glow Неоновое свечение
|
||||
Poster Edges… Края плаката…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4553,6 +4553,8 @@ lighting Light Ljus
|
||||
Film Grain… Filmkorn…
|
||||
Cutout… Urklipp…
|
||||
Cutout Urklipp
|
||||
Dry Brush… Torr pensel…
|
||||
Dry Brush Torr pensel
|
||||
Neon Glow… Neonsken…
|
||||
Neon Glow Neonsken
|
||||
Poster Edges… Affischkanter…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4656,6 +4656,8 @@ lighting Light Світло
|
||||
Film Grain… Зернистість плівки…
|
||||
Cutout… Аплікація…
|
||||
Cutout Аплікація
|
||||
Dry Brush… Суха кисть…
|
||||
Dry Brush Суха кисть
|
||||
Neon Glow… Неонове сяйво…
|
||||
Neon Glow Неонове сяйво
|
||||
Poster Edges… Плакатні краї…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 726 and column 40.
|
@@ -1269,6 +1269,8 @@
|
||||
Film Grain… Hạt phim…
|
||||
Cutout… Cắt dán…
|
||||
Cutout Cắt dán
|
||||
Dry Brush… Cọ khô…
|
||||
Dry Brush Cọ khô
|
||||
Neon Glow… Ánh neon…
|
||||
Neon Glow Ánh neon
|
||||
Poster Edges… Viền áp phích…
|
||||
|
||||
|
@@ -4937,7 +4937,9 @@ lighting Light 光源
|
||||
Artistic 艺术效果
|
||||
Film Grain… 胶片颗粒…
|
||||
Cutout… 剪纸…
|
||||
Dry Brush… 干画笔…
|
||||
Cutout 剪纸
|
||||
Dry Brush 干画笔
|
||||
Neon Glow… 霓虹灯光…
|
||||
Neon Glow 霓虹灯光
|
||||
Poster Edges… 海报边缘…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4743,7 +4743,9 @@ lighting Light 光源
|
||||
Artistic 藝術效果
|
||||
Film Grain… 底片顆粒…
|
||||
Cutout… 剪紙…
|
||||
Dry Brush… 乾畫筆…
|
||||
Cutout 剪紙
|
||||
Dry Brush 乾畫筆
|
||||
Neon Glow… 霓虹光…
|
||||
Neon Glow 霓虹光
|
||||
Poster Edges… 海報邊緣…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -86,9 +86,23 @@ fn effect_menu_lists_the_artistic_filters_before_blur() {
|
||||
let entries = menus::menu_entries(&app);
|
||||
let artistic: Vec<_> = entries.iter().filter(|e| e.path == ["Effect", "Artistic"]).collect();
|
||||
let labels: Vec<&str> = artistic.iter().map(|e| e.label.as_str()).collect();
|
||||
assert_eq!(labels, ["Cutout…", "Film Grain…", "Neon Glow…", "Plastic Wrap…", "Poster Edges…", "Smudge Stick…", "Sponge…", "Watercolor…"]);
|
||||
assert_eq!(
|
||||
labels,
|
||||
[
|
||||
"Cutout…",
|
||||
"Dry Brush…",
|
||||
"Film Grain…",
|
||||
"Neon Glow…",
|
||||
"Plastic Wrap…",
|
||||
"Poster Edges…",
|
||||
"Smudge Stick…",
|
||||
"Sponge…",
|
||||
"Watercolor…"
|
||||
]
|
||||
);
|
||||
assert!(artistic.iter().all(|e| e.command.as_deref() == Some("effect.dialog")));
|
||||
assert_eq!(artistic[2].params, json!({"effect": "artistic.neonGlow"}));
|
||||
assert_eq!(artistic[1].params, json!({"effect": "artistic.dryBrush"}));
|
||||
assert_eq!(artistic[3].params, json!({"effect": "artistic.neonGlow"}));
|
||||
let at = |name: &str| entries.iter().position(|e| e.path == ["Effect", name]).unwrap();
|
||||
assert!(at("Artistic") < at("Blur"));
|
||||
}
|
||||
@@ -209,7 +223,8 @@ fn effect_menu_lists_the_sketch_filters_between_sharpen_and_stylize() {
|
||||
assert_eq!(sketch[10].params, json!({"effect": "sketch.stamp"}));
|
||||
assert_eq!(sketch[11].params, json!({"effect": "sketch.waterPaper"}));
|
||||
let at = |label: &str| entries.iter().position(|e| e.path[0] == "Effect" && e.label == label).unwrap();
|
||||
assert!(at("Unsharp Mask…") < at("Chalk & Charcoal…") && at("Chalk & Charcoal…") < at("Chrome…"));
|
||||
assert!(at("Unsharp Mask…") < at("Bas Relief…") && at("Bas Relief…") < at("Chalk & Charcoal…"));
|
||||
assert!(at("Chalk & Charcoal…") < at("Chrome…"));
|
||||
assert!(at("Chrome…") < at("Graphic Pen…") && at("Graphic Pen…") < at("Halftone Pattern…"));
|
||||
assert!(at("Halftone Pattern…") < at("Note Paper…") && at("Note Paper…") < at("Plaster…"));
|
||||
assert!(at("Plaster…") < at("Reticulation…"));
|
||||
|
||||
Reference in New Issue
Block a user