G2: Effect › Artistic › Rough Pastels raster filter

Squashed from #3044 onto main.
This commit is contained in:
bflatastic
2026-10-22 00:23:50 +07:00
parent 695f7c0d4b
commit 755f2b9158
31 changed files with 200 additions and 10 deletions
+15
View File
@@ -406,6 +406,21 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
"{edgeThickness: 0..10 (2; wider lines), edgeIntensity: 0..10 (1; darker lines, on fainter edges too), posterization: 0..6 (2; posterization + 2 levels per colour channel)} reduces the object's colours and draws black lines on its edges, keeping its coverage. Original visual approximation; chosen VectorCraft defaults.",
json!({"edgeThickness": 2.0, "edgeIntensity": 1.0, "posterization": 2.0}),
),
r(
"artistic.roughPastels",
"Rough Pastels…",
ARTISTIC,
"{strokeLength: 0..40 (6; longer pastel strokes), strokeDetail: 1..20 (10; bristle texture along the strokes), texture: \"brick\"|\"burlap\"|\"canvas\"|\"sandstone\" (\"canvas\"), scaling: % 50..200 (100), relief: 0..50 (4), lightDirection: \"bottom\"|\"bottomLeft\"|\"left\"|\"topLeft\"|\"top\"|\"topRight\"|\"right\"|\"bottomRight\" (\"top\"), invert: bool (false)} repaints the object in soft diagonal pastel strokes over a textured surface, keeping its coverage. Slider ranges follow the documented controls; numeric defaults are chosen for VectorCraft and are not verified against proprietary defaults.",
json!({
"strokeLength": 6.0,
"strokeDetail": 10.0,
"texture": "canvas",
"scaling": 100.0,
"relief": 4.0,
"lightDirection": "top",
"invert": false
}),
),
r(
"artistic.smudgeStick",
"Smudge Stick…",
+64 -3
View File
@@ -1,5 +1,5 @@
//! Effect › Artistic (Cutout, Dry Brush, Film Grain, Neon Glow, Paint Daubs, Palette Knife, Plastic Wrap, Poster
//! Edges, Smudge Stick, Sponge, Underpainting and Watercolor). Original filters built from
//! Edges, Rough Pastels, Smudge Stick, Sponge, Underpainting 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>.
@@ -7,10 +7,10 @@
use std::f64::consts::TAU;
use super::brushstrokes::{Smear, mix, stroke_reach, unit, unpremultiply};
use super::brushstrokes::{LEFT, RIGHT, Smear, TONE_SOFTNESS, mix, streak, stroke_reach, unit, unpremultiply};
use super::edges::sobel;
use super::pixelate::{Around, Place, Soft, cell, point_in, rand, value_noise};
use super::texture::{self, Light, Texture, luma, premultiply, straight};
use super::texture::{self, Light, Texture, Tones, luma, premultiply, straight, texturizer};
use super::{PixelSpace, blur_plane, pick};
use vectorcraft_geom::{Point, Vec2};
@@ -18,6 +18,8 @@ const SALT_DRY: u64 = 264;
const MARGIN: f64 = 1.0;
const SALT_PLASTIC: u64 = 512;
const SALT_PASTEL: u64 = 200;
const SALT_FILM: u64 = 256;
const SALT_DAUB: u64 = 272;
@@ -526,6 +528,65 @@ pub(super) fn underpainting(
}
}
/// How far (document units) Rough Pastels reaches with strokes `len` long.
pub(super) fn rough_pastels_reach(len: f64) -> f64 {
super::brushstrokes::stroke_reach(len).max(super::brushstrokes::TONE_REACH)
}
/// Rough Pastels: soft diagonal pastel strokes `length` (0..40) long with bristle `detail` (1..20),
/// then the shared Texturizer surface (`texture`, `scaling`, `relief`, `light`, `invert`).
/// Coverage is kept.
#[allow(clippy::too_many_arguments)]
pub(super) fn rough_pastels(
px: &mut [[u8; 4]],
w: usize,
h: usize,
space: &PixelSpace,
length: f64,
detail: f64,
texture: &Texture,
scaling: f64,
relief: f64,
light: Light,
invert: bool,
) {
if !([length, detail, scaling, relief].iter().all(|v| v.is_finite()) && space.px.is_finite() && space.px > 0.0) {
return;
}
let length = length.clamp(0.0, 40.0);
let detail = detail.clamp(1.0, 20.0);
if length > 0.0 {
let Some(place) = Place::new(space, w, h) else { return };
let smear = Smear::new(px, w, h, &place, length.max(1.0));
let tones = Tones::new(px, w, h, TONE_SOFTNESS / place.px);
let row = (0.18 * smear.len).max(1.0);
let stroke_mix = (0.35 + 0.0225 * detail).clamp(0.35, 0.8) as f32;
let bristle = (0.015 + 0.0035 * detail) as f32;
let src = px.to_vec();
place.each(px, |x, y, p| {
if p[3] == 0 {
return;
}
let q = place.at(x, y);
let pm = mix(smear.stroke(SALT_PASTEL, q, RIGHT, row), smear.stroke(SALT_PASTEL + 1, q, LEFT, row), 0.5);
let orig = unit(src.get(y * w + x).copied().unwrap_or([0; 4]));
let pm = mix(orig, pm, stroke_mix);
let (mut c, a) = unpremultiply(pm);
if a <= 0.0 {
*p = [0; 4];
return;
}
let dir = if tones.get(x, y) > 0.52 { RIGHT } else { LEFT };
let n = bristle * streak(SALT_PASTEL + 2, q, dir, smear.len.max(1.0), row) as f32;
c = c.map(|v| (v + n).clamp(0.0, 1.0));
*p = premultiply(c, orig[3]);
});
}
if relief > 0.0 {
texturizer(px, w, h, space, texture, scaling, relief, light, invert);
}
}
/// Neon Glow: the object in dimmed greys, lit by `colour` glowing from its light areas (`size`
/// above 0) or from its dark areas (below 0), spread as far as |`size`| (0..24) says and as
/// strong as `brightness` (0..50). Coverage is kept.
+1 -1
View File
@@ -231,7 +231,7 @@ fn frame(q: Point, dir: Vec2) -> (f64, f64) {
/// How far along `dir` the stroke through `q` is shifted: strokes `len` long lie in rows `width`
/// apart across `dir`, each row starting at random, each stroke shifted by up to [`JITTER`] ×
/// `len`.
fn stroke_shift(salt: u64, q: Point, dir: Vec2, len: f64, width: f64) -> f64 {
pub(super) fn stroke_shift(salt: u64, q: Point, dir: Vec2, len: f64, width: f64) -> f64 {
if !(len > 0.0 && width > 0.0) {
return 0.0;
}
+24 -5
View File
@@ -1,5 +1,5 @@
//! Photoshop-style raster effects (Effect › Artistic › Cutout, Dry Brush, Film Grain, Neon Glow, Paint Daubs, Plastic Wrap and
//! Poster Edges, Smudge Stick, Sponge and Watercolor, Blur › Radial Blur and Smart Blur, Brush Strokes › Accented Edges, Angled Strokes,
//! Poster Edges, Rough Pastels, 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 › Bas Relief, Chalk & Charcoal, Charcoal, Graphic Pen, Halftone Pattern, Note Paper, Photocopy, Plaster,
@@ -44,7 +44,7 @@ pub use sketch::{HALFTONE_TYPES, HalftonePattern};
pub use texture::{GRAIN_TYPES, Grain, LIGHT_DIRECTIONS, Light, TEXTURES, TEXTURES_LOADED, Texture};
/// The Photoshop-style effect ids (all raster effects, see [`crate::is_raster`]).
pub const PIXEL_EFFECTS: [&str; 51] = [
pub const PIXEL_EFFECTS: [&str; 52] = [
"artistic.cutout",
"artistic.dryBrush",
"artistic.filmGrain",
@@ -53,6 +53,7 @@ pub const PIXEL_EFFECTS: [&str; 51] = [
"artistic.paletteKnife",
"artistic.plasticWrap",
"artistic.posterEdges",
"artistic.roughPastels",
"artistic.smudgeStick",
"artistic.sponge",
"artistic.underpainting",
@@ -143,6 +144,10 @@ pub enum PixelFx {
/// `texture` surface (`scaling`, `relief`, `light`, `invert`) blended in as strongly as
/// `texture_coverage` (0..40) says.
Underpainting { brush_size: f64, texture_coverage: f64, texture: Texture, scaling: f64, relief: f64, light: Light, invert: bool },
/// Artistic › Rough Pastels: diagonal pastel strokes `length` (0..40) long, bristle detail
/// (1..20), then a [`Texture`] surface scaled by `scaling` (0.5..2), in relief as strong as
/// `relief` (0..50) and lit from `light`; `invert` turns the surface's heights over.
RoughPastels { length: f64, detail: f64, texture: Texture, scaling: f64, relief: f64, light: Light, invert: bool },
/// Blur › Radial Blur around the object's centre: along arcs (spin, an arc of `amount`°) or
/// along rays (zoom, the content scaled by up to about ±`amount`/2 %), averaging 2^`passes` samples.
RadialBlur { amount: f64, zoom: bool, passes: u32 },
@@ -346,6 +351,15 @@ pub(crate) fn parse(id: &str, p: &Value) -> Option<PixelFx> {
light: Light::parse(text(p, "lightDirection", "top")),
invert: flag(p, "invert", false),
},
"artistic.roughPastels" => PixelFx::RoughPastels {
length: num(p, "strokeLength", 6.0).clamp(0.0, 40.0),
detail: num(p, "strokeDetail", 10.0).clamp(1.0, 20.0),
texture: Texture::parse(text(p, "texture", "canvas")),
scaling: num(p, "scaling", 100.0).clamp(50.0, 200.0) / 100.0,
relief: num(p, "relief", 4.0).clamp(0.0, 50.0),
light: Light::parse(text(p, "lightDirection", "top")),
invert: flag(p, "invert", false),
},
"artistic.smudgeStick" => PixelFx::SmudgeStick {
stroke: num(p, "strokeLength", 2.0).clamp(0.0, 10.0),
highlight: num(p, "highlightArea", 12.0).clamp(0.0, 20.0),
@@ -558,9 +572,10 @@ impl PixelFx {
PixelFx::AccentedEdges { .. } => 0.0,
// Strokes reach out of the object as far as they reach into it.
PixelFx::PaletteKnife { stroke_size, softness, .. } => artistic::palette_knife_reach(stroke_size, softness),
PixelFx::AngledStrokes { length, .. } | PixelFx::GraphicPen { length, .. } | PixelFx::InkOutlines { length, .. } => {
brushstrokes::stroke_reach(length)
}
PixelFx::AngledStrokes { length, .. }
| PixelFx::GraphicPen { length, .. }
| PixelFx::InkOutlines { length, .. }
| PixelFx::RoughPastels { length, .. } => brushstrokes::stroke_reach(length),
PixelFx::Crosshatch { length, .. } => brushstrokes::stroke_reach(length) + brushstrokes::rough(length),
PixelFx::DarkStrokes { .. } => brushstrokes::stroke_reach(brushstrokes::LIGHT_LENGTH),
PixelFx::Spatter { radius, .. } => brushstrokes::spatter_shift(radius),
@@ -622,6 +637,7 @@ impl PixelFx {
PixelFx::AngledStrokes { length, .. } | PixelFx::GraphicPen { length, .. } => {
Some(brushstrokes::stroke_reach(length).max(brushstrokes::TONE_REACH))
}
PixelFx::RoughPastels { length, .. } => Some(artistic::rough_pastels_reach(length)),
PixelFx::Crosshatch { length, .. } => {
Some((brushstrokes::stroke_reach(length) + brushstrokes::rough(length)).max(brushstrokes::TONE_REACH))
}
@@ -701,6 +717,9 @@ impl PixelFx {
PixelFx::Underpainting { brush_size, texture_coverage, ref texture, scaling, relief, light, invert } => {
artistic::underpainting(px, w, h, space, brush_size, texture_coverage, texture, scaling, relief, light, invert)
}
PixelFx::RoughPastels { length, detail, ref texture, scaling, relief, light, invert } => {
artistic::rough_pastels(px, w, h, space, length, detail, texture, scaling, relief, light, invert)
}
PixelFx::RadialBlur { amount, zoom, passes } => blur::radial(px, w, h, space, amount, zoom, passes),
PixelFx::SmartBlur { radius, threshold, samples, mode: SmartBlurMode::Normal } => {
blur::smart(px, w, h, to_px(radius), threshold, samples)
+30
View File
@@ -1222,6 +1222,8 @@ fn texture_params_take_defaults_and_stay_in_range() {
("texture.grain", "grainType", GRAIN_TYPES.as_slice()),
("texture.texturizer", "texture", TEXTURES.as_slice()),
("texture.texturizer", "lightDirection", LIGHT_DIRECTIONS.as_slice()),
("artistic.roughPastels", "texture", TEXTURES.as_slice()),
("artistic.roughPastels", "lightDirection", LIGHT_DIRECTIONS.as_slice()),
] {
let doc = crate::effect_info(id).unwrap().params;
let parsed: Vec<PixelFx> = table.iter().map(|(_, v)| fx(id, json!({ key: v }))).collect();
@@ -2466,6 +2468,17 @@ fn artistic_params_take_defaults_and_stay_in_range() {
invert: true,
}
);
assert_eq!(
fx("artistic.roughPastels", json!({})),
PixelFx::RoughPastels { length: 6.0, detail: 10.0, texture: Texture::Canvas, scaling: 1.0, relief: 4.0, light: Light::Top, invert: false }
);
assert_eq!(
fx(
"artistic.roughPastels",
json!({"strokeLength": 0, "strokeDetail": 99, "texture": -3, "scaling": 10, "relief": 60, "lightDirection": "up", "invert": true})
),
PixelFx::RoughPastels { length: 0.0, detail: 20.0, texture: Texture::Canvas, scaling: 0.5, relief: 50.0, light: Light::Top, invert: true }
);
assert_eq!(fx("artistic.plasticWrap", json!({})), PixelFx::PlasticWrap { highlight: 20.0, detail: 12.0, smoothness: 15.0 });
assert_eq!(
fx("artistic.plasticWrap", json!({"highlightStrength": -1, "detail": 99, "smoothness": 0})),
@@ -2493,6 +2506,7 @@ fn artistic_params_take_defaults_and_stay_in_range() {
assert!(parsed.iter().skip(i + 1).all(|p| *p != parsed[i]), "{id} {v}");
}
}
assert!(fx("artistic.roughPastels", json!({})).outset(Rect::new(0.0, 0.0, 40.0, 30.0)) > 0.0);
}
#[test]
@@ -2520,6 +2534,7 @@ fn artistic_filters_leave_invalid_coordinate_spaces_alone() {
light: Light::Top,
invert: false,
},
PixelFx::RoughPastels { length: 6.0, detail: 10.0, texture: Texture::Canvas, scaling: 1.0, relief: 4.0, light: Light::Top, invert: false },
];
for f in &filters {
assert_ne!(run(f, &space(w, h)), src, "{f:?} changes the art in a valid space");
@@ -2622,6 +2637,20 @@ fn cutout_paints_a_few_flat_colours_and_keeps_coverage() {
}
}
#[test]
fn rough_pastels_keeps_coverage_and_longer_strokes_smear_more() {
let (w, h) = (48, 48);
let src = ramp(w, h);
let mut short = src.clone();
let mut long = src.clone();
fx("artistic.roughPastels", json!({"strokeLength": 8, "strokeDetail": 18, "relief": 12})).apply(&mut short, w, h, &space(w, h));
fx("artistic.roughPastels", json!({"strokeLength": 32, "strokeDetail": 18, "relief": 12})).apply(&mut long, w, h, &space(w, h));
assert!(short.as_chunks::<4>().0.iter().zip(src.as_chunks::<4>().0).all(|(o, s)| o[3] == s[3]));
let changed = |a: &Vec<u8>, b: &Vec<u8>| a.as_chunks::<4>().0.iter().zip(b.as_chunks::<4>().0).filter(|(o, s)| o != s).count();
assert!(changed(&short, &src) > w * h / 8, "short strokes repaint many pixels");
assert!(changed(&long, &short) > w * h / 16, "longer strokes smear differently");
}
#[test]
fn cutout_simplicity_smooths_the_pieces_edges() {
// A noisy grey: simpler edges join the speckles into fewer changes between neighbours.
@@ -2747,6 +2776,7 @@ fn artistic_filters_look_the_same_at_any_resolution() {
("artistic.neonGlow", json!({"glowSize": 12, "glowBrightness": 30})),
("artistic.posterEdges", json!({"edgeThickness": 6, "edgeIntensity": 6})),
("artistic.underpainting", json!({"brushSize": 12, "textureCoverage": 20, "relief": 10})),
("artistic.roughPastels", json!({"strokeLength": 12, "strokeDetail": 15, "texture": "burlap", "relief": 10})),
] {
let (mut a, mut b) = (coarse.clone(), fine.clone());
fx(id, p.clone()).apply(&mut a, w, h, &space(w, h));
+15 -1
View File
@@ -31,7 +31,7 @@ const BRUSH_STROKES: [(&str, &str); 8] = [
];
/// Effect › Artistic, at their defaults.
const ARTISTIC: [(&str, &str); 12] = [
const ARTISTIC: [(&str, &str); 13] = [
("artistic.cutout", "{}"),
("artistic.dryBrush", "{}"),
("artistic.filmGrain", "{}"),
@@ -42,6 +42,7 @@ const ARTISTIC: [(&str, &str); 12] = [
("artistic.plasticWrap", "{}"),
("artistic.posterEdges", "{}"),
("artistic.underpainting", "{}"),
("artistic.roughPastels", "{}"),
("artistic.smudgeStick", "{}"),
("artistic.sponge", "{}"),
("artistic.watercolor", "{}"),
@@ -227,6 +228,19 @@ fn artistic_settings_survive_a_save_and_open() {
json!({"highlightStrength": 18.0, "detail": 10.0, "smoothness": 8.0}),
[json!({"highlightStrength": 5.0}), json!({"highlightStrength": 0.0})],
),
(
"artistic.roughPastels",
json!({
"strokeLength": 8.0,
"strokeDetail": 12.0,
"texture": "sandstone",
"scaling": 100.0,
"relief": 8.0,
"lightDirection": "topLeft",
"invert": false
}),
[json!({"strokeLength": 20.0}), json!({"relief": 0.0})],
),
] {
let (mut s, g) = striped();
// Black and a grey beside the reds: Cutout puts the grey with black at 2 levels and apart
+4
View File
@@ -373,6 +373,10 @@ fn structured_junk() {
("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": "artistic.roughPastels", "params": {"strokeLength": 1e308, "strokeDetail": "NaN", "texture": -1e308, "scaling": "bad", "lightDirection": "nope"}}),
),
("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}})),
+2
View File
@@ -4568,7 +4568,9 @@ lighting Light Светлина
Paint Daubs Мазки с боя
Palette Knife Нож за палитра
Poster Edges… Плакатни ръбове…
Rough Pastels… Груби пастели…
Poster Edges Плакатни ръбове
Rough Pastels Груби пастели
Underpainting… Подбоя…
Underpainting Подбоя
Levels Нива
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5067,7 +5067,9 @@ imageTrace Outline Obrys
Paint Daubs Malířské tahy
Palette Knife Špachtle
Poster Edges… Plakátové okraje…
Rough Pastels… Hrubé pastely…
Poster Edges Plakátové okraje
Rough Pastels Hrubé pastely
Underpainting… Podmalba…
Underpainting Podmalba
Levels Úrovně
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5187,6 +5187,7 @@ swatch Bright Violet Kraftig violet
Neon Glow… Neonglød…
Palette Knife… Paletkniv…
Poster Edges… Plakatkanter…
Rough Pastels… Grove pasteller…
Cutout Udklip
Dry Brush Tør pensel
Neon Glow Neonglød
@@ -5194,6 +5195,7 @@ swatch Bright Violet Kraftig violet
Paint Daubs Malerklatter
Palette Knife Paletkniv
Poster Edges Plakatkanter
Rough Pastels Grove pasteller
Underpainting… Undermaling…
Underpainting Undermaling
Apply Data Set Anvend datasæt
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4654,7 +4654,9 @@ lighting Light Licht
Paint Daubs Tupfer
Palette Knife Palettenmesser
Poster Edges… Posterkanten…
Rough Pastels… Pastellkreide…
Poster Edges Posterkanten
Rough Pastels Pastellkreide
Underpainting… Untermalung…
Underpainting Untermalung
Levels Stufen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2277,7 +2277,9 @@ lighting Light Φως
Paint Daubs Πινελιές βαφής
Palette Knife Μαχαίρι παλέτας
Poster Edges… Άκρες αφίσας…
Rough Pastels… Χοντρά παστέλ…
Poster Edges Άκρες αφίσας
Rough Pastels Χοντρά παστέλ
Underpainting… Υποζωγραφία…
Underpainting Υποζωγραφία
Levels Επίπεδα
Can't render this file because it contains an unexpected character in line 2038 and column 19.
+2
View File
@@ -4655,7 +4655,9 @@ lighting Light Luz
Paint Daubs Pinceladas
Palette Knife Espátula
Poster Edges… Bordes de póster…
Rough Pastels… Pastel rudo…
Poster Edges Bordes de póster
Rough Pastels Pastel rudo
Underpainting… Subpintura…
Underpainting Subpintura
Levels Niveles
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4962,7 +4962,9 @@ imageTrace Outline Ääriviiva
Paint Daubs Maalitahrat
Palette Knife Palettiveitsi
Poster Edges… Julisteen reunat…
Rough Pastels… Karkeat pastellit…
Poster Edges Julisteen reunat
Rough Pastels Karkeat pastellit
Underpainting… Alapinta…
Underpainting Alapinta
Levels Tasot
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4665,7 +4665,9 @@ lighting Light Lumière
Paint Daubs Empâtements
Palette Knife Couteau à palette
Poster Edges… Bords d’affiche…
Rough Pastels… Pastels secs…
Poster Edges Bords d’affiche
Rough Pastels Pastels secs
Underpainting… Sous-peinture…
Underpainting Sous-peinture
Levels Niveaux
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -1598,7 +1598,9 @@ file-filter Templates Sablonok
Paint Daubs Festékpaca
Palette Knife Palettakés
Poster Edges… Poszterszélek…
Rough Pastels… Durva pasztell…
Poster Edges Poszterszélek
Rough Pastels Durva pasztell
Underpainting… Alapozás…
Underpainting Alapozás
Levels Szintek
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5187,6 +5187,7 @@ swatch Bright Violet Violet cerah
Neon Glow… Pendar neon…
Palette Knife… Pisau palet…
Poster Edges… Tepi poster…
Rough Pastels… Pastel kasar…
Cutout Potongan
Dry Brush Kuas kering
Neon Glow Pendar neon
@@ -5194,6 +5195,7 @@ swatch Bright Violet Violet cerah
Paint Daubs Coretan cat
Palette Knife Pisau palet
Poster Edges Tepi poster
Rough Pastels Pastel kasar
Underpainting… Underpainting…
Underpainting Underpainting
Apply Data Set Terapkan set data
Can't render this file because it contains an unexpected character in line 3808 and column 19.
+2
View File
@@ -4697,7 +4697,9 @@ lighting Light Luce
Paint Daubs Pennellate
Palette Knife Spatola
Poster Edges… Bordi poster…
Rough Pastels… Pastelli grezzi…
Poster Edges Bordi poster
Rough Pastels Pastelli grezzi
Underpainting… Sottopittura…
Underpainting Sottopittura
Levels Livelli
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4736,7 +4736,9 @@ lighting Light ライト
Paint Daubs ペイントダブ
Palette Knife パレットナイフ
Poster Edges… ポスターエッジ…
Rough Pastels… ラフパステル…
Poster Edges ポスターエッジ
Rough Pastels ラフパステル
Underpainting… アンダーペインティング…
Underpainting アンダーペインティング
Levels レベル数
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5096,9 +5096,11 @@ imageTrace Outline კონტური
Persistent მუდმივი
Place from Library ბიბლიოთეკიდან განთავსება
Poster Edges პოსტერის კიდეები
Rough Pastels მკვრივი პასტელი
Underpainting… ქვეფენის მხატვრობა…
Underpainting ქვეფენის მხატვრობა
Poster Edges… პოსტერის კიდეები…
Rough Pastels… მკვრივი პასტელი…
Posterization პოსტერიზაცია
Previous Artboard წინა სამუშაო დაფა
Rearrange Artboards სამუშაო დაფების გადალაგება
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4660,7 +4660,9 @@ lighting Light 광원
Paint Daubs 페인트 다브
Palette Knife 팔레트 나이프
Poster Edges… 포스터 가장자리…
Rough Pastels… 거친 파스텔…
Poster Edges 포스터 가장자리
Rough Pastels 거친 파스텔
Underpainting… 언더페인팅…
Underpainting 언더페인팅
Levels 레벨 수
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -5198,6 +5198,7 @@ swatch Bright Violet Felviolet
Neon Glow… Neongloed…
Palette Knife… Paletmes…
Poster Edges… Posterranden…
Rough Pastels… Ruwe pastels…
Cutout Knipsel
Dry Brush Droog penseel
Neon Glow Neongloed
@@ -5205,6 +5206,7 @@ swatch Bright Violet Felviolet
Paint Daubs Verfstipjes
Palette Knife Paletmes
Poster Edges Posterranden
Rough Pastels Ruwe pastels
Underpainting… Onderlaag…
Underpainting Onderlaag
Apply Data Set Gegevensset toepassen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4900,7 +4900,9 @@ imageTrace Outline Kontur
Paint Daubs Plamy farby
Palette Knife Szpachel
Poster Edges… Krawędzie plakatu…
Rough Pastels… Suche pastele…
Poster Edges Krawędzie plakatu
Rough Pastels Suche pastele
Underpainting… Podmalówka…
Underpainting Podmalówka
Levels Poziomy
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4050,7 +4050,9 @@ axis Both Ambos
Paint Daubs Manchas de tinta
Palette Knife Espátula
Poster Edges… Bordas de pôster…
Rough Pastels… Pastéis ásperos…
Poster Edges Bordas de pôster
Rough Pastels Pastéis ásperos
Underpainting… Subpintura…
Underpainting Subpintura
Levels Níveis
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2
View File
@@ -4693,7 +4693,9 @@ lighting Light Свет
Paint Daubs Мазки кисти
Palette Knife Нож для палитры
Poster Edges… Края плаката…
Rough Pastels… Грубые пастели…
Poster Edges Края плаката
Rough Pastels Грубые пастели
Underpainting… Подмалёвка…
Underpainting Подмалёвка
Levels Уровни
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4574,7 +4574,9 @@ lighting Light Ljus
Paint Daubs Färgklickar
Palette Knife Palettkniv
Poster Edges… Affischkanter…
Rough Pastels… Grova pasteller…
Poster Edges Affischkanter
Rough Pastels Grova pasteller
Underpainting… Undermålning…
Underpainting Undermålning
Levels Nivåer
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -4677,7 +4677,9 @@ lighting Light Світло
Paint Daubs Мазки фарби
Palette Knife Ніж палітри
Poster Edges… Плакатні краї…
Rough Pastels… Грубі пастелі…
Poster Edges Плакатні краї
Rough Pastels Грубі пастелі
Underpainting… Підмалювання…
Underpainting Підмалювання
Levels Рівні
Can't render this file because it contains an unexpected character in line 733 and column 40.
+2
View File
@@ -1279,7 +1279,9 @@
Paint Daubs Vệt sơn
Palette Knife Dao palette
Poster Edges… Viền áp phích…
Rough Pastels… Pastel thô…
Poster Edges Viền áp phích
Rough Pastels Pastel thô
Underpainting… Lớp sơn nền…
Underpainting Lớp sơn nền
Levels Số mức
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1279
1280
1281
1282
1283
1284
1285
1286
1287
+2
View File
@@ -4961,7 +4961,9 @@ lighting Light 光源
Paint Daubs 绘画涂抹
Palette Knife 调色刀
Poster Edges… 海报边缘…
Rough Pastels… 粗糙蜡笔…
Poster Edges 海报边缘
Rough Pastels 粗糙蜡笔
Underpainting… 底纹效果…
Underpainting 底纹效果
Levels 色阶数
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -4767,7 +4767,9 @@ lighting Light 光源
Paint Daubs 繪畫塗抹
Palette Knife 調色刀
Poster Edges… 海報邊緣…
Rough Pastels… 粗糙粉彩…
Poster Edges 海報邊緣
Rough Pastels 粗糙粉彩
Underpainting… 底紋效果…
Underpainting 底紋效果
Levels 色階數
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -97,6 +97,7 @@ fn effect_menu_lists_the_artistic_filters_before_blur() {
"Palette Knife…",
"Plastic Wrap…",
"Poster Edges…",
"Rough Pastels…",
"Smudge Stick…",
"Sponge…",
"Underpainting…",