Implement stable serialization and restoration for dock layouts, including auto-hidden and floating panels

- Add `persistence.rs` for stable serialization of dock layouts without runtime identifiers.
- Introduce `preview.rs` for geometry handling of dock drop previews.
- Create `sizing.rs` to manage content-aware sizing policies and constraint solving for dock panels.
- Implement `welcome.rs` to provide a welcome surface when no documents are open, featuring New and Open actions.
This commit is contained in:
2026-07-16 22:10:22 +03:00
parent 1240d25011
commit b09795ccff
72 changed files with 11898 additions and 3115 deletions
+221 -53
View File
@@ -1,18 +1,17 @@
use crate::color_overlay::generate_color_overlay;
use crate::drop_shadow::generate_shadow;
use crate::emboss::generate_bevel_emboss;
use crate::gradient_overlay::generate_gradient_overlay;
use crate::helpers::{composite_effect, composite_inside_effect, extract_alpha};
use crate::inner_shadow::generate_inner_shadow;
use crate::outer_glow::generate_glow;
use crate::satin::generate_satin;
use crate::stroke::generate_stroke;
/// Effect application orchestrator.
///
/// Applies all enabled layer effects to a layer's pixel buffer in standard
/// Photoshop order. Delegates to individual effect modules for rendering.
use crate::types::LayerEffect;
use crate::emboss::generate_bevel_emboss;
use crate::outer_glow::generate_glow;
use crate::color_overlay::generate_color_overlay;
use crate::satin::generate_satin;
use crate::gradient_overlay::generate_gradient_overlay;
use crate::stroke::generate_stroke;
use crate::drop_shadow::generate_shadow;
use crate::inner_shadow::generate_inner_shadow;
use crate::helpers::{extract_alpha, composite_effect, composite_inside_effect};
use hcie_blend::blend_pixels;
/// Apply all enabled layer effects to a layer's pixel buffer in standard Photoshop order.
@@ -34,7 +33,9 @@ pub fn apply_layer_effects(
} else {
let mut out = pixels.to_vec();
for i in 0..px_count {
out[i * 4 + 3] = (out[i * 4 + 3] as f32 * fill_opacity).round().clamp(0.0, 255.0) as u8;
out[i * 4 + 3] = (out[i * 4 + 3] as f32 * fill_opacity)
.round()
.clamp(0.0, 255.0) as u8;
}
return out;
}
@@ -55,31 +56,62 @@ pub fn apply_layer_effects(
inside.copy_from_slice(pixels);
if fill_opacity < 1.0 {
for i in 0..px_count {
inside[i * 4 + 3] = (inside[i * 4 + 3] as f32 * fill_opacity).round().clamp(0.0, 255.0) as u8;
inside[i * 4 + 3] = (inside[i * 4 + 3] as f32 * fill_opacity)
.round()
.clamp(0.0, 255.0) as u8;
}
}
// Inside effects — Photoshop spec order:
// a. Bevel & Emboss
if let Some(LayerEffect::BevelEmboss {
enabled: true, style, technique, depth, direction, size, soften,
angle, altitude, highlight_blend, highlight_color, highlight_opacity,
shadow_blend, shadow_color, shadow_opacity, contour, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::BevelEmboss { .. })) {
enabled: true,
style,
technique,
depth,
direction,
size,
soften,
angle,
altitude,
highlight_blend,
highlight_color,
highlight_opacity,
shadow_blend,
shadow_color,
shadow_opacity,
contour,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::BevelEmboss { .. }))
{
// Emboss is rendered on top of the original layer fill; do not replace
// it with a hard-coded grey. Photoshop keeps the original pixel color
// and applies highlight/shadow blends over it.
let (highlight, shadow) = generate_bevel_emboss(
&alpha, canvas_w, canvas_h,
*depth, *size, *soften, *angle, *altitude,
*direction, *technique, *style,
*highlight_color, *shadow_color,
&alpha,
canvas_w,
canvas_h,
*depth,
*size,
*soften,
*angle,
*altitude,
*direction,
*technique,
*style,
*highlight_color,
*shadow_color,
contour,
);
// For Emboss and OuterBevel, the effect extends outside the shape.
// Inside pixels go to `inside`, outside pixels go to `behind`.
let has_outer = matches!(*style, crate::types::BevelStyle::Emboss | crate::types::BevelStyle::OuterBevel);
let has_outer = matches!(
*style,
crate::types::BevelStyle::Emboss | crate::types::BevelStyle::OuterBevel
);
if has_outer {
// Split highlight/shadow into inside and outside portions
let mut hl_inside = vec![0u8; px_count * 4];
@@ -95,8 +127,20 @@ pub fn apply_layer_effects(
sh_outside[i * 4..i * 4 + 4].copy_from_slice(&shadow[i * 4..i * 4 + 4]);
}
}
composite_effect(&mut inside, &hl_inside, *highlight_blend, *highlight_opacity, px_count);
composite_effect(&mut inside, &sh_inside, *shadow_blend, *shadow_opacity, px_count);
composite_effect(
&mut inside,
&hl_inside,
*highlight_blend,
*highlight_opacity,
px_count,
);
composite_effect(
&mut inside,
&sh_inside,
*shadow_blend,
*shadow_opacity,
px_count,
);
// Store outside portions to apply to `behind` later
bevel_hl_outside = Some(hl_outside);
bevel_sh_outside = Some(sh_outside);
@@ -105,65 +149,146 @@ pub fn apply_layer_effects(
bevel_hl_op = *highlight_opacity;
bevel_sh_op = *shadow_opacity;
} else {
composite_effect(&mut inside, &highlight, *highlight_blend, *highlight_opacity, px_count);
composite_effect(&mut inside, &shadow, *shadow_blend, *shadow_opacity, px_count);
composite_effect(
&mut inside,
&highlight,
*highlight_blend,
*highlight_opacity,
px_count,
);
composite_effect(
&mut inside,
&shadow,
*shadow_blend,
*shadow_opacity,
px_count,
);
}
}
// b. Satin
if let Some(LayerEffect::Satin {
enabled: true, blend_mode, color, opacity, angle, distance, size, invert, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::Satin { .. })) {
let satin = generate_satin(&alpha, canvas_w, canvas_h, *angle, *distance, *size, *color, *invert);
enabled: true,
blend_mode,
color,
opacity,
angle,
distance,
size,
invert,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::Satin { .. }))
{
let satin = generate_satin(
&alpha, canvas_w, canvas_h, *angle, *distance, *size, *color, *invert,
);
composite_effect(&mut inside, &satin, *blend_mode, *opacity, px_count);
}
// c. Color Overlay
if let Some(LayerEffect::ColorOverlay {
enabled: true, blend_mode, color, opacity, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::ColorOverlay { .. })) {
enabled: true,
blend_mode,
color,
opacity,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::ColorOverlay { .. }))
{
let overlay = generate_color_overlay(&alpha, canvas_w, canvas_h, *color);
composite_inside_effect(&mut inside, &overlay, *blend_mode, *opacity, px_count);
}
// d. Gradient Overlay
if let Some(LayerEffect::GradientOverlay {
enabled: true, blend_mode, opacity, angle, scale, gradient, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::GradientOverlay { .. })) {
let overlay = generate_gradient_overlay(&alpha, canvas_w, canvas_h, *angle, *scale, gradient);
enabled: true,
blend_mode,
opacity,
angle,
scale,
gradient,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::GradientOverlay { .. }))
{
let overlay =
generate_gradient_overlay(&alpha, canvas_w, canvas_h, *angle, *scale, gradient);
composite_inside_effect(&mut inside, &overlay, *blend_mode, *opacity, px_count);
}
// e. Pattern Overlay
if let Some(LayerEffect::PatternOverlay {
enabled: true, blend_mode, opacity, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::PatternOverlay { .. })) {
enabled: true,
blend_mode,
opacity,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::PatternOverlay { .. }))
{
let overlay = generate_color_overlay(&alpha, canvas_w, canvas_h, [128, 128, 128, 255]);
composite_inside_effect(&mut inside, &overlay, *blend_mode, *opacity, px_count);
}
// f. Inner Glow
if let Some(LayerEffect::InnerGlow {
enabled: true, blend_mode, color, opacity, choke, size, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::InnerGlow { .. })) {
enabled: true,
blend_mode,
color,
opacity,
choke,
size,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::InnerGlow { .. }))
{
let glow = generate_glow(&alpha, canvas_w, canvas_h, *choke, *size, *color, true);
composite_effect(&mut inside, &glow, *blend_mode, *opacity, px_count);
}
// g. Inner Shadow
if let Some(LayerEffect::InnerShadow {
enabled: true, blend_mode, color, opacity, angle, distance, choke, size, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::InnerShadow { .. })) {
let shadow = generate_inner_shadow(&alpha, canvas_w, canvas_h, *angle, *distance, *choke, *size, *color);
enabled: true,
blend_mode,
color,
opacity,
angle,
distance,
choke,
size,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::InnerShadow { .. }))
{
let shadow = generate_inner_shadow(
&alpha, canvas_w, canvas_h, *angle, *distance, *choke, *size, *color,
);
composite_effect(&mut inside, &shadow, *blend_mode, *opacity, px_count);
}
// h. Stroke (Inside and Center — Outside handled in behind phase)
if let Some(LayerEffect::Stroke {
enabled: true, position, blend_mode, opacity, size, color, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::Stroke { .. })) {
enabled: true,
position,
blend_mode,
opacity,
size,
color,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::Stroke { .. }))
{
if !matches!(position, crate::types::StrokePosition::Outside) {
let stroke = generate_stroke(&alpha, canvas_w, canvas_h, *size, *position, *color, *opacity);
let stroke = generate_stroke(
&alpha, canvas_w, canvas_h, *size, *position, *color, *opacity,
);
composite_inside_effect(&mut inside, &stroke, *blend_mode, 1.0, px_count);
}
}
@@ -174,26 +299,59 @@ pub fn apply_layer_effects(
// Behind effects — Photoshop spec order:
// a. Drop Shadow
if let Some(LayerEffect::DropShadow {
enabled: true, blend_mode, color, opacity, angle, distance, spread, size, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::DropShadow { .. })) {
let shadow = generate_shadow(&alpha, canvas_w, canvas_h, *angle, *distance, *spread, *size, *color, false);
enabled: true,
blend_mode,
color,
opacity,
angle,
distance,
spread,
size,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::DropShadow { .. }))
{
let shadow = generate_shadow(
&alpha, canvas_w, canvas_h, *angle, *distance, *spread, *size, *color, false,
);
composite_effect(&mut behind, &shadow, *blend_mode, *opacity, px_count);
}
// b. Outer Glow
if let Some(LayerEffect::OuterGlow {
enabled: true, blend_mode, color, opacity, spread, size, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::OuterGlow { .. })) {
enabled: true,
blend_mode,
color,
opacity,
spread,
size,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::OuterGlow { .. }))
{
let glow = generate_glow(&alpha, canvas_w, canvas_h, *spread, *size, *color, false);
composite_effect(&mut behind, &glow, *blend_mode, *opacity, px_count);
}
// c. Outside Stroke
if let Some(LayerEffect::Stroke {
enabled: true, position, blend_mode, opacity, size, color, ..
}) = effects.iter().find(|e| matches!(e, LayerEffect::Stroke { .. })) {
enabled: true,
position,
blend_mode,
opacity,
size,
color,
..
}) = effects
.iter()
.find(|e| matches!(e, LayerEffect::Stroke { .. }))
{
if matches!(position, crate::types::StrokePosition::Outside) {
let stroke = generate_stroke(&alpha, canvas_w, canvas_h, *size, *position, *color, *opacity);
let stroke = generate_stroke(
&alpha, canvas_w, canvas_h, *size, *position, *color, *opacity,
);
composite_effect(&mut behind, &stroke, *blend_mode, 1.0, px_count);
}
}
@@ -209,8 +367,18 @@ pub fn apply_layer_effects(
// 3. Blend inside ON TOP of behind using Normal blend
for i in 0..px_count {
let b = [behind[i * 4], behind[i * 4 + 1], behind[i * 4 + 2], behind[i * 4 + 3]];
let ins = [inside[i * 4], inside[i * 4 + 1], inside[i * 4 + 2], inside[i * 4 + 3]];
let b = [
behind[i * 4],
behind[i * 4 + 1],
behind[i * 4 + 2],
behind[i * 4 + 3],
];
let ins = [
inside[i * 4],
inside[i * 4 + 1],
inside[i * 4 + 2],
inside[i * 4 + 3],
];
if ins[3] == 0 {
behind[i * 4..i * 4 + 4].copy_from_slice(&b);
} else {
+2 -7
View File
@@ -1,9 +1,4 @@
pub fn generate_color_overlay(
alpha: &[u8],
w: u32,
h: u32,
color: [u8; 4],
) -> Vec<u8> {
pub fn generate_color_overlay(alpha: &[u8], w: u32, h: u32, color: [u8; 4]) -> Vec<u8> {
let px = (w * h) as usize;
let mut buf = vec![0u8; px * 4];
@@ -18,4 +13,4 @@ pub fn generate_color_overlay(
}
buf
}
}
+3 -2
View File
@@ -2,7 +2,6 @@
///
/// Renders a drop shadow by offsetting the layer alpha, applying spread,
/// blurring, and filling with the shadow color. Supports knock-out mode.
use crate::helpers::{apply_spread, box_blur_f32};
/// Generate a drop shadow buffer.
@@ -56,7 +55,9 @@ pub(crate) fn generate_shadow(
for y in 0..h as i32 {
for x in 0..w as i32 {
let a = alpha[(y as u32 * w + x as u32) as usize];
if a == 0 { continue; }
if a == 0 {
continue;
}
let dx = x + dx_raw;
let dy = y + dy_raw;
if dx >= 0 && dx < w as i32 && dy >= 0 && dy < h as i32 {
+81 -25
View File
@@ -1,5 +1,5 @@
use crate::types;
use crate::helpers::box_blur_f32;
use crate::types;
/// Look up a value in a contour curve using linear interpolation.
/// input is in [0.0, 1.0], output is remapped through the curve.
@@ -96,7 +96,10 @@ pub fn generate_bevel_emboss(
// the outer bevel band can inherit the alpha of the nearest content pixel.
// This is critical: without it, alpha[i]==0 outside the shape produces
// zero-strength highlight/shadow, making Emboss/OuterBevel invisible.
let outside_src = if matches!(style, types::BevelStyle::Emboss | types::BevelStyle::OuterBevel) {
let outside_src = if matches!(
style,
types::BevelStyle::Emboss | types::BevelStyle::OuterBevel
) {
edt_outside_src(alpha, iw, ih)
} else {
vec![0usize; px]
@@ -112,10 +115,18 @@ pub fn generate_bevel_emboss(
if alpha[i] > 0 {
let x = i % iw;
let y = i / iw;
if x < min_x { min_x = x; }
if x > max_x { max_x = x; }
if y < min_y { min_y = y; }
if y > max_y { max_y = y; }
if x < min_x {
min_x = x;
}
if x > max_x {
max_x = x;
}
if y < min_y {
min_y = y;
}
if y > max_y {
max_y = y;
}
}
}
((min_x + max_x) as f32 / 2.0, (min_y + max_y) as f32 / 2.0)
@@ -180,7 +191,11 @@ pub fn generate_bevel_emboss(
// Apply soften box-blur on the height field.
// Even at soften=0, apply minimal 2px blur for smoother gradient estimation at edges.
// Emboss benefits from more blur to reduce wrinkled edges on large shapes.
let base_blur = if matches!(style, types::BevelStyle::Emboss) { 2 } else { 1 };
let base_blur = if matches!(style, types::BevelStyle::Emboss) {
2
} else {
1
};
let soften_radius = (soften.max(0.0).round() as i32).max(base_blur);
height = box_blur_f32(&height, w, h, soften_radius);
@@ -200,20 +215,32 @@ pub fn generate_bevel_emboss(
types::BevelStyle::Emboss => true,
_ => alpha[i] > 0,
};
if !show { continue; }
if !show {
continue;
}
let is_inner = alpha[i] > 0;
let dist_abs = if is_inner { dist_inside_sq[i].sqrt() } else { dist_outside_sq[i].sqrt() };
let dist_abs = if is_inner {
dist_inside_sq[i].sqrt()
} else {
dist_outside_sq[i].sqrt()
};
if !is_inner && dist_abs > radius { continue; }
if !is_inner && dist_abs > radius {
continue;
}
let technique_show = match technique {
types::Technique::Smooth => true,
_ => dist_abs <= radius,
};
if !technique_show { continue; }
if !technique_show {
continue;
}
if is_inner && dist_abs > radius { continue; }
if is_inner && dist_abs > radius {
continue;
}
let x0 = if x > 0 { x - 1 } else { x };
let x1 = if x < iw - 1 { x + 1 } else { x };
@@ -250,7 +277,11 @@ pub fn generate_bevel_emboss(
// Inner/Outer bevel need blur for smooth corners.
// MAE-optimized against Photoshop ground truth (288 emboss samples, 256×256).
// BEGIN_TUNING_ZONE
let lighting_blur = if matches!(style, crate::types::BevelStyle::Emboss) { 0 } else { 5 };
let lighting_blur = if matches!(style, crate::types::BevelStyle::Emboss) {
0
} else {
5
};
lighting = box_blur_f32(&lighting, w, h, lighting_blur);
for i in 0..px {
@@ -261,20 +292,31 @@ pub fn generate_bevel_emboss(
crate::types::BevelStyle::Emboss => true,
_ => is_inner,
};
if !show { continue; }
let dist_abs = if is_inner { dist_inside_sq[i].sqrt() } else { dist_outside_sq[i].sqrt() };
if !is_inner && dist_abs > radius { continue; }
if !show {
continue;
}
let dist_abs = if is_inner {
dist_inside_sq[i].sqrt()
} else {
dist_outside_sq[i].sqrt()
};
if !is_inner && dist_abs > radius {
continue;
}
let technique_show = match technique {
crate::types::Technique::Smooth => true,
_ => dist_abs <= radius,
};
if !technique_show { continue; }
if !technique_show {
continue;
}
let ndl = lighting[i];
// Emboss has tilt so the interior is never truly flat — skip the
// is_flat shortcut for emboss to allow tilt-based highlight/shadow
// across the entire shape interior.
let is_flat = is_inner && dist_abs > radius && !matches!(style, crate::types::BevelStyle::Emboss);
let is_flat =
is_inner && dist_abs > radius && !matches!(style, crate::types::BevelStyle::Emboss);
if is_flat {
continue;
}
@@ -417,7 +459,9 @@ fn edt_2d(dt: &mut [f32], w: usize, h: usize) {
/// 1D EDT (Felzenszwalb & Huttenlocher).
fn edt_1d(f: &mut [f32]) {
let n = f.len();
if n == 0 { return; }
if n == 0 {
return;
}
let mut d = vec![0.0f32; n];
let mut v = vec![0usize; n];
@@ -437,7 +481,10 @@ fn edt_1d(f: &mut [f32]) {
let mut s = (fq - fv_vk) / (2.0 * qf - 2.0 * vk as f32);
while s <= z[k] {
k = k.wrapping_sub(1);
if k == usize::MAX { k = 0; break; }
if k == usize::MAX {
k = 0;
break;
}
let vk2 = v[k];
let fv_vk2 = f[vk2] + vk2 as f32 * vk2 as f32;
s = (fq - fv_vk2) / (2.0 * qf - 2.0 * vk2 as f32);
@@ -451,7 +498,9 @@ fn edt_1d(f: &mut [f32]) {
k = 0;
for q in 0..n {
let qf = q as f32;
while z[k + 1] < qf { k += 1; }
while z[k + 1] < qf {
k += 1;
}
let vk = v[k];
let diff = qf - vk as f32;
d[q] = diff * diff + f[vk];
@@ -494,7 +543,9 @@ fn edt_2d_src(dt: &mut [f32], src_idx: &mut [usize], w: usize, h: usize) {
/// 1D EDT with source index tracking (Felzenszwalb & Huttenlocher).
fn edt_1d_src(f: &mut [f32], src_idx: &mut [usize]) {
let n = f.len();
if n == 0 { return; }
if n == 0 {
return;
}
let mut d = vec![0.0f32; n];
let mut d_src = vec![0usize; n];
@@ -515,7 +566,10 @@ fn edt_1d_src(f: &mut [f32], src_idx: &mut [usize]) {
let mut s = (fq - fv_vk) / (2.0 * qf - 2.0 * vk as f32);
while s <= z[k] {
k = k.wrapping_sub(1);
if k == usize::MAX { k = 0; break; }
if k == usize::MAX {
k = 0;
break;
}
let vk2 = v[k];
let fv_vk2 = f[vk2] + vk2 as f32 * vk2 as f32;
s = (fq - fv_vk2) / (2.0 * qf - 2.0 * vk2 as f32);
@@ -529,7 +583,9 @@ fn edt_1d_src(f: &mut [f32], src_idx: &mut [usize]) {
k = 0;
for q in 0..n {
let qf = q as f32;
while z[k + 1] < qf { k += 1; }
while z[k + 1] < qf {
k += 1;
}
let vk = v[k];
let diff = qf - vk as f32;
d[q] = diff * diff + f[vk];
@@ -538,4 +594,4 @@ fn edt_1d_src(f: &mut [f32], src_idx: &mut [usize]) {
f.copy_from_slice(&d);
src_idx.copy_from_slice(&d_src);
}
}
+4 -2
View File
@@ -22,7 +22,9 @@ pub fn generate_gradient_overlay(
for x in 0..w as usize {
let i = y * w as usize + x;
let a = alpha[i];
if a == 0 { continue; }
if a == 0 {
continue;
}
let fx = x as f32 - cx;
let fy = y as f32 - cy;
@@ -89,4 +91,4 @@ fn sample_gradient(t: f32, gradient: &crate::types::GradientDef) -> [u8; 4] {
}
stops[stops.len() - 1].1
}
}
+23 -7
View File
@@ -40,8 +40,15 @@ pub(crate) fn composite_effect(
) {
for i in 0..px_count {
let dst_px = [dst[i * 4], dst[i * 4 + 1], dst[i * 4 + 2], dst[i * 4 + 3]];
let src_px = [effect_buf[i * 4], effect_buf[i * 4 + 1], effect_buf[i * 4 + 2], effect_buf[i * 4 + 3]];
if src_px[3] == 0 { continue; }
let src_px = [
effect_buf[i * 4],
effect_buf[i * 4 + 1],
effect_buf[i * 4 + 2],
effect_buf[i * 4 + 3],
];
if src_px[3] == 0 {
continue;
}
let out = hcie_blend::blend_pixels(dst_px, src_px, blend_mode, opacity);
dst[i * 4..i * 4 + 4].copy_from_slice(&out);
}
@@ -68,11 +75,18 @@ pub(crate) fn composite_inside_effect(
) {
for i in 0..px_count {
let dst_px = [dst[i * 4], dst[i * 4 + 1], dst[i * 4 + 2], dst[i * 4 + 3]];
let src_px = [effect_buf[i * 4], effect_buf[i * 4 + 1], effect_buf[i * 4 + 2], effect_buf[i * 4 + 3]];
if src_px[3] == 0 || dst_px[3] == 0 { continue; }
let src_px = [
effect_buf[i * 4],
effect_buf[i * 4 + 1],
effect_buf[i * 4 + 2],
effect_buf[i * 4 + 3],
];
if src_px[3] == 0 || dst_px[3] == 0 {
continue;
}
let eff_sa = (src_px[3] as f32 / 255.0) * opacity;
if blend_mode == hcie_blend::BlendMode::Normal {
if eff_sa >= 1.0 {
dst[i * 4] = src_px[0];
@@ -110,7 +124,9 @@ pub(crate) fn apply_spread(alpha: &mut [u8], spread: f32) {
}
if spread >= 100.0 {
for a in alpha.iter_mut() {
if *a > 0 { *a = 255; }
if *a > 0 {
*a = 255;
}
}
return;
}
+1 -2
View File
@@ -3,7 +3,6 @@
/// Renders an inner shadow by offsetting the inverted layer alpha,
/// blurring, and masking with the original alpha. MAE-optimized
/// parameters for Photoshop-compatible output.
use crate::helpers::{apply_spread, box_blur_f32};
/// Generate an inner shadow buffer.
@@ -54,7 +53,7 @@ pub(crate) fn generate_inner_shadow(
for x in 0..w as i32 {
let orig_i = (y as u32 * w + x as u32) as usize;
let val = 255 - alpha[orig_i];
let dx = x + dx_raw;
let dy = y + dy_raw;
if dx >= 0 && dx < w as i32 && dy >= 0 && dy < h as i32 {
+14 -11
View File
@@ -25,22 +25,25 @@
//! ## Dependencies
//! - `hcie-blend` — Blend mode math for compositing effects with layers
pub mod types;
pub mod parser;
pub mod helpers;
pub mod apply_effects;
pub mod drop_shadow;
pub mod inner_shadow;
pub mod emboss;
pub mod outer_glow;
pub mod color_overlay;
pub mod satin;
pub mod drop_shadow;
pub mod emboss;
pub mod gradient_overlay;
pub mod helpers;
pub mod inner_shadow;
pub mod outer_glow;
pub mod parser;
pub mod satin;
pub mod stroke;
pub mod tuned;
pub mod types;
pub use types::LayerEffect;
pub use types::{blend_mode_to_string, blend_mode_from_string, protocol_to_hcie_fx_effect, hcie_fx_effect_to_protocol, layer_style_to_effect};
pub use parser::{parse_lfx2, parse_lrFX, parse_asl, parse_asl_styles};
pub use apply_effects::apply_layer_effects;
pub use helpers::box_blur_f32;
pub use parser::{parse_asl, parse_asl_styles, parse_lfx2, parse_lrFX};
pub use types::LayerEffect;
pub use types::{
blend_mode_from_string, blend_mode_to_string, hcie_fx_effect_to_protocol,
layer_style_to_effect, protocol_to_hcie_fx_effect,
};
+1 -1
View File
@@ -73,4 +73,4 @@ pub fn generate_glow(
}
}
buf
}
}
+534 -168
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -74,4 +74,4 @@ pub fn generate_satin(
}
buf
}
}
+14 -6
View File
@@ -29,9 +29,13 @@ pub fn generate_stroke(
crate::types::StrokePosition::Inside => {
let dist_sq = crate::tuned::edt_inside(alpha, iw, ih);
for i in 0..px {
if alpha[i] == 0 { continue; }
if alpha[i] == 0 {
continue;
}
let d = dist_sq[i].sqrt();
if d >= radius { continue; }
if d >= radius {
continue;
}
let outer_dist = radius - d;
let coverage = (outer_dist / aa).clamp(0.0, 1.0);
stroke_alpha[i] = coverage * alpha[i] as f32;
@@ -42,7 +46,9 @@ pub fn generate_stroke(
let nearest = crate::tuned::edt_outside_src(alpha, iw, ih);
for i in 0..px {
let d = dist_sq[i].sqrt();
if d == 0.0 || d >= radius { continue; }
if d == 0.0 || d >= radius {
continue;
}
let x_i = (i % iw) as f32;
let y_i = (i / iw) as f32;
let x_n = (nearest[i] % iw) as f32;
@@ -76,8 +82,10 @@ pub fn generate_stroke(
} else {
(dist_out_sq[i].sqrt(), false)
};
if d == 0.0 || d >= half_r { continue; }
if d == 0.0 || d >= half_r {
continue;
}
let src_a = if is_inside {
alpha[i] as f32
} else {
@@ -123,4 +131,4 @@ pub fn generate_stroke(
}
buf
}
}
+109 -35
View File
@@ -36,7 +36,10 @@ pub fn generate_bevel_tuned(
// For Emboss and OuterBevel, track the nearest opaque source pixel so
// the outer bevel band can inherit the alpha of the nearest content pixel.
let outside_src = if matches!(*style, types::BevelStyle::Emboss | types::BevelStyle::OuterBevel) {
let outside_src = if matches!(
*style,
types::BevelStyle::Emboss | types::BevelStyle::OuterBevel
) {
edt_outside_src(alpha, iw, ih)
} else {
vec![0usize; px]
@@ -52,10 +55,18 @@ pub fn generate_bevel_tuned(
if alpha[i] > 0 {
let x = i % iw;
let y = i / iw;
if x < min_x { min_x = x; }
if x > max_x { max_x = x; }
if y < min_y { min_y = y; }
if y > max_y { max_y = y; }
if x < min_x {
min_x = x;
}
if x > max_x {
max_x = x;
}
if y < min_y {
min_y = y;
}
if y > max_y {
max_y = y;
}
}
}
((min_x + max_x) as f32 / 2.0, (min_y + max_y) as f32 / 2.0)
@@ -134,11 +145,19 @@ pub fn generate_bevel_tuned(
types::BevelStyle::Emboss => true,
_ => alpha[i] > 0,
};
if !show { continue; }
if !show {
continue;
}
let is_inner = alpha[i] > 0;
let dist_abs = if is_inner { dist_inside_sq[i].sqrt() } else { dist_outside_sq[i].sqrt() };
if !is_inner && dist_abs > radius { continue; }
let x0 = if x > 0 { x - 1 } else { x };
let dist_abs = if is_inner {
dist_inside_sq[i].sqrt()
} else {
dist_outside_sq[i].sqrt()
};
if !is_inner && dist_abs > radius {
continue;
}
let x0 = if x > 0 { x - 1 } else { x };
let x1 = if x < iw - 1 { x + 1 } else { x };
let y0 = if y > 0 { y - 1 } else { y };
let y1 = if y < ih - 1 { y + 1 } else { y };
@@ -177,9 +196,17 @@ pub fn generate_bevel_tuned(
types::BevelStyle::Emboss => true,
_ => is_inner,
};
if !show { continue; }
let dist_abs = if is_inner { dist_inside_sq[i].sqrt() } else { dist_outside_sq[i].sqrt() };
if !is_inner && dist_abs > radius { continue; }
if !show {
continue;
}
let dist_abs = if is_inner {
dist_inside_sq[i].sqrt()
} else {
dist_outside_sq[i].sqrt()
};
if !is_inner && dist_abs > radius {
continue;
}
let ndl = lighting[i];
// Emboss has tilt so the interior is never truly flat — skip the
// is_flat shortcut for emboss to allow tilt-based highlight/shadow
@@ -325,7 +352,9 @@ pub fn generate_shadow_tuned(
for y in 0..h as i32 {
for x in 0..w as i32 {
let a = alpha[(y as u32 * w + x as u32) as usize];
if a == 0 { continue; }
if a == 0 {
continue;
}
let dx = x + dx_raw;
let dy = y + dy_raw;
if dx >= 0 && dx < w as i32 && dy >= 0 && dy < h as i32 {
@@ -587,9 +616,13 @@ pub fn generate_stroke_tuned(
types::StrokePosition::Inside => {
let dist_sq = edt_inside(alpha, iw, ih);
for i in 0..px {
if alpha[i] == 0 { continue; }
if alpha[i] == 0 {
continue;
}
let d = dist_sq[i].sqrt();
if d >= radius { continue; }
if d >= radius {
continue;
}
let outer_dist = radius - d; // distance to outer stroke edge
let coverage = (outer_dist / aa).clamp(0.0, 1.0);
stroke_alpha[i] = coverage * alpha[i] as f32;
@@ -600,7 +633,9 @@ pub fn generate_stroke_tuned(
let nearest = edt_outside_src(alpha, iw, ih);
for i in 0..px {
let d = dist_sq[i].sqrt();
if d == 0.0 || d >= radius { continue; }
if d == 0.0 || d >= radius {
continue;
}
let src_a = alpha[nearest[i]] as f32;
let outer_dist = radius - d; // distance to outer stroke edge
let coverage = (outer_dist / aa).clamp(0.0, 1.0);
@@ -618,7 +653,9 @@ pub fn generate_stroke_tuned(
} else {
(dist_out_sq[i].sqrt(), alpha[nearest[i]] as f32)
};
if d == 0.0 || d >= half_r { continue; }
if d == 0.0 || d >= half_r {
continue;
}
let outer_dist = half_r - d; // distance to outer stroke edge
let coverage = (outer_dist / aa).clamp(0.0, 1.0);
stroke_alpha[i] = coverage * base_a;
@@ -653,7 +690,9 @@ pub fn edt_inside(alpha: &[u8], w: usize, h: usize) -> Vec<f32> {
for y in 0..h {
for x in 0..w {
let i = y * w + x;
if alpha[i] == 0 { dt[i] = 0.0; }
if alpha[i] == 0 {
dt[i] = 0.0;
}
}
}
edt_2d(&mut dt, w, h);
@@ -670,7 +709,9 @@ pub fn edt_outside(alpha: &[u8], w: usize, h: usize) -> Vec<f32> {
for y in 0..h {
for x in 0..w {
let i = y * w + x;
if alpha[i] > 0 { dt[i] = 0.0; }
if alpha[i] > 0 {
dt[i] = 0.0;
}
}
}
edt_2d(&mut dt, w, h);
@@ -696,7 +737,9 @@ pub fn edt_outside_src(alpha: &[u8], w: usize, h: usize) -> Vec<usize> {
fn edt_1d_src(f: &mut [f32], src_idx: &mut [usize]) {
let n = f.len();
if n == 0 { return; }
if n == 0 {
return;
}
let mut d = vec![0.0f32; n];
let mut d_src = vec![0usize; n];
let mut v = vec![0usize; n];
@@ -713,7 +756,10 @@ fn edt_1d_src(f: &mut [f32], src_idx: &mut [usize]) {
let mut s = (fq - fv_vk) / (2.0 * qf - 2.0 * vk as f32);
while s <= z[k] {
k = k.wrapping_sub(1);
if k == usize::MAX { k = 0; break; }
if k == usize::MAX {
k = 0;
break;
}
let vk2 = v[k];
let fv_vk2 = f[vk2] + vk2 as f32 * vk2 as f32;
s = (fq - fv_vk2) / (2.0 * qf - 2.0 * vk2 as f32);
@@ -726,7 +772,9 @@ fn edt_1d_src(f: &mut [f32], src_idx: &mut [usize]) {
k = 0;
for q in 0..n {
let qf = q as f32;
while z[k + 1] < qf { k += 1; }
while z[k + 1] < qf {
k += 1;
}
let vk = v[k];
let diff = qf - vk as f32;
d[q] = diff * diff + f[vk];
@@ -740,39 +788,60 @@ fn edt_2d_src(dt: &mut [f32], src_idx: &mut [usize], w: usize, h: usize) {
let mut col = vec![0.0f32; h];
let mut col_src = vec![0usize; h];
for x in 0..w {
for y in 0..h { col[y] = dt[y * w + x]; col_src[y] = src_idx[y * w + x]; }
for y in 0..h {
col[y] = dt[y * w + x];
col_src[y] = src_idx[y * w + x];
}
edt_1d_src(&mut col, &mut col_src);
for y in 0..h { dt[y * w + x] = col[y]; src_idx[y * w + x] = col_src[y]; }
for y in 0..h {
dt[y * w + x] = col[y];
src_idx[y * w + x] = col_src[y];
}
}
let mut row = vec![0.0f32; w];
let mut row_src = vec![0usize; w];
for y in 0..h {
for x in 0..w { row[x] = dt[y * w + x]; row_src[x] = src_idx[y * w + x]; }
for x in 0..w {
row[x] = dt[y * w + x];
row_src[x] = src_idx[y * w + x];
}
edt_1d_src(&mut row, &mut row_src);
for x in 0..w { dt[y * w + x] = row[x]; src_idx[y * w + x] = row_src[x]; }
for x in 0..w {
dt[y * w + x] = row[x];
src_idx[y * w + x] = row_src[x];
}
}
}
fn edt_2d(dt: &mut [f32], w: usize, h: usize) {
let mut col = vec![0.0f32; h];
for x in 0..w {
for y in 0..h { col[y] = dt[y * w + x]; }
for y in 0..h {
col[y] = dt[y * w + x];
}
edt_1d(&mut col);
for y in 0..h { dt[y * w + x] = col[y]; }
for y in 0..h {
dt[y * w + x] = col[y];
}
}
let mut row = vec![0.0f32; w];
for y in 0..h {
for x in 0..w { row[x] = dt[y * w + x]; }
for x in 0..w {
row[x] = dt[y * w + x];
}
edt_1d(&mut row);
for x in 0..w { dt[y * w + x] = row[x]; }
for x in 0..w {
dt[y * w + x] = row[x];
}
}
}
fn edt_1d(f: &mut [f32]) {
let n = f.len();
if n == 0 { return; }
if n == 0 {
return;
}
let mut d = vec![0.0f32; n];
let mut v = vec![0usize; n];
let mut z = vec![0.0f32; n + 1];
@@ -788,7 +857,10 @@ fn edt_1d(f: &mut [f32]) {
let mut s = (fq - fv_vk) / (2.0 * qf - 2.0 * vk as f32);
while s <= z[k] {
k = k.wrapping_sub(1);
if k == usize::MAX { k = 0; break; }
if k == usize::MAX {
k = 0;
break;
}
let vk2 = v[k];
let fv_vk2 = f[vk2] + vk2 as f32 * vk2 as f32;
s = (fq - fv_vk2) / (2.0 * qf - 2.0 * vk2 as f32);
@@ -801,10 +873,12 @@ fn edt_1d(f: &mut [f32]) {
k = 0;
for q in 0..n {
let qf = q as f32;
while z[k + 1] < qf { k += 1; }
while z[k + 1] < qf {
k += 1;
}
let vk = v[k];
let diff = qf - vk as f32;
d[q] = diff * diff + f[vk];
}
f.copy_from_slice(&d);
}
}
+664 -194
View File
@@ -2,19 +2,40 @@ use hcie_blend::BlendMode;
use hcie_protocol::effects;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BevelStyle { InnerBevel, OuterBevel, Emboss, PillowEmboss, StrokeEmboss }
pub enum BevelStyle {
InnerBevel,
OuterBevel,
Emboss,
PillowEmboss,
StrokeEmboss,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Technique { Smooth, ChiselHard, ChiselSoft }
pub enum Technique {
Smooth,
ChiselHard,
ChiselSoft,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction { Up, Down }
pub enum Direction {
Up,
Down,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StrokePosition { Inside, Center, Outside }
pub enum StrokePosition {
Inside,
Center,
Outside,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StrokeFillType { Color, Gradient, Pattern }
pub enum StrokeFillType {
Color,
Gradient,
Pattern,
}
#[derive(Debug, Clone)]
pub struct ContourCurve {
@@ -25,20 +46,74 @@ pub struct ContourCurve {
pub fn contour_name_to_curve(name: &str) -> Option<ContourCurve> {
match name {
"Linear" => None, // Linear is the default (no remapping needed)
"Cone" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)] }),
"Cone-Inverted" => Some(ContourCurve { points: vec![(0.0, 1.0), (0.5, 0.0), (1.0, 1.0)] }),
"Gaussian" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.5, 0.85), (1.0, 1.0)] }),
"Half Round" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.25, 0.75), (0.5, 1.0), (0.75, 0.75), (1.0, 0.0)] }),
"Round" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)] }),
"Ring" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.25, 1.0), (0.5, 0.0), (0.75, 1.0), (1.0, 0.0)] }),
"Ring-Double" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.125, 1.0), (0.25, 0.0), (0.375, 1.0), (0.5, 0.0), (0.625, 1.0), (0.75, 0.0), (0.875, 1.0), (1.0, 0.0)] }),
"Sawtooth" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.5, 1.0), (0.5, 0.0), (1.0, 1.0)] }),
"Square" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.5, 0.0), (0.5, 1.0), (1.0, 1.0)] }),
"Valley" => Some(ContourCurve { points: vec![(0.0, 1.0), (0.5, 0.0), (1.0, 1.0)] }),
"Shallow-Slope" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.5, 0.25), (1.0, 1.0)] }),
"Wave" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.25, 1.0), (0.5, 0.5), (0.75, 1.0), (1.0, 0.0)] }),
"Cove" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.25, 0.5), (0.5, 1.0), (0.75, 0.5), (1.0, 0.0)] }),
"Washboard" => Some(ContourCurve { points: vec![(0.0, 0.0), (0.125, 1.0), (0.25, 0.0), (0.375, 1.0), (0.5, 0.0), (0.625, 1.0), (0.75, 0.0), (0.875, 1.0), (1.0, 0.0)] }),
"Cone" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)],
}),
"Cone-Inverted" => Some(ContourCurve {
points: vec![(0.0, 1.0), (0.5, 0.0), (1.0, 1.0)],
}),
"Gaussian" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.5, 0.85), (1.0, 1.0)],
}),
"Half Round" => Some(ContourCurve {
points: vec![
(0.0, 0.0),
(0.25, 0.75),
(0.5, 1.0),
(0.75, 0.75),
(1.0, 0.0),
],
}),
"Round" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)],
}),
"Ring" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.25, 1.0), (0.5, 0.0), (0.75, 1.0), (1.0, 0.0)],
}),
"Ring-Double" => Some(ContourCurve {
points: vec![
(0.0, 0.0),
(0.125, 1.0),
(0.25, 0.0),
(0.375, 1.0),
(0.5, 0.0),
(0.625, 1.0),
(0.75, 0.0),
(0.875, 1.0),
(1.0, 0.0),
],
}),
"Sawtooth" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.5, 1.0), (0.5, 0.0), (1.0, 1.0)],
}),
"Square" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.5, 0.0), (0.5, 1.0), (1.0, 1.0)],
}),
"Valley" => Some(ContourCurve {
points: vec![(0.0, 1.0), (0.5, 0.0), (1.0, 1.0)],
}),
"Shallow-Slope" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.5, 0.25), (1.0, 1.0)],
}),
"Wave" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.25, 1.0), (0.5, 0.5), (0.75, 1.0), (1.0, 0.0)],
}),
"Cove" => Some(ContourCurve {
points: vec![(0.0, 0.0), (0.25, 0.5), (0.5, 1.0), (0.75, 0.5), (1.0, 0.0)],
}),
"Washboard" => Some(ContourCurve {
points: vec![
(0.0, 0.0),
(0.125, 1.0),
(0.25, 0.0),
(0.375, 1.0),
(0.5, 0.0),
(0.625, 1.0),
(0.75, 0.0),
(0.875, 1.0),
(1.0, 0.0),
],
}),
_ => None, // Default to linear
}
}
@@ -223,7 +298,8 @@ pub fn blend_mode_to_string(mode: &BlendMode) -> String {
BlendMode::Color => "color",
BlendMode::PassThrough => "passthrough",
BlendMode::Luminosity => "luminosity",
}.to_string()
}
.to_string()
}
pub fn blend_mode_from_string(s: &str) -> BlendMode {
@@ -265,88 +341,244 @@ pub fn blend_mode_from_string(s: &str) -> BlendMode {
/// Used by engine-api before calling apply_layer_effects().
pub fn protocol_to_hcie_fx_effect(fx: &effects::LayerEffect) -> LayerEffect {
match fx {
effects::LayerEffect::DropShadow { enabled, blend_mode, color, opacity, angle, distance, spread, size, noise, contour } => {
LayerEffect::DropShadow {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), color: *color,
opacity: *opacity, angle: *angle, distance: *distance, spread: *spread, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| ContourCurve { points: c.points.clone() }),
}
}
effects::LayerEffect::InnerShadow { enabled, blend_mode, color, opacity, angle, distance, choke, size, noise, contour } => {
LayerEffect::InnerShadow {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), color: *color,
opacity: *opacity, angle: *angle, distance: *distance, choke: *choke, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| ContourCurve { points: c.points.clone() }),
}
}
effects::LayerEffect::OuterGlow { enabled, blend_mode, color, opacity, spread, size, noise, contour } => {
LayerEffect::OuterGlow {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), color: *color,
opacity: *opacity, spread: *spread, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| ContourCurve { points: c.points.clone() }),
}
}
effects::LayerEffect::InnerGlow { enabled, blend_mode, color, opacity, choke, size, noise, contour, source } => {
LayerEffect::InnerGlow {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), color: *color,
opacity: *opacity, choke: *choke, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| ContourCurve { points: c.points.clone() }),
source: *source,
}
}
effects::LayerEffect::BevelEmboss { enabled, style, technique, depth, direction, size, soften, angle, altitude, highlight_blend, highlight_color, highlight_opacity, shadow_blend, shadow_color, shadow_opacity, contour } => {
LayerEffect::BevelEmboss {
enabled: *enabled,
style: match style { effects::BevelStyle::InnerBevel => BevelStyle::InnerBevel, effects::BevelStyle::OuterBevel => BevelStyle::OuterBevel, effects::BevelStyle::Emboss => BevelStyle::Emboss, effects::BevelStyle::PillowEmboss => BevelStyle::PillowEmboss, effects::BevelStyle::StrokeEmboss => BevelStyle::StrokeEmboss },
technique: match technique { effects::Technique::Smooth => Technique::Smooth, effects::Technique::ChiselHard => Technique::ChiselHard, effects::Technique::ChiselSoft => Technique::ChiselSoft },
depth: *depth,
direction: match direction { effects::Direction::Up => Direction::Up, effects::Direction::Down => Direction::Down },
size: *size, soften: *soften, angle: *angle, altitude: *altitude,
highlight_blend: blend_mode_from_string(highlight_blend), highlight_color: *highlight_color, highlight_opacity: *highlight_opacity,
shadow_blend: blend_mode_from_string(shadow_blend), shadow_color: *shadow_color, shadow_opacity: *shadow_opacity,
contour: contour.as_ref().map(|c| ContourCurve { points: c.points.clone() }),
}
}
effects::LayerEffect::Satin { enabled, blend_mode, color, opacity, angle, distance, size, invert, contour } => {
LayerEffect::Satin {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), color: *color,
opacity: *opacity, angle: *angle, distance: *distance, size: *size, invert: *invert,
contour: contour.as_ref().map(|c| ContourCurve { points: c.points.clone() }),
}
}
effects::LayerEffect::ColorOverlay { enabled, blend_mode, color, opacity } => {
LayerEffect::ColorOverlay {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), color: *color, opacity: *opacity,
}
}
effects::LayerEffect::GradientOverlay { enabled, blend_mode, opacity, angle, scale, gradient } => {
LayerEffect::GradientOverlay {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), opacity: *opacity,
angle: *angle, scale: *scale,
gradient: GradientDef {
color_stops: gradient.color_stops.clone(),
transparency_stops: gradient.transparency_stops.clone(),
midpoint: gradient.midpoint,
angle: gradient.angle,
scale: gradient.scale,
gradient_type: gradient.gradient_type,
},
}
}
effects::LayerEffect::PatternOverlay { enabled, blend_mode, opacity, scale, pattern_id } => {
LayerEffect::PatternOverlay {
enabled: *enabled, blend_mode: blend_mode_from_string(blend_mode), opacity: *opacity,
scale: *scale, pattern_id: pattern_id.clone(),
}
}
effects::LayerEffect::Stroke { enabled, position, fill_type, blend_mode, opacity, size, color } => {
LayerEffect::Stroke {
enabled: *enabled,
position: match position { effects::StrokePosition::Inside => StrokePosition::Inside, effects::StrokePosition::Center => StrokePosition::Center, effects::StrokePosition::Outside => StrokePosition::Outside },
fill_type: match fill_type { effects::StrokeFillType::Color => StrokeFillType::Color, effects::StrokeFillType::Gradient => StrokeFillType::Gradient, effects::StrokeFillType::Pattern => StrokeFillType::Pattern },
blend_mode: blend_mode_from_string(blend_mode), opacity: *opacity, size: *size, color: *color,
}
}
effects::LayerEffect::DropShadow {
enabled,
blend_mode,
color,
opacity,
angle,
distance,
spread,
size,
noise,
contour,
} => LayerEffect::DropShadow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
opacity: *opacity,
angle: *angle,
distance: *distance,
spread: *spread,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| ContourCurve {
points: c.points.clone(),
}),
},
effects::LayerEffect::InnerShadow {
enabled,
blend_mode,
color,
opacity,
angle,
distance,
choke,
size,
noise,
contour,
} => LayerEffect::InnerShadow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
opacity: *opacity,
angle: *angle,
distance: *distance,
choke: *choke,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| ContourCurve {
points: c.points.clone(),
}),
},
effects::LayerEffect::OuterGlow {
enabled,
blend_mode,
color,
opacity,
spread,
size,
noise,
contour,
} => LayerEffect::OuterGlow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
opacity: *opacity,
spread: *spread,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| ContourCurve {
points: c.points.clone(),
}),
},
effects::LayerEffect::InnerGlow {
enabled,
blend_mode,
color,
opacity,
choke,
size,
noise,
contour,
source,
} => LayerEffect::InnerGlow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
opacity: *opacity,
choke: *choke,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| ContourCurve {
points: c.points.clone(),
}),
source: *source,
},
effects::LayerEffect::BevelEmboss {
enabled,
style,
technique,
depth,
direction,
size,
soften,
angle,
altitude,
highlight_blend,
highlight_color,
highlight_opacity,
shadow_blend,
shadow_color,
shadow_opacity,
contour,
} => LayerEffect::BevelEmboss {
enabled: *enabled,
style: match style {
effects::BevelStyle::InnerBevel => BevelStyle::InnerBevel,
effects::BevelStyle::OuterBevel => BevelStyle::OuterBevel,
effects::BevelStyle::Emboss => BevelStyle::Emboss,
effects::BevelStyle::PillowEmboss => BevelStyle::PillowEmboss,
effects::BevelStyle::StrokeEmboss => BevelStyle::StrokeEmboss,
},
technique: match technique {
effects::Technique::Smooth => Technique::Smooth,
effects::Technique::ChiselHard => Technique::ChiselHard,
effects::Technique::ChiselSoft => Technique::ChiselSoft,
},
depth: *depth,
direction: match direction {
effects::Direction::Up => Direction::Up,
effects::Direction::Down => Direction::Down,
},
size: *size,
soften: *soften,
angle: *angle,
altitude: *altitude,
highlight_blend: blend_mode_from_string(highlight_blend),
highlight_color: *highlight_color,
highlight_opacity: *highlight_opacity,
shadow_blend: blend_mode_from_string(shadow_blend),
shadow_color: *shadow_color,
shadow_opacity: *shadow_opacity,
contour: contour.as_ref().map(|c| ContourCurve {
points: c.points.clone(),
}),
},
effects::LayerEffect::Satin {
enabled,
blend_mode,
color,
opacity,
angle,
distance,
size,
invert,
contour,
} => LayerEffect::Satin {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
opacity: *opacity,
angle: *angle,
distance: *distance,
size: *size,
invert: *invert,
contour: contour.as_ref().map(|c| ContourCurve {
points: c.points.clone(),
}),
},
effects::LayerEffect::ColorOverlay {
enabled,
blend_mode,
color,
opacity,
} => LayerEffect::ColorOverlay {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
opacity: *opacity,
},
effects::LayerEffect::GradientOverlay {
enabled,
blend_mode,
opacity,
angle,
scale,
gradient,
} => LayerEffect::GradientOverlay {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
opacity: *opacity,
angle: *angle,
scale: *scale,
gradient: GradientDef {
color_stops: gradient.color_stops.clone(),
transparency_stops: gradient.transparency_stops.clone(),
midpoint: gradient.midpoint,
angle: gradient.angle,
scale: gradient.scale,
gradient_type: gradient.gradient_type,
},
},
effects::LayerEffect::PatternOverlay {
enabled,
blend_mode,
opacity,
scale,
pattern_id,
} => LayerEffect::PatternOverlay {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
opacity: *opacity,
scale: *scale,
pattern_id: pattern_id.clone(),
},
effects::LayerEffect::Stroke {
enabled,
position,
fill_type,
blend_mode,
opacity,
size,
color,
} => LayerEffect::Stroke {
enabled: *enabled,
position: match position {
effects::StrokePosition::Inside => StrokePosition::Inside,
effects::StrokePosition::Center => StrokePosition::Center,
effects::StrokePosition::Outside => StrokePosition::Outside,
},
fill_type: match fill_type {
effects::StrokeFillType::Color => StrokeFillType::Color,
effects::StrokeFillType::Gradient => StrokeFillType::Gradient,
effects::StrokeFillType::Pattern => StrokeFillType::Pattern,
},
blend_mode: blend_mode_from_string(blend_mode),
opacity: *opacity,
size: *size,
color: *color,
},
}
}
@@ -354,88 +586,244 @@ pub fn protocol_to_hcie_fx_effect(fx: &effects::LayerEffect) -> LayerEffect {
/// to protocol-layer effect (serialization format with String blend_mode).
pub fn hcie_fx_effect_to_protocol(fx: &LayerEffect) -> effects::LayerEffect {
match fx {
LayerEffect::DropShadow { enabled, blend_mode, color, opacity, angle, distance, spread, size, noise, contour } => {
effects::LayerEffect::DropShadow {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), color: *color,
opacity: *opacity, angle: *angle, distance: *distance, spread: *spread, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| effects::ContourCurve { points: c.points.clone() }),
}
}
LayerEffect::InnerShadow { enabled, blend_mode, color, opacity, angle, distance, choke, size, noise, contour } => {
effects::LayerEffect::InnerShadow {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), color: *color,
opacity: *opacity, angle: *angle, distance: *distance, choke: *choke, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| effects::ContourCurve { points: c.points.clone() }),
}
}
LayerEffect::OuterGlow { enabled, blend_mode, color, opacity, spread, size, noise, contour } => {
effects::LayerEffect::OuterGlow {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), color: *color,
opacity: *opacity, spread: *spread, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| effects::ContourCurve { points: c.points.clone() }),
}
}
LayerEffect::InnerGlow { enabled, blend_mode, color, opacity, choke, size, noise, contour, source } => {
effects::LayerEffect::InnerGlow {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), color: *color,
opacity: *opacity, choke: *choke, size: *size,
noise: *noise, contour: contour.as_ref().map(|c| effects::ContourCurve { points: c.points.clone() }),
source: *source,
}
}
LayerEffect::BevelEmboss { enabled, style, technique, depth, direction, size, soften, angle, altitude, highlight_blend, highlight_color, highlight_opacity, shadow_blend, shadow_color, shadow_opacity, contour } => {
effects::LayerEffect::BevelEmboss {
enabled: *enabled,
style: match style { BevelStyle::InnerBevel => effects::BevelStyle::InnerBevel, BevelStyle::OuterBevel => effects::BevelStyle::OuterBevel, BevelStyle::Emboss => effects::BevelStyle::Emboss, BevelStyle::PillowEmboss => effects::BevelStyle::PillowEmboss, BevelStyle::StrokeEmboss => effects::BevelStyle::StrokeEmboss },
technique: match technique { Technique::Smooth => effects::Technique::Smooth, Technique::ChiselHard => effects::Technique::ChiselHard, Technique::ChiselSoft => effects::Technique::ChiselSoft },
depth: *depth,
direction: match direction { Direction::Up => effects::Direction::Up, Direction::Down => effects::Direction::Down },
size: *size, soften: *soften, angle: *angle, altitude: *altitude,
highlight_blend: blend_mode_to_string(highlight_blend), highlight_color: *highlight_color, highlight_opacity: *highlight_opacity,
shadow_blend: blend_mode_to_string(shadow_blend), shadow_color: *shadow_color, shadow_opacity: *shadow_opacity,
contour: contour.as_ref().map(|c| effects::ContourCurve { points: c.points.clone() }),
}
}
LayerEffect::Satin { enabled, blend_mode, color, opacity, angle, distance, size, invert, contour } => {
effects::LayerEffect::Satin {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), color: *color,
opacity: *opacity, angle: *angle, distance: *distance, size: *size, invert: *invert,
contour: contour.as_ref().map(|c| effects::ContourCurve { points: c.points.clone() }),
}
}
LayerEffect::ColorOverlay { enabled, blend_mode, color, opacity } => {
effects::LayerEffect::ColorOverlay {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), color: *color, opacity: *opacity,
}
}
LayerEffect::GradientOverlay { enabled, blend_mode, opacity, angle, scale, gradient } => {
effects::LayerEffect::GradientOverlay {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), opacity: *opacity,
angle: *angle, scale: *scale,
gradient: effects::GradientDef {
color_stops: gradient.color_stops.clone(),
transparency_stops: gradient.transparency_stops.clone(),
midpoint: gradient.midpoint,
angle: gradient.angle,
scale: gradient.scale,
gradient_type: gradient.gradient_type,
},
}
}
LayerEffect::PatternOverlay { enabled, blend_mode, opacity, scale, pattern_id } => {
effects::LayerEffect::PatternOverlay {
enabled: *enabled, blend_mode: blend_mode_to_string(blend_mode), opacity: *opacity,
scale: *scale, pattern_id: pattern_id.clone(),
}
}
LayerEffect::Stroke { enabled, position, fill_type, blend_mode, opacity, size, color } => {
effects::LayerEffect::Stroke {
enabled: *enabled,
position: match position { StrokePosition::Inside => effects::StrokePosition::Inside, StrokePosition::Center => effects::StrokePosition::Center, StrokePosition::Outside => effects::StrokePosition::Outside },
fill_type: match fill_type { StrokeFillType::Color => effects::StrokeFillType::Color, StrokeFillType::Gradient => effects::StrokeFillType::Gradient, StrokeFillType::Pattern => effects::StrokeFillType::Pattern },
blend_mode: blend_mode_to_string(blend_mode), opacity: *opacity, size: *size, color: *color,
}
}
LayerEffect::DropShadow {
enabled,
blend_mode,
color,
opacity,
angle,
distance,
spread,
size,
noise,
contour,
} => effects::LayerEffect::DropShadow {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
color: *color,
opacity: *opacity,
angle: *angle,
distance: *distance,
spread: *spread,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| effects::ContourCurve {
points: c.points.clone(),
}),
},
LayerEffect::InnerShadow {
enabled,
blend_mode,
color,
opacity,
angle,
distance,
choke,
size,
noise,
contour,
} => effects::LayerEffect::InnerShadow {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
color: *color,
opacity: *opacity,
angle: *angle,
distance: *distance,
choke: *choke,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| effects::ContourCurve {
points: c.points.clone(),
}),
},
LayerEffect::OuterGlow {
enabled,
blend_mode,
color,
opacity,
spread,
size,
noise,
contour,
} => effects::LayerEffect::OuterGlow {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
color: *color,
opacity: *opacity,
spread: *spread,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| effects::ContourCurve {
points: c.points.clone(),
}),
},
LayerEffect::InnerGlow {
enabled,
blend_mode,
color,
opacity,
choke,
size,
noise,
contour,
source,
} => effects::LayerEffect::InnerGlow {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
color: *color,
opacity: *opacity,
choke: *choke,
size: *size,
noise: *noise,
contour: contour.as_ref().map(|c| effects::ContourCurve {
points: c.points.clone(),
}),
source: *source,
},
LayerEffect::BevelEmboss {
enabled,
style,
technique,
depth,
direction,
size,
soften,
angle,
altitude,
highlight_blend,
highlight_color,
highlight_opacity,
shadow_blend,
shadow_color,
shadow_opacity,
contour,
} => effects::LayerEffect::BevelEmboss {
enabled: *enabled,
style: match style {
BevelStyle::InnerBevel => effects::BevelStyle::InnerBevel,
BevelStyle::OuterBevel => effects::BevelStyle::OuterBevel,
BevelStyle::Emboss => effects::BevelStyle::Emboss,
BevelStyle::PillowEmboss => effects::BevelStyle::PillowEmboss,
BevelStyle::StrokeEmboss => effects::BevelStyle::StrokeEmboss,
},
technique: match technique {
Technique::Smooth => effects::Technique::Smooth,
Technique::ChiselHard => effects::Technique::ChiselHard,
Technique::ChiselSoft => effects::Technique::ChiselSoft,
},
depth: *depth,
direction: match direction {
Direction::Up => effects::Direction::Up,
Direction::Down => effects::Direction::Down,
},
size: *size,
soften: *soften,
angle: *angle,
altitude: *altitude,
highlight_blend: blend_mode_to_string(highlight_blend),
highlight_color: *highlight_color,
highlight_opacity: *highlight_opacity,
shadow_blend: blend_mode_to_string(shadow_blend),
shadow_color: *shadow_color,
shadow_opacity: *shadow_opacity,
contour: contour.as_ref().map(|c| effects::ContourCurve {
points: c.points.clone(),
}),
},
LayerEffect::Satin {
enabled,
blend_mode,
color,
opacity,
angle,
distance,
size,
invert,
contour,
} => effects::LayerEffect::Satin {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
color: *color,
opacity: *opacity,
angle: *angle,
distance: *distance,
size: *size,
invert: *invert,
contour: contour.as_ref().map(|c| effects::ContourCurve {
points: c.points.clone(),
}),
},
LayerEffect::ColorOverlay {
enabled,
blend_mode,
color,
opacity,
} => effects::LayerEffect::ColorOverlay {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
color: *color,
opacity: *opacity,
},
LayerEffect::GradientOverlay {
enabled,
blend_mode,
opacity,
angle,
scale,
gradient,
} => effects::LayerEffect::GradientOverlay {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
opacity: *opacity,
angle: *angle,
scale: *scale,
gradient: effects::GradientDef {
color_stops: gradient.color_stops.clone(),
transparency_stops: gradient.transparency_stops.clone(),
midpoint: gradient.midpoint,
angle: gradient.angle,
scale: gradient.scale,
gradient_type: gradient.gradient_type,
},
},
LayerEffect::PatternOverlay {
enabled,
blend_mode,
opacity,
scale,
pattern_id,
} => effects::LayerEffect::PatternOverlay {
enabled: *enabled,
blend_mode: blend_mode_to_string(blend_mode),
opacity: *opacity,
scale: *scale,
pattern_id: pattern_id.clone(),
},
LayerEffect::Stroke {
enabled,
position,
fill_type,
blend_mode,
opacity,
size,
color,
} => effects::LayerEffect::Stroke {
enabled: *enabled,
position: match position {
StrokePosition::Inside => effects::StrokePosition::Inside,
StrokePosition::Center => effects::StrokePosition::Center,
StrokePosition::Outside => effects::StrokePosition::Outside,
},
fill_type: match fill_type {
StrokeFillType::Color => effects::StrokeFillType::Color,
StrokeFillType::Gradient => effects::StrokeFillType::Gradient,
StrokeFillType::Pattern => effects::StrokeFillType::Pattern,
},
blend_mode: blend_mode_to_string(blend_mode),
opacity: *opacity,
size: *size,
color: *color,
},
}
}
@@ -445,7 +833,16 @@ pub fn hcie_fx_effect_to_protocol(fx: &LayerEffect) -> effects::LayerEffect {
pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerEffect> {
use hcie_protocol::LayerStyle;
match style {
LayerStyle::DropShadow { enabled, opacity, angle, distance, spread, size, color, blend_mode } => Some(LayerEffect::DropShadow {
LayerStyle::DropShadow {
enabled,
opacity,
angle,
distance,
spread,
size,
color,
blend_mode,
} => Some(LayerEffect::DropShadow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
@@ -457,7 +854,16 @@ pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerE
noise: 0.0,
contour: None,
}),
LayerStyle::InnerShadow { enabled, opacity, angle, distance, spread, size, color, blend_mode } => Some(LayerEffect::InnerShadow {
LayerStyle::InnerShadow {
enabled,
opacity,
angle,
distance,
spread,
size,
color,
blend_mode,
} => Some(LayerEffect::InnerShadow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
@@ -469,7 +875,14 @@ pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerE
noise: 0.0,
contour: None,
}),
LayerStyle::OuterGlow { enabled, opacity, spread, size, color, blend_mode } => Some(LayerEffect::OuterGlow {
LayerStyle::OuterGlow {
enabled,
opacity,
spread,
size,
color,
blend_mode,
} => Some(LayerEffect::OuterGlow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
@@ -479,7 +892,14 @@ pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerE
noise: 0.0,
contour: None,
}),
LayerStyle::InnerGlow { enabled, opacity, spread, size, color, blend_mode } => Some(LayerEffect::InnerGlow {
LayerStyle::InnerGlow {
enabled,
opacity,
spread,
size,
color,
blend_mode,
} => Some(LayerEffect::InnerGlow {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
@@ -490,7 +910,24 @@ pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerE
contour: None,
source: 0,
}),
LayerStyle::BevelEmboss { enabled, depth, size, angle, altitude, highlight_opacity, shadow_opacity, direction, style, technique, soften, highlight_blend_mode, highlight_color, shadow_blend_mode, shadow_color, contour } => Some(LayerEffect::BevelEmboss {
LayerStyle::BevelEmboss {
enabled,
depth,
size,
angle,
altitude,
highlight_opacity,
shadow_opacity,
direction,
style,
technique,
soften,
highlight_blend_mode,
highlight_color,
shadow_blend_mode,
shadow_color,
contour,
} => Some(LayerEffect::BevelEmboss {
enabled: *enabled,
style: match style.as_str() {
"InnerBevel" => BevelStyle::InnerBevel,
@@ -524,7 +961,15 @@ pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerE
shadow_opacity: *shadow_opacity,
contour: contour_name_to_curve(contour),
}),
LayerStyle::Satin { enabled, opacity, angle, distance, size, color, invert } => Some(LayerEffect::Satin {
LayerStyle::Satin {
enabled,
opacity,
angle,
distance,
size,
color,
invert,
} => Some(LayerEffect::Satin {
enabled: *enabled,
blend_mode: BlendMode::Multiply,
color: *color,
@@ -535,13 +980,25 @@ pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerE
invert: *invert,
contour: None,
}),
LayerStyle::ColorOverlay { enabled, opacity, color, blend_mode } => Some(LayerEffect::ColorOverlay {
LayerStyle::ColorOverlay {
enabled,
opacity,
color,
blend_mode,
} => Some(LayerEffect::ColorOverlay {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
color: *color,
opacity: *opacity,
}),
LayerStyle::GradientOverlay { enabled, opacity, blend_mode, angle, scale, gradient_type: _ } => Some(LayerEffect::GradientOverlay {
LayerStyle::GradientOverlay {
enabled,
opacity,
blend_mode,
angle,
scale,
gradient_type: _,
} => Some(LayerEffect::GradientOverlay {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
opacity: *opacity,
@@ -556,14 +1013,27 @@ pub fn layer_style_to_effect(style: &hcie_protocol::LayerStyle) -> Option<LayerE
gradient_type: 0,
},
}),
LayerStyle::PatternOverlay { enabled, opacity, blend_mode, scale, pattern_name } => Some(LayerEffect::PatternOverlay {
LayerStyle::PatternOverlay {
enabled,
opacity,
blend_mode,
scale,
pattern_name,
} => Some(LayerEffect::PatternOverlay {
enabled: *enabled,
blend_mode: blend_mode_from_string(blend_mode),
opacity: *opacity,
scale: *scale,
pattern_id: pattern_name.clone(),
}),
LayerStyle::Stroke { enabled, size, position, opacity, color, blend_mode } => Some(LayerEffect::Stroke {
LayerStyle::Stroke {
enabled,
size,
position,
opacity,
color,
blend_mode,
} => Some(LayerEffect::Stroke {
enabled: *enabled,
position: match position.as_str() {
"Inside" => StrokePosition::Inside,