bevel and emboss geliştirme
This commit is contained in:
Generated
+1
-149
@@ -455,19 +455,6 @@ version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "av-data"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fca67ba5d317924c02180c576157afd54babe48a76ebc66ce6d34bb8ba08308e"
|
||||
dependencies = [
|
||||
"byte-slice-cast",
|
||||
"bytes",
|
||||
"num-derive",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "av-scenechange"
|
||||
version = "0.14.1"
|
||||
@@ -592,15 +579,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitreader"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "886559b1e163d56c765bc3a985febb4eee8009f625244511d8ee3c432e08c066"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitstream-io"
|
||||
version = "4.10.0"
|
||||
@@ -686,12 +664,6 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
|
||||
|
||||
[[package]]
|
||||
name = "byte-slice-cast"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
@@ -793,16 +765,6 @@ dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
@@ -1148,28 +1110,6 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||
|
||||
[[package]]
|
||||
name = "dav1d"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ee89cb860616069c67520dcd66cacdb900b57c799f634a0eb6d91f6e2a82b61"
|
||||
dependencies = [
|
||||
"av-data",
|
||||
"bitflags 2.13.0",
|
||||
"dav1d-sys",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dav1d-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c91aea6668645415331133ed6f8ddf0e7f40160cd97a12d59e68716a58704b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dconf_rs"
|
||||
version = "0.3.0"
|
||||
@@ -1873,15 +1813,6 @@ dependencies = [
|
||||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible_collections"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd"
|
||||
dependencies = [
|
||||
"hashbrown 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-srgb8"
|
||||
version = "1.0.0"
|
||||
@@ -2515,15 +2446,6 @@ dependencies = [
|
||||
"ahash 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@@ -2826,6 +2748,7 @@ dependencies = [
|
||||
"proptest",
|
||||
"psd",
|
||||
"rand 0.8.6",
|
||||
"rayon",
|
||||
"resvg 0.43.0",
|
||||
"rfd",
|
||||
"rstest",
|
||||
@@ -3453,12 +3376,10 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"dav1d",
|
||||
"exr",
|
||||
"gif 0.14.2",
|
||||
"image-webp 0.2.4",
|
||||
"moxcms",
|
||||
"mp4parse",
|
||||
"num-traits",
|
||||
"png 0.18.1",
|
||||
"qoi",
|
||||
@@ -4039,20 +3960,6 @@ dependencies = [
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mp4parse"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63a35203d3c6ce92d5251c77520acb2e57108c88728695aa883f70023624c570"
|
||||
dependencies = [
|
||||
"bitreader",
|
||||
"byteorder",
|
||||
"fallible_collections",
|
||||
"log",
|
||||
"num-traits",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mutate_once"
|
||||
version = "0.1.2"
|
||||
@@ -5956,15 +5863,6 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@@ -6354,31 +6252,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "7.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
@@ -6591,21 +6470,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
@@ -6636,12 +6500,6 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
@@ -7047,12 +6905,6 @@ dependencies = [
|
||||
"vello_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
lighting_blur=100 hl_scale=0.10 sh_scale=0.10
|
||||
avg_mae=29.5671 samples=864 combos=343
|
||||
@@ -0,0 +1,2 @@
|
||||
lighting_blur=0 hl_scale=0.050 sh_scale=16.00 tilt_scale=0.05
|
||||
avg_mae=23.1730 samples=864 combos=432
|
||||
@@ -0,0 +1,3 @@
|
||||
=== Emboss Tuned ===
|
||||
Params: blur=5, hl_scale=0.05, sh_scale=2.50
|
||||
MAE: 14.583
|
||||
@@ -222,6 +222,7 @@ impl Engine {
|
||||
|
||||
if !layer.effects.is_empty() || !layer.styles.is_empty() {
|
||||
layer.effects_dirty.store(true, std::sync::atomic::Ordering::Release);
|
||||
self.document.composite_dirty = true;
|
||||
}
|
||||
|
||||
// Take the pooled before buffer (no allocation)
|
||||
|
||||
@@ -42,6 +42,14 @@ pub fn apply_layer_effects(
|
||||
|
||||
let alpha = extract_alpha(pixels, px_count);
|
||||
|
||||
// Storage for Emboss/OuterBevel outside portions (applied to `behind` later)
|
||||
let mut bevel_hl_outside: Option<Vec<u8>> = None;
|
||||
let mut bevel_sh_outside: Option<Vec<u8>> = None;
|
||||
let mut bevel_hl_blend: hcie_blend::BlendMode = hcie_blend::BlendMode::Screen;
|
||||
let mut bevel_sh_blend: hcie_blend::BlendMode = hcie_blend::BlendMode::Multiply;
|
||||
let mut bevel_hl_op: f32 = 1.0;
|
||||
let mut bevel_sh_op: f32 = 1.0;
|
||||
|
||||
// 1. Prepare inside content (original pixels + inside effects)
|
||||
let mut inside = vec![0u8; px_count * 4];
|
||||
inside.copy_from_slice(pixels);
|
||||
@@ -67,8 +75,38 @@ pub fn apply_layer_effects(
|
||||
*direction, *technique, *style,
|
||||
*highlight_color, *shadow_color,
|
||||
);
|
||||
composite_effect(&mut inside, &highlight, *highlight_blend, *highlight_opacity, px_count);
|
||||
composite_effect(&mut inside, &shadow, *shadow_blend, *shadow_opacity, px_count);
|
||||
|
||||
// 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);
|
||||
if has_outer {
|
||||
// Split highlight/shadow into inside and outside portions
|
||||
let mut hl_inside = vec![0u8; px_count * 4];
|
||||
let mut hl_outside = vec![0u8; px_count * 4];
|
||||
let mut sh_inside = vec![0u8; px_count * 4];
|
||||
let mut sh_outside = vec![0u8; px_count * 4];
|
||||
for i in 0..px_count {
|
||||
if alpha[i] > 0 {
|
||||
hl_inside[i * 4..i * 4 + 4].copy_from_slice(&highlight[i * 4..i * 4 + 4]);
|
||||
sh_inside[i * 4..i * 4 + 4].copy_from_slice(&shadow[i * 4..i * 4 + 4]);
|
||||
} else {
|
||||
hl_outside[i * 4..i * 4 + 4].copy_from_slice(&highlight[i * 4..i * 4 + 4]);
|
||||
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);
|
||||
// Store outside portions to apply to `behind` later
|
||||
bevel_hl_outside = Some(hl_outside);
|
||||
bevel_sh_outside = Some(sh_outside);
|
||||
bevel_hl_blend = *highlight_blend;
|
||||
bevel_sh_blend = *shadow_blend;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// b. Satin
|
||||
@@ -159,6 +197,15 @@ pub fn apply_layer_effects(
|
||||
}
|
||||
}
|
||||
|
||||
// d. Emboss/OuterBevel outside portion (highlight then shadow)
|
||||
// Applied to `behind` so the outer bevel band renders behind the shape.
|
||||
if let Some(hl_out) = &bevel_hl_outside {
|
||||
composite_effect(&mut behind, hl_out, bevel_hl_blend, bevel_hl_op, px_count);
|
||||
}
|
||||
if let Some(sh_out) = &bevel_sh_outside {
|
||||
composite_effect(&mut behind, sh_out, bevel_sh_blend, bevel_sh_op, px_count);
|
||||
}
|
||||
|
||||
// 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]];
|
||||
|
||||
@@ -68,8 +68,6 @@ pub(crate) fn generate_shadow(
|
||||
|
||||
// MAE-optimized against Krita ground truth (768 samples, 350 combos, 800x800)
|
||||
// Best: blur_factor=5.0, passes=5, spread_scale=1.00
|
||||
// Actually use the spread parameter (0.0 to 100.0) from the user
|
||||
apply_spread(&mut offset_alpha, spread);
|
||||
|
||||
let radius = size.max(0.0).round() as i32;
|
||||
if radius > 0 {
|
||||
@@ -81,6 +79,9 @@ pub(crate) fn generate_shadow(
|
||||
offset_alpha = f32_buf.iter().map(|&a| (a as u8).min(255)).collect();
|
||||
}
|
||||
|
||||
// Actually use the spread parameter (0.0 to 100.0) from the user AFTER the blur!
|
||||
apply_spread(&mut offset_alpha, spread);
|
||||
|
||||
for i in 0..px {
|
||||
let a = offset_alpha[i];
|
||||
if a > 0 {
|
||||
|
||||
+195
-30
@@ -1,5 +1,44 @@
|
||||
use crate::types;
|
||||
use crate::helpers::box_blur_f32;
|
||||
|
||||
/// Generate bevel & emboss highlight and shadow buffers.
|
||||
///
|
||||
/// **Purpose:** Renders Photoshop-style Bevel & Emboss layer effect. Produces
|
||||
/// two separate RGBA buffers (highlight + shadow) that the caller composites
|
||||
/// over the layer content using Screen and Multiply blend modes respectively.
|
||||
///
|
||||
/// **Logic & Workflow:**
|
||||
/// 1. Compute inside/outside Euclidean Distance Transforms (EDT) from the
|
||||
/// layer alpha mask. For Emboss and OuterBevel, also track which opaque
|
||||
/// pixel is nearest to each outside pixel so the outer bevel can inherit
|
||||
/// the alpha of the nearest content pixel.
|
||||
/// 2. Build a continuous height field from the EDT values. The profile shape
|
||||
/// depends on the BevelStyle:
|
||||
/// - InnerBevel: ramps up from edge to center inside the shape.
|
||||
/// - OuterBevel: ramps down from edge outward outside the shape.
|
||||
/// - Emboss: combines both — ramps inside (raised) and outside (lowered).
|
||||
/// 3. Apply a box-blur soften pass on the height field.
|
||||
/// 4. Compute per-pixel lighting via central-difference normals · light vector.
|
||||
/// 5. Apply a lighting blur to smooth wrinkles at sharp corners.
|
||||
/// 6. Map lighting to highlight/shadow alpha. For outside pixels, use the
|
||||
/// nearest opaque pixel's alpha instead of the (zero) local alpha.
|
||||
///
|
||||
/// **Arguments:**
|
||||
/// * `alpha` — Layer alpha mask (one byte per pixel)
|
||||
/// * `w`, `h` — Canvas dimensions
|
||||
/// * `depth` — Bevel depth (0–1000+, controls slope steepness)
|
||||
/// * `size` — Bevel size in pixels (radius of the bevel band)
|
||||
/// * `soften` — Soften amount in pixels (extra blur on height field)
|
||||
/// * `angle` — Light azimuth angle in degrees
|
||||
/// * `altitude` — Light altitude angle in degrees
|
||||
/// * `direction` — Up or Down (flips lighting)
|
||||
/// * `technique` — Smooth, ChiselHard, ChiselSoft
|
||||
/// * `style` — InnerBevel, OuterBevel, Emboss, etc.
|
||||
/// * `highlight_color` — RGBA color for the highlight channel
|
||||
/// * `shadow_color` — RGBA color for the shadow channel
|
||||
///
|
||||
/// **Returns:**
|
||||
/// `(highlight_buf, shadow_buf)` — two RGBA buffers, same size as canvas.
|
||||
pub fn generate_bevel_emboss(
|
||||
alpha: &[u8],
|
||||
w: u32,
|
||||
@@ -27,6 +66,16 @@ pub fn generate_bevel_emboss(
|
||||
let dist_inside_sq = edt_inside(alpha, iw, ih);
|
||||
let dist_outside_sq = edt_outside(alpha, iw, ih);
|
||||
|
||||
// For Emboss and OuterBevel, track the nearest opaque source pixel so
|
||||
// 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) {
|
||||
edt_outside_src(alpha, iw, ih)
|
||||
} else {
|
||||
vec![0usize; px]
|
||||
};
|
||||
|
||||
// Compute shape center for Emboss tilt
|
||||
let (cx, cy) = {
|
||||
let mut min_x = iw;
|
||||
@@ -76,15 +125,17 @@ pub fn generate_bevel_emboss(
|
||||
}
|
||||
}
|
||||
types::BevelStyle::Emboss => {
|
||||
// Emboss = inner bevel (inside) + outer bevel (outside) + tilt.
|
||||
// tilt_scale controls the global slope for 3D emboss effect.
|
||||
// Negate tilt so direction=Down correctly produces highlight on
|
||||
// the light side and shadow on the dark side.
|
||||
let px_x = (i % iw) as f32;
|
||||
let px_y = (i / iw) as f32;
|
||||
let tilt = (px_x - cx) * light_x + (px_y - cy) * light_y;
|
||||
let tilt = ((px_x - cx) * light_x + (px_y - cy) * light_y) * radius * 0.05;
|
||||
if alpha[i] > 0 {
|
||||
let edge_factor = (d_in / radius).min(1.0);
|
||||
radius * edge_factor + tilt
|
||||
(d_in / radius).min(1.0) * radius + tilt
|
||||
} else {
|
||||
let t = (d_out / radius).min(1.0);
|
||||
(radius * (1.0 - t) * 0.5 + tilt).max(0.0)
|
||||
((radius - d_out).max(0.0) / radius) * radius + tilt
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -155,13 +206,7 @@ pub fn generate_bevel_emboss(
|
||||
|
||||
let ndl = nx * light_x + ny * light_y + nz * light_z;
|
||||
|
||||
let factor = if matches!(style, types::BevelStyle::Emboss) && is_inner {
|
||||
let height_factor = height[i] / radius;
|
||||
let blended = ndl * 0.4 + height_factor * 0.6;
|
||||
blended * depth_scale
|
||||
} else {
|
||||
ndl
|
||||
};
|
||||
let factor = ndl;
|
||||
|
||||
lighting[i] = match direction {
|
||||
types::Direction::Up => factor,
|
||||
@@ -171,46 +216,59 @@ pub fn generate_bevel_emboss(
|
||||
}
|
||||
|
||||
// Smooth the lighting to reduce wrinkles at sharp corners.
|
||||
// MAE-optimized against Krita ground truth (320 samples, 256×256).
|
||||
// Best: lighting_blur=5, hl_scale=0.30, sh_scale=2.00, avg_mae=17.18
|
||||
// Emboss uses tilt for 3D effect and needs sharp lighting (no blur).
|
||||
// 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 = (soften_radius + 5).max(1);
|
||||
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 {
|
||||
let is_inner = alpha[i] > 0;
|
||||
let show = match style {
|
||||
types::BevelStyle::InnerBevel => is_inner,
|
||||
types::BevelStyle::OuterBevel => !is_inner,
|
||||
types::BevelStyle::Emboss => true,
|
||||
crate::types::BevelStyle::InnerBevel => is_inner,
|
||||
crate::types::BevelStyle::OuterBevel => !is_inner,
|
||||
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; }
|
||||
let technique_show = match technique {
|
||||
types::Technique::Smooth => true,
|
||||
crate::types::Technique::Smooth => true,
|
||||
_ => dist_abs <= radius,
|
||||
};
|
||||
if !technique_show { continue; }
|
||||
|
||||
let ndl = lighting[i];
|
||||
let is_flat = is_inner && dist_abs > radius;
|
||||
|
||||
// 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);
|
||||
if is_flat {
|
||||
if style == types::BevelStyle::Emboss && depth > 100.0 {
|
||||
shadow_buf[i * 4 + 3] = alpha[i];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// MAE-optimized against Krita ground truth (320 samples, 256×256).
|
||||
// Best: lighting_blur=5, hl_scale=0.30, sh_scale=2.00, avg_mae=17.18
|
||||
let hl_pred = (ndl * 0.30).clamp(0.0, 1.0);
|
||||
let sh_pred = (-ndl * 2.00).clamp(0.0, 1.0);
|
||||
let (hl_scale, sh_scale) = if matches!(style, crate::types::BevelStyle::Emboss) {
|
||||
(0.05, 16.00)
|
||||
} else {
|
||||
(0.05, 2.50)
|
||||
};
|
||||
let hl_pred = (ndl * hl_scale).clamp(0.0, 1.0);
|
||||
let sh_pred = (-ndl * sh_scale).clamp(0.0, 1.0);
|
||||
|
||||
let hl_a = (alpha[i] as f32 * hl_pred).round() as u8;
|
||||
let sh_a = (alpha[i] as f32 * sh_pred).round() as u8;
|
||||
// For outside pixels (alpha[i]==0), use the nearest opaque pixel's
|
||||
// alpha as the strength base. This is what Photoshop does: the outer
|
||||
// bevel band fades out based on distance to the shape edge, and the
|
||||
// effect intensity is proportional to the nearest content alpha.
|
||||
let base_alpha = if is_inner {
|
||||
alpha[i] as f32
|
||||
} else {
|
||||
alpha[outside_src[i]] as f32
|
||||
};
|
||||
|
||||
let hl_a = (base_alpha * hl_pred).round() as u8;
|
||||
let sh_a = (base_alpha * sh_pred).round() as u8;
|
||||
|
||||
if hl_a > 0 {
|
||||
highlight_buf[i * 4] = highlight_color[0];
|
||||
@@ -267,6 +325,33 @@ fn edt_outside(alpha: &[u8], w: usize, h: usize) -> Vec<f32> {
|
||||
dt
|
||||
}
|
||||
|
||||
/// EDT for outside pixels with source index tracking.
|
||||
///
|
||||
/// **Purpose:** Same as `edt_outside` but also returns, for each pixel, the
|
||||
/// index of the nearest opaque pixel. This is needed by Emboss and OuterBevel
|
||||
/// to give outside (transparent) pixels the alpha of the nearest content pixel.
|
||||
///
|
||||
/// **Returns:** `Vec<usize>` where each element is the pixel index of the
|
||||
/// nearest opaque source pixel.
|
||||
fn edt_outside_src(alpha: &[u8], w: usize, h: usize) -> Vec<usize> {
|
||||
let n = w * h;
|
||||
let mut dt = vec![1e20f32; n];
|
||||
let mut src = vec![0usize; n];
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let i = y * w + x;
|
||||
if alpha[i] > 0 {
|
||||
dt[i] = 0.0;
|
||||
src[i] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edt_2d_src(&mut dt, &mut src, w, h);
|
||||
src
|
||||
}
|
||||
|
||||
/// 2D Euclidean Distance Transform using the Felzenszwalb & Huttenlocher algorithm.
|
||||
fn edt_2d(dt: &mut [f32], w: usize, h: usize) {
|
||||
let mut col = vec![0.0f32; h];
|
||||
@@ -337,3 +422,83 @@ fn edt_1d(f: &mut [f32]) {
|
||||
|
||||
f.copy_from_slice(&d);
|
||||
}
|
||||
|
||||
/// 2D EDT with source index tracking (Felzenszwalb & Huttenlocher).
|
||||
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];
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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; }
|
||||
|
||||
let mut d = vec![0.0f32; n];
|
||||
let mut d_src = vec![0usize; n];
|
||||
let mut v = vec![0usize; n];
|
||||
let mut z = vec![0.0f32; n + 1];
|
||||
|
||||
let mut k = 0usize;
|
||||
v[0] = 0;
|
||||
z[0] = f32::NEG_INFINITY;
|
||||
z[1] = f32::INFINITY;
|
||||
|
||||
for q in 1..n {
|
||||
let qf = q as f32;
|
||||
let fq = f[q] + qf * qf;
|
||||
let vk = v[k];
|
||||
let fv_vk = f[vk] + vk as f32 * vk as 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; }
|
||||
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);
|
||||
}
|
||||
k += 1;
|
||||
v[k] = q;
|
||||
z[k] = s;
|
||||
z[k + 1] = f32::INFINITY;
|
||||
}
|
||||
|
||||
k = 0;
|
||||
for q in 0..n {
|
||||
let qf = q as f32;
|
||||
while z[k + 1] < qf { k += 1; }
|
||||
let vk = v[k];
|
||||
let diff = qf - vk as f32;
|
||||
d[q] = diff * diff + f[vk];
|
||||
d_src[q] = src_idx[v[k]];
|
||||
}
|
||||
|
||||
f.copy_from_slice(&d);
|
||||
src_idx.copy_from_slice(&d_src);
|
||||
}
|
||||
@@ -64,8 +64,6 @@ pub(crate) fn generate_inner_shadow(
|
||||
}
|
||||
}
|
||||
|
||||
apply_spread(&mut offset_alpha, choke);
|
||||
|
||||
let radius = size.max(0.0).round() as i32;
|
||||
if radius > 0 {
|
||||
let gaussian_r = (radius as f32 / 10.0).round().max(1.0) as i32;
|
||||
@@ -75,6 +73,8 @@ pub(crate) fn generate_inner_shadow(
|
||||
offset_alpha = f32_buf.iter().map(|&a| (a as u8).min(255)).collect();
|
||||
}
|
||||
|
||||
apply_spread(&mut offset_alpha, choke);
|
||||
|
||||
for i in 0..px {
|
||||
let a = ((offset_alpha[i] as f32 * 0.50) as u32 * alpha[i] as u32 / 255) as u8;
|
||||
if a > 0 {
|
||||
|
||||
+35
-17
@@ -19,6 +19,7 @@ pub fn generate_bevel_tuned(
|
||||
hl_scale: f32,
|
||||
sh_scale: f32,
|
||||
profile_exp: f32,
|
||||
tilt_scale: f32,
|
||||
) -> (Vec<u8>, Vec<u8>) {
|
||||
let hl_color = [255u8; 4];
|
||||
let sh_color = [0u8; 4];
|
||||
@@ -33,6 +34,14 @@ pub fn generate_bevel_tuned(
|
||||
let dist_inside_sq = edt_inside(alpha, iw, ih);
|
||||
let dist_outside_sq = edt_outside(alpha, iw, ih);
|
||||
|
||||
// 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) {
|
||||
edt_outside_src(alpha, iw, ih)
|
||||
} else {
|
||||
vec![0usize; px]
|
||||
};
|
||||
|
||||
// Compute shape center for Emboss tilt
|
||||
let (cx, cy) = {
|
||||
let mut min_x = iw;
|
||||
@@ -81,15 +90,22 @@ pub fn generate_bevel_tuned(
|
||||
}
|
||||
}
|
||||
types::BevelStyle::Emboss => {
|
||||
// Emboss = inner bevel (inside) + outer bevel (outside) + tilt.
|
||||
// tilt_scale controls how much global slope is added across the
|
||||
// shape based on light direction. The tilt makes one side of
|
||||
// the shape raised (toward light = highlight) and the other
|
||||
// lowered (away from light = shadow), producing the 3D effect.
|
||||
let px_x = (i % iw) as f32;
|
||||
let px_y = (i / iw) as f32;
|
||||
let tilt = (px_x - cx) * light_x + (px_y - cy) * light_y;
|
||||
// Negate tilt so direction=Down (default) correctly produces
|
||||
// highlight on the light side and shadow on the dark side.
|
||||
let tilt = ((px_x - cx) * light_x + (px_y - cy) * light_y) * radius * tilt_scale;
|
||||
if alpha[i] > 0 {
|
||||
let t = (d_in / radius).min(1.0);
|
||||
radius * t.powf(profile_exp) + tilt
|
||||
t.powf(profile_exp) * radius + tilt
|
||||
} else {
|
||||
let t = (d_out / radius).min(1.0);
|
||||
(radius * (1.0 - t).powf(profile_exp) * 0.5 + tilt).max(0.0)
|
||||
let t = ((radius - d_out).max(0.0) / radius).min(1.0);
|
||||
(1.0 - t).powf(profile_exp) * radius + tilt
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -142,13 +158,7 @@ pub fn generate_bevel_tuned(
|
||||
|
||||
let ndl = nx * light_x + ny * light_y + nz * light_z;
|
||||
|
||||
let factor = if matches!(style, types::BevelStyle::Emboss) && is_inner {
|
||||
let height_factor = height[i] / radius;
|
||||
let blended = ndl * 0.4 + height_factor * 0.6;
|
||||
blended * depth_scale
|
||||
} else {
|
||||
ndl
|
||||
};
|
||||
let factor = ndl;
|
||||
|
||||
lighting[i] = match direction {
|
||||
types::Direction::Up => factor,
|
||||
@@ -171,19 +181,27 @@ pub fn generate_bevel_tuned(
|
||||
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];
|
||||
let is_flat = is_inner && dist_abs > radius;
|
||||
// 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, types::BevelStyle::Emboss);
|
||||
if is_flat {
|
||||
if *style == types::BevelStyle::Emboss && depth > 100.0 {
|
||||
shadow_buf[i * 4 + 3] = alpha[i];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let hl_pred = (ndl * hl_scale).clamp(0.0, 1.0);
|
||||
let sh_pred = (-ndl * sh_scale).clamp(0.0, 1.0);
|
||||
|
||||
let hl_a = (alpha[i] as f32 * hl_pred).round() as u8;
|
||||
let sh_a = (alpha[i] as f32 * sh_pred).round() as u8;
|
||||
// For outside pixels (alpha[i]==0), use the nearest opaque pixel's
|
||||
// alpha as the strength base, matching Photoshop behavior.
|
||||
let base_alpha = if is_inner {
|
||||
alpha[i] as f32
|
||||
} else {
|
||||
alpha[outside_src[i]] as f32
|
||||
};
|
||||
|
||||
let hl_a = (base_alpha * hl_pred).round() as u8;
|
||||
let sh_a = (base_alpha * sh_pred).round() as u8;
|
||||
|
||||
if hl_a > 0 {
|
||||
highlight_buf[i * 4] = hl_color[0];
|
||||
|
||||
@@ -129,6 +129,9 @@ pub struct HcieIcedApp {
|
||||
pub style_color_picker_idx: usize,
|
||||
/// HSL color state for the style color picker (hue 0..360, sat 0..1, light 0..1).
|
||||
pub style_color_hsl: (f32, f32, f32),
|
||||
/// True when the color picker is editing the shadow color (BevelEmboss),
|
||||
/// false when editing the highlight color or the single-color style's color.
|
||||
pub style_color_is_shadow: bool,
|
||||
/// Currently active color picker tab (0=Wheel, 1=Sliders, 2=Grid).
|
||||
pub color_tab: usize,
|
||||
/// Layer Style dialog drag offset from center (x, y) in pixels.
|
||||
@@ -295,7 +298,12 @@ pub enum Message {
|
||||
LayerStyleUpdateParam(usize, String, f32),
|
||||
LayerStyleUpdateBlendMode(usize, String),
|
||||
LayerStyleUpdateColor(usize, [u8; 4]),
|
||||
LayerStyleUpdateString(usize, String, String),
|
||||
LayerStyleUpdateShadowColor(usize, [u8; 4]),
|
||||
LayerStyleUpdateShadowBlend(usize, String),
|
||||
LayerStyleUpdateShadowOpacity(usize, f32),
|
||||
ShowStyleColorPicker(usize),
|
||||
ShowStyleShadowColorPicker(usize),
|
||||
HideStyleColorPicker,
|
||||
OpenLayerStyleDialog,
|
||||
LayerStyleDialogDragStart(f32, f32),
|
||||
@@ -532,6 +540,7 @@ impl HcieIcedApp {
|
||||
show_style_color_picker: false,
|
||||
style_color_picker_idx: 0,
|
||||
style_color_hsl: (0.0, 0.0, 0.0),
|
||||
style_color_is_shadow: false,
|
||||
color_tab: 0,
|
||||
layer_style_offset: (0.0, 0.0),
|
||||
layer_style_dragging: false,
|
||||
@@ -1238,6 +1247,12 @@ impl HcieIcedApp {
|
||||
*highlight_opacity = value;
|
||||
*shadow_opacity = value;
|
||||
},
|
||||
(LayerStyle::BevelEmboss { highlight_opacity, .. }, "highlight_opacity") => {
|
||||
*highlight_opacity = value;
|
||||
},
|
||||
(LayerStyle::BevelEmboss { shadow_opacity, .. }, "shadow_opacity") => {
|
||||
*shadow_opacity = value;
|
||||
},
|
||||
(LayerStyle::Satin { opacity, .. }, "opacity") => *opacity = value,
|
||||
(LayerStyle::Satin { angle, .. }, "angle") => *angle = value,
|
||||
(LayerStyle::Satin { distance, .. }, "distance") => *distance = value,
|
||||
@@ -1292,6 +1307,7 @@ impl HcieIcedApp {
|
||||
| LayerStyle::GradientOverlay { blend_mode, .. }
|
||||
| LayerStyle::PatternOverlay { blend_mode, .. }
|
||||
| LayerStyle::Stroke { blend_mode, .. } => *blend_mode = mode,
|
||||
LayerStyle::BevelEmboss { highlight_blend_mode, .. } => *highlight_blend_mode = mode,
|
||||
_ => {}
|
||||
}
|
||||
self.documents[self.active_doc].engine.update_layer_style(layer_id, new_style);
|
||||
@@ -1333,7 +1349,13 @@ impl HcieIcedApp {
|
||||
| LayerStyle::ColorOverlay { color, .. }
|
||||
| LayerStyle::Satin { color, .. }
|
||||
| LayerStyle::Stroke { color, .. } => *color = new_color,
|
||||
LayerStyle::BevelEmboss { highlight_color, .. } => *highlight_color = new_color,
|
||||
LayerStyle::BevelEmboss { highlight_color, shadow_color, .. } => {
|
||||
if self.style_color_is_shadow {
|
||||
*shadow_color = new_color;
|
||||
} else {
|
||||
*highlight_color = new_color;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.documents[self.active_doc].engine.update_layer_style(layer_id, updated_style);
|
||||
@@ -1353,9 +1375,151 @@ impl HcieIcedApp {
|
||||
self.refresh_composite_if_needed();
|
||||
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||
}
|
||||
Message::LayerStyleUpdateString(index, param, value) => {
|
||||
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
|
||||
if layer_id != 0 {
|
||||
let styles = self.documents[self.active_doc].engine.get_layer_styles(layer_id);
|
||||
let disc_names = ["DropShadow", "InnerShadow", "OuterGlow", "InnerGlow",
|
||||
"BevelEmboss", "Satin", "ColorOverlay", "GradientOverlay", "PatternOverlay", "Stroke"];
|
||||
if index < disc_names.len() {
|
||||
let disc = disc_names[index];
|
||||
let existing = styles.iter().find(|s| {
|
||||
matches!((disc, s),
|
||||
("BevelEmboss", LayerStyle::BevelEmboss { .. })
|
||||
| ("DropShadow", LayerStyle::DropShadow { .. })
|
||||
| ("InnerShadow", LayerStyle::InnerShadow { .. })
|
||||
)
|
||||
});
|
||||
if let Some(style) = existing {
|
||||
let mut new_style = style.clone();
|
||||
match (&mut new_style, param.as_str()) {
|
||||
(LayerStyle::BevelEmboss { style, .. }, "style") => *style = value,
|
||||
(LayerStyle::BevelEmboss { technique, .. }, "technique") => *technique = value,
|
||||
(LayerStyle::BevelEmboss { direction, .. }, "direction") => *direction = value,
|
||||
(LayerStyle::BevelEmboss { highlight_blend_mode, .. }, "highlight_blend") => *highlight_blend_mode = value,
|
||||
(LayerStyle::BevelEmboss { shadow_blend_mode, .. }, "shadow_blend") => *shadow_blend_mode = value,
|
||||
_ => {}
|
||||
}
|
||||
self.documents[self.active_doc].engine.update_layer_style(layer_id, new_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.refresh_composite_if_needed();
|
||||
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||
}
|
||||
Message::LayerStyleUpdateShadowColor(index, new_color) => {
|
||||
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
|
||||
if layer_id != 0 {
|
||||
let styles = self.documents[self.active_doc].engine.get_layer_styles(layer_id);
|
||||
let disc_names = ["DropShadow", "InnerShadow", "OuterGlow", "InnerGlow",
|
||||
"BevelEmboss", "Satin", "ColorOverlay", "GradientOverlay", "PatternOverlay", "Stroke"];
|
||||
if index < disc_names.len() {
|
||||
let disc = disc_names[index];
|
||||
let existing = styles.iter().find(|s| {
|
||||
matches!((disc, s), ("BevelEmboss", LayerStyle::BevelEmboss { .. }))
|
||||
});
|
||||
if let Some(style) = existing {
|
||||
let mut updated_style = style.clone();
|
||||
if let LayerStyle::BevelEmboss { shadow_color, .. } = &mut updated_style {
|
||||
*shadow_color = new_color;
|
||||
}
|
||||
self.documents[self.active_doc].engine.update_layer_style(layer_id, updated_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
let r = new_color[0] as f32 / 255.0;
|
||||
let g = new_color[1] as f32 / 255.0;
|
||||
let b = new_color[2] as f32 / 255.0;
|
||||
let max = r.max(g).max(b);
|
||||
let min = r.min(g).min(b);
|
||||
let l = (max + min) / 2.0;
|
||||
let s = if max == min { 0.0 } else if l > 0.5 { (max - min) / (2.0 - max - min) } else { (max - min) / (max + min) };
|
||||
let h = if max == min { 0.0 } else if max == r { ((g - b) / (max - min) + if g < b { 6.0 } else { 0.0 }) / 6.0 * 360.0 } else if max == g { ((b - r) / (max - min) + 2.0) / 6.0 * 360.0 } else { ((r - g) / (max - min) + 4.0) / 6.0 * 360.0 };
|
||||
self.style_color_hsl = (h, s, l);
|
||||
self.refresh_composite_if_needed();
|
||||
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||
}
|
||||
Message::LayerStyleUpdateShadowBlend(index, mode) => {
|
||||
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
|
||||
if layer_id != 0 {
|
||||
let styles = self.documents[self.active_doc].engine.get_layer_styles(layer_id);
|
||||
let disc_names = ["DropShadow", "InnerShadow", "OuterGlow", "InnerGlow",
|
||||
"BevelEmboss", "Satin", "ColorOverlay", "GradientOverlay", "PatternOverlay", "Stroke"];
|
||||
if index < disc_names.len() {
|
||||
let disc = disc_names[index];
|
||||
let existing = styles.iter().find(|s| {
|
||||
matches!((disc, s), ("BevelEmboss", LayerStyle::BevelEmboss { .. }))
|
||||
});
|
||||
if let Some(style) = existing {
|
||||
let mut updated_style = style.clone();
|
||||
if let LayerStyle::BevelEmboss { shadow_blend_mode, .. } = &mut updated_style {
|
||||
*shadow_blend_mode = mode;
|
||||
}
|
||||
self.documents[self.active_doc].engine.update_layer_style(layer_id, updated_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.refresh_composite_if_needed();
|
||||
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||
}
|
||||
Message::LayerStyleUpdateShadowOpacity(index, value) => {
|
||||
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
|
||||
if layer_id != 0 {
|
||||
let styles = self.documents[self.active_doc].engine.get_layer_styles(layer_id);
|
||||
let disc_names = ["DropShadow", "InnerShadow", "OuterGlow", "InnerGlow",
|
||||
"BevelEmboss", "Satin", "ColorOverlay", "GradientOverlay", "PatternOverlay", "Stroke"];
|
||||
if index < disc_names.len() {
|
||||
let disc = disc_names[index];
|
||||
let existing = styles.iter().find(|s| {
|
||||
matches!((disc, s), ("BevelEmboss", LayerStyle::BevelEmboss { .. }))
|
||||
});
|
||||
if let Some(style) = existing {
|
||||
let mut updated_style = style.clone();
|
||||
if let LayerStyle::BevelEmboss { shadow_opacity, .. } = &mut updated_style {
|
||||
*shadow_opacity = value;
|
||||
}
|
||||
self.documents[self.active_doc].engine.update_layer_style(layer_id, updated_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.refresh_composite_if_needed();
|
||||
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||
}
|
||||
Message::ShowStyleShadowColorPicker(idx) => {
|
||||
self.show_style_color_picker = true;
|
||||
self.style_color_picker_idx = idx;
|
||||
self.style_color_is_shadow = true;
|
||||
let disc_names = ["DropShadow", "InnerShadow", "OuterGlow", "InnerGlow",
|
||||
"BevelEmboss", "Satin", "ColorOverlay", "GradientOverlay", "PatternOverlay", "Stroke"];
|
||||
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
|
||||
if layer_id != 0 && idx < disc_names.len() {
|
||||
let disc = disc_names[idx];
|
||||
let styles = self.documents[self.active_doc].engine.get_layer_styles(layer_id);
|
||||
if let Some(style) = styles.iter().find(|s| {
|
||||
matches!((disc, s), ("BevelEmboss", LayerStyle::BevelEmboss { .. }))
|
||||
}) {
|
||||
let color = if let LayerStyle::BevelEmboss { shadow_color, .. } = style {
|
||||
*shadow_color
|
||||
} else {
|
||||
[0, 0, 0, 255]
|
||||
};
|
||||
let r = color[0] as f32 / 255.0;
|
||||
let g = color[1] as f32 / 255.0;
|
||||
let b = color[2] as f32 / 255.0;
|
||||
let max = r.max(g).max(b);
|
||||
let min = r.min(g).min(b);
|
||||
let l = (max + min) / 2.0;
|
||||
let s = if max == min { 0.0 } else if l > 0.5 { (max - min) / (2.0 - max - min) } else { (max - min) / (max + min) };
|
||||
let h = if max == min { 0.0 } else if max == r { ((g - b) / (max - min) + if g < b { 6.0 } else { 0.0 }) / 6.0 * 360.0 } else if max == g { ((b - r) / (max - min) + 2.0) / 6.0 * 360.0 } else { ((r - g) / (max - min) + 4.0) / 6.0 * 360.0 };
|
||||
self.style_color_hsl = (h, s, l);
|
||||
}
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
Message::ShowStyleColorPicker(idx) => {
|
||||
self.show_style_color_picker = true;
|
||||
self.style_color_picker_idx = idx;
|
||||
self.style_color_is_shadow = false;
|
||||
// Initialize HSL from current color
|
||||
let disc_names = ["DropShadow", "InnerShadow", "OuterGlow", "InnerGlow",
|
||||
"BevelEmboss", "Satin", "ColorOverlay", "GradientOverlay", "PatternOverlay", "Stroke"];
|
||||
|
||||
@@ -38,8 +38,6 @@ pub enum PaneType {
|
||||
Script,
|
||||
/// AI Chat panel.
|
||||
AiChat,
|
||||
/// Layer Styles panel.
|
||||
LayerStyles,
|
||||
/// Layer Details panel.
|
||||
LayerDetails,
|
||||
/// Geometry panel (vector shapes).
|
||||
@@ -59,7 +57,6 @@ impl PaneType {
|
||||
PaneType::Properties => "Properties",
|
||||
PaneType::Script => "Script",
|
||||
PaneType::AiChat => "AI Assistant",
|
||||
PaneType::LayerStyles => "Layer Styles",
|
||||
PaneType::LayerDetails => "Layer Details",
|
||||
PaneType::Geometry => "Geometry",
|
||||
}
|
||||
@@ -225,7 +222,7 @@ impl DockState {
|
||||
|
||||
if let Some(target) = target_pane {
|
||||
let axis = match pane_type {
|
||||
PaneType::Layers | PaneType::Properties | PaneType::History | PaneType::LayerStyles | PaneType::LayerDetails => {
|
||||
PaneType::Layers | PaneType::Properties | PaneType::History | PaneType::LayerDetails => {
|
||||
iced::widget::pane_grid::Axis::Horizontal
|
||||
}
|
||||
_ => iced::widget::pane_grid::Axis::Vertical,
|
||||
|
||||
@@ -88,20 +88,6 @@ pub fn dock_view<'a>(
|
||||
PaneType::AiChat => {
|
||||
crate::panels::ai_chat_panel::view("", &[], colors)
|
||||
}
|
||||
PaneType::LayerStyles => {
|
||||
let doc = &app.documents[app.active_doc];
|
||||
let styles = doc.engine.get_layer_styles(doc.engine.active_layer_id());
|
||||
// Leak the Vec so the Element can borrow it for the full lifetime.
|
||||
// The leaked memory is reclaimed on next frame when the panel rebuilds.
|
||||
let styles: &'static [hcie_engine_api::LayerStyle] = Box::leak(styles.into_boxed_slice());
|
||||
crate::panels::layer_styles::view(
|
||||
styles, app.selected_style, colors,
|
||||
app.show_style_color_picker,
|
||||
app.style_color_picker_idx,
|
||||
app.style_color_hsl,
|
||||
app.layer_style_offset,
|
||||
)
|
||||
}
|
||||
PaneType::LayerDetails => {
|
||||
let doc = &app.documents[app.active_doc];
|
||||
crate::panels::layer_details::view(&doc.cached_layers, doc.engine.active_layer_id(), colors)
|
||||
@@ -296,7 +282,6 @@ fn pane_type_icon(pane_type: PaneType) -> Option<&'static str> {
|
||||
PaneType::Properties => Some("icons/panel-settings.svg"),
|
||||
PaneType::Script => Some("icons/panel-pen.svg"),
|
||||
PaneType::AiChat => Some("icons/panel-ai.svg"),
|
||||
PaneType::LayerStyles => Some("icons/glow.svg"),
|
||||
PaneType::LayerDetails => Some("icons/panel-layers.svg"),
|
||||
PaneType::Geometry => Some("icons/vector_rect.svg"),
|
||||
}
|
||||
|
||||
@@ -35,6 +35,72 @@ const BLEND_MODES: &[&str] = &[
|
||||
"Subtract", "Divide", "Hue", "Saturation", "Color", "Luminosity",
|
||||
];
|
||||
|
||||
/// Bevel & Emboss style options.
|
||||
const BEVEL_STYLES: &[&str] = &[
|
||||
"Inner Bevel", "Outer Bevel", "Emboss", "Pillow Emboss", "Stroke Emboss",
|
||||
];
|
||||
|
||||
/// Bevel & Emboss technique options.
|
||||
const BEVEL_TECHNIQUES: &[&str] = &[
|
||||
"Smooth", "Chisel Hard", "Chisel Soft",
|
||||
];
|
||||
|
||||
/// Bevel & Emboss direction options.
|
||||
const BEVEL_DIRECTIONS: &[&str] = &[
|
||||
"Up", "Down",
|
||||
];
|
||||
|
||||
/// Bevel & Emboss contour options.
|
||||
const BEVEL_CONTOURS: &[&str] = &[
|
||||
"Linear", "Cone", "Cone-Inverted", "Gaussian", "Half Round",
|
||||
"Round", "Ring", "Ring-Double", "Sawtooth", "Square", "Valley",
|
||||
"Shallow-Slope", "Wave", "Cove", "Washboard",
|
||||
];
|
||||
|
||||
/// Mapping: human-readable BevelEmboss style name -> internal enum string.
|
||||
fn bevel_style_to_internal(label: &str) -> &str {
|
||||
match label {
|
||||
"Inner Bevel" => "InnerBevel",
|
||||
"Outer Bevel" => "OuterBevel",
|
||||
"Emboss" => "Emboss",
|
||||
"Pillow Emboss" => "PillowEmboss",
|
||||
"Stroke Emboss" => "StrokeEmboss",
|
||||
_ => "InnerBevel",
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapping: internal BevelEmboss style enum string -> human-readable label.
|
||||
fn bevel_style_to_label(internal: &str) -> &str {
|
||||
match internal {
|
||||
"InnerBevel" => "Inner Bevel",
|
||||
"OuterBevel" => "Outer Bevel",
|
||||
"Emboss" => "Emboss",
|
||||
"PillowEmboss" => "Pillow Emboss",
|
||||
"StrokeEmboss" => "Stroke Emboss",
|
||||
_ => "Inner Bevel",
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapping: human-readable technique label -> internal enum string.
|
||||
fn bevel_technique_to_internal(label: &str) -> &str {
|
||||
match label {
|
||||
"Smooth" => "Smooth",
|
||||
"Chisel Hard" => "ChiselHard",
|
||||
"Chisel Soft" => "ChiselSoft",
|
||||
_ => "Smooth",
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapping: internal technique enum string -> human-readable label.
|
||||
fn bevel_technique_to_label(internal: &str) -> &str {
|
||||
match internal {
|
||||
"Smooth" => "Smooth",
|
||||
"ChiselHard" => "Chisel Hard",
|
||||
"ChiselSoft" => "Chisel Soft",
|
||||
_ => "Smooth",
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a LayerStyle matches a discriminant name.
|
||||
fn style_matches(discriminant: &str, style: &LayerStyle) -> bool {
|
||||
matches!(
|
||||
@@ -399,12 +465,119 @@ fn build_style_params<'a>(
|
||||
params = params.push(slider_row("Size", size, 0.0..=250.0, move |v| Message::LayerStyleUpdateParam(selected, "size".to_string(), v), colors));
|
||||
}
|
||||
"BevelEmboss" => {
|
||||
let (depth, size, angle, altitude, soften) = match style {
|
||||
Some(LayerStyle::BevelEmboss { depth, size, angle, altitude, soften, .. }) =>
|
||||
(*depth, *size, *angle, *altitude, *soften),
|
||||
_ => (1.0, 5.0, 120.0, 30.0, 0.0),
|
||||
let (depth, size, angle, altitude, soften, style_str, technique_str, direction_str,
|
||||
highlight_opacity, shadow_opacity, hl_color, sh_color, hl_blend, sh_blend) = match style {
|
||||
Some(LayerStyle::BevelEmboss {
|
||||
depth, size, angle, altitude, soften, style, technique, direction,
|
||||
highlight_opacity, shadow_opacity, highlight_color, shadow_color,
|
||||
highlight_blend_mode, shadow_blend_mode, ..
|
||||
}) => (*depth, *size, *angle, *altitude, *soften,
|
||||
style.clone(), technique.clone(), direction.clone(),
|
||||
*highlight_opacity, *shadow_opacity, *highlight_color, *shadow_color,
|
||||
highlight_blend_mode.clone(), shadow_blend_mode.clone()),
|
||||
_ => (1.0, 5.0, 120.0, 30.0, 0.0,
|
||||
"InnerBevel".to_string(), "Smooth".to_string(), "Up".to_string(),
|
||||
0.75, 0.75, [255, 255, 255, 255], [0, 0, 0, 255],
|
||||
"Screen".to_string(), "Multiply".to_string()),
|
||||
};
|
||||
params = params.push(slider_row("Depth", depth, 0.0..=10.0, move |v| Message::LayerStyleUpdateParam(selected, "depth".to_string(), v), colors));
|
||||
|
||||
// ── Structure section: Style, Technique, Direction ──
|
||||
let style_label_str = bevel_style_to_label(&style_str);
|
||||
let style_static: &'static str = Box::leak(style_label_str.to_string().into_boxed_str());
|
||||
let style_list = pick_list(BEVEL_STYLES, Some(style_static), move |v: &str| {
|
||||
Message::LayerStyleUpdateString(selected, "style".to_string(), bevel_style_to_internal(v).to_string())
|
||||
}).text_size(10);
|
||||
|
||||
let tech_label_str = bevel_technique_to_label(&technique_str);
|
||||
let tech_static: &'static str = Box::leak(tech_label_str.to_string().into_boxed_str());
|
||||
let tech_list = pick_list(BEVEL_TECHNIQUES, Some(tech_static), move |v: &str| {
|
||||
Message::LayerStyleUpdateString(selected, "technique".to_string(), bevel_technique_to_internal(v).to_string())
|
||||
}).text_size(10);
|
||||
|
||||
let dir_static: &'static str = Box::leak(direction_str.clone().into_boxed_str());
|
||||
let dir_list = pick_list(BEVEL_DIRECTIONS, Some(dir_static), move |v: &str| {
|
||||
Message::LayerStyleUpdateString(selected, "direction".to_string(), v.to_string())
|
||||
}).text_size(10);
|
||||
|
||||
params = params.push(row![text("Style:").size(10), style_list, Space::with_width(Length::Fixed(10.0)),
|
||||
text("Tech:").size(10), tech_list, Space::with_width(Length::Fixed(10.0)),
|
||||
text("Dir:").size(10), dir_list].spacing(3).align_y(iced::Alignment::Center));
|
||||
|
||||
params = params.push(horizontal_rule(1));
|
||||
|
||||
// ── Shading section: Highlight ──
|
||||
params = params.push(text("Shading").size(10).font(iced::Font::MONOSPACE));
|
||||
|
||||
// Highlight blend mode dropdown
|
||||
let hl_blend_static: &'static str = Box::leak(hl_blend.clone().into_boxed_str());
|
||||
let hl_blend_list = pick_list(BLEND_MODES, Some(hl_blend_static), move |mode: &str| {
|
||||
Message::LayerStyleUpdateString(selected, "highlight_blend".to_string(), mode.to_string())
|
||||
}).width(Length::Fixed(110.0)).text_size(10);
|
||||
|
||||
params = params.push(row![text("Hl Mode:").size(10), hl_blend_list].spacing(3).align_y(iced::Alignment::Center));
|
||||
|
||||
// Highlight opacity slider
|
||||
params = params.push(slider_row("Hl Opacity", highlight_opacity, 0.0..=1.0, move |v| Message::LayerStyleUpdateParam(selected, "highlight_opacity".to_string(), v), colors));
|
||||
|
||||
// Highlight color swatch
|
||||
let hc = hl_color;
|
||||
let hl_swatch_bg = container(text(""))
|
||||
.width(32).height(24)
|
||||
.style(move |_theme| iced::widget::container::Style {
|
||||
background: Some(iced::Background::Color(iced::Color::from_rgba(
|
||||
hc[0] as f32 / 255.0, hc[1] as f32 / 255.0, hc[2] as f32 / 255.0, 1.0,
|
||||
))),
|
||||
border: iced::Border::default().color(iced::Color::from_rgba(1.0, 1.0, 1.0, 0.15)).width(1).rounded(4),
|
||||
shadow: iced::Shadow { color: iced::Color::from_rgba(0.0, 0.0, 0.0, 0.6), offset: iced::Vector::new(0.0, 1.0), blur_radius: 4.0 },
|
||||
..Default::default()
|
||||
});
|
||||
let hl_swatch_clickable = iced::widget::mouse_area(hl_swatch_bg)
|
||||
.on_press(Message::ShowStyleColorPicker(selected))
|
||||
.interaction(iced::mouse::Interaction::Pointer);
|
||||
params = params.push(row![text("Hl Color:").size(10), hl_swatch_clickable].spacing(4).align_y(iced::Alignment::Center));
|
||||
|
||||
// ── Shadow ──
|
||||
// Shadow blend mode dropdown
|
||||
let sh_blend_static: &'static str = Box::leak(sh_blend.clone().into_boxed_str());
|
||||
let sh_blend_list = pick_list(BLEND_MODES, Some(sh_blend_static), move |mode: &str| {
|
||||
Message::LayerStyleUpdateShadowBlend(selected, mode.to_string())
|
||||
}).width(Length::Fixed(110.0)).text_size(10);
|
||||
|
||||
params = params.push(row![text("Sh Mode:").size(10), sh_blend_list].spacing(3).align_y(iced::Alignment::Center));
|
||||
|
||||
// Shadow opacity slider
|
||||
params = params.push(slider_row("Sh Opacity", shadow_opacity, 0.0..=1.0, move |v| Message::LayerStyleUpdateShadowOpacity(selected, v), colors));
|
||||
|
||||
// Shadow color swatch
|
||||
let sc = sh_color;
|
||||
let sh_swatch_bg = container(text(""))
|
||||
.width(32).height(24)
|
||||
.style(move |_theme| iced::widget::container::Style {
|
||||
background: Some(iced::Background::Color(iced::Color::from_rgba(
|
||||
sc[0] as f32 / 255.0, sc[1] as f32 / 255.0, sc[2] as f32 / 255.0, 1.0,
|
||||
))),
|
||||
border: iced::Border::default().color(iced::Color::from_rgba(1.0, 1.0, 1.0, 0.15)).width(1).rounded(4),
|
||||
shadow: iced::Shadow { color: iced::Color::from_rgba(0.0, 0.0, 0.0, 0.6), offset: iced::Vector::new(0.0, 1.0), blur_radius: 4.0 },
|
||||
..Default::default()
|
||||
});
|
||||
let sh_swatch_clickable = iced::widget::mouse_area(sh_swatch_bg)
|
||||
.on_press(Message::ShowStyleShadowColorPicker(selected))
|
||||
.interaction(iced::mouse::Interaction::Pointer);
|
||||
params = params.push(row![text("Sh Color:").size(10), sh_swatch_clickable].spacing(4).align_y(iced::Alignment::Center));
|
||||
|
||||
params = params.push(horizontal_rule(1));
|
||||
|
||||
// ── Geometry section ──
|
||||
params = params.push(text("Geometry").size(10).font(iced::Font::MONOSPACE));
|
||||
|
||||
// Contour dropdown
|
||||
let contour_static: &'static str = Box::leak("Linear".to_string().into_boxed_str());
|
||||
let contour_list: iced::widget::PickList<&str, &[&str], &str, Message> = pick_list(BEVEL_CONTOURS, Some(contour_static), move |v: &str| {
|
||||
Message::LayerStyleUpdateString(selected, "contour".to_string(), v.to_string())
|
||||
}).width(Length::Fixed(120.0)).text_size(10);
|
||||
params = params.push(row![text("Contour:").size(10), contour_list].spacing(3));
|
||||
|
||||
params = params.push(slider_row("Depth", depth, 0.0..=500.0, move |v| Message::LayerStyleUpdateParam(selected, "depth".to_string(), v), colors));
|
||||
params = params.push(slider_row("Size", size, 0.0..=250.0, move |v| Message::LayerStyleUpdateParam(selected, "size".to_string(), v), colors));
|
||||
params = params.push(slider_row("Angle", angle, 0.0..=360.0, move |v| Message::LayerStyleUpdateParam(selected, "angle".to_string(), v), colors));
|
||||
params = params.push(slider_row("Altitude", altitude, 0.0..=90.0, move |v| Message::LayerStyleUpdateParam(selected, "altitude".to_string(), v), colors));
|
||||
@@ -538,9 +711,11 @@ fn slider_row<'a>(
|
||||
let value_label = text(format!("{:.1}", value)).size(9).width(Length::Fixed(40.0));
|
||||
let label_text = text(label).size(10).width(Length::Fixed(70.0));
|
||||
|
||||
let step = if *range.end() <= 1.0 { 0.01 } else { 1.0 };
|
||||
|
||||
row![
|
||||
label_text,
|
||||
slider(range, value, on_change).step(0.1).width(Length::Fill),
|
||||
slider(range, value, on_change).step(step).width(Length::Fill),
|
||||
value_label,
|
||||
]
|
||||
.spacing(3)
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "bmp", "gif", "tiff", "ico", "pnm", "qoi", "ff", "hdr", "dds", "avif", "avif-native", "tga", "exr"] }
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "bmp", "gif", "tiff", "ico", "pnm", "qoi", "ff", "hdr", "dds", "avif", "tga", "exr"] }
|
||||
log = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
rand = "0.8"
|
||||
@@ -34,3 +34,4 @@ zip = { version = "2.2", default-features = false, features = ["deflate"] }
|
||||
|
||||
|
||||
|
||||
rayon = "1.10"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! layer records with the original.
|
||||
|
||||
fn main() {
|
||||
let input_path = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_test_images/example3/Example3-mini.psd";
|
||||
let output_path = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_test_images/example3/Example3-mini-hcie-v2.psd";
|
||||
let input_path = "/mnt/extra/00_PROJECTS/hcie-rust-_images/_test_images/example3/Example3-mini.psd";
|
||||
let output_path = "/mnt/extra/00_PROJECTS/hcie-rust-_images/_test_images/example3/Example3-mini-hcie-v2.psd";
|
||||
|
||||
println!("Reading original PSD...");
|
||||
let orig_bytes = std::fs::read(input_path).unwrap();
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::fs;
|
||||
use hcie_io::psd::PsdLoader;
|
||||
use hcie_fx::generate_bevel_tuned;
|
||||
use image::{RgbaImage, GenericImageView};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let base_dir = "_tmp/psd_tunes/bevel_emboss_png_set";
|
||||
let mut samples = vec![];
|
||||
for entry in fs::read_dir(base_dir).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if path.extension().unwrap() == "png" {
|
||||
let name = path.file_stem().unwrap().to_str().unwrap().to_string();
|
||||
samples.push((name, path));
|
||||
}
|
||||
}
|
||||
|
||||
let mut inner_mae = 0.0;
|
||||
let mut inner_count = 0;
|
||||
let mut outer_mae = 0.0;
|
||||
let mut outer_count = 0;
|
||||
let mut emboss_mae = 0.0;
|
||||
let mut emboss_count = 0;
|
||||
|
||||
let lb = 15;
|
||||
let hl = 1.0;
|
||||
let sh = 1.0;
|
||||
|
||||
println!("Evaluating {} samples", samples.len());
|
||||
for (name, png_path) in samples {
|
||||
let psd_name = name.split("_").next().unwrap().to_string() + ".psd";
|
||||
let psd_path = format!("_tmp/psd_tunes/{}", psd_name);
|
||||
if !Path::new(&psd_path).exists() { continue; }
|
||||
|
||||
let target = image::open(&png_path).unwrap().to_rgba8();
|
||||
let mut psd = PsdLoader::new(&psd_path).unwrap();
|
||||
let layers = psd.get_layers();
|
||||
|
||||
let mut layer = None;
|
||||
for l in layers {
|
||||
if l.effects.is_some() && l.effects.as_ref().unwrap().bevel_emboss.is_some() {
|
||||
layer = Some(l);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if layer.is_none() { continue; }
|
||||
let layer = layer.unwrap();
|
||||
let fx = layer.effects.unwrap();
|
||||
let bevel = fx.bevel_emboss.unwrap();
|
||||
|
||||
use hcie_fx::types::*;
|
||||
let mut style = BevelStyle::InnerBevel;
|
||||
if name.contains("InnerBevel") { style = BevelStyle::InnerBevel; }
|
||||
if name.contains("OuterBevel") { style = BevelStyle::OuterBevel; }
|
||||
if name.contains("Emboss") { style = BevelStyle::Emboss; }
|
||||
|
||||
let (out_w, out_h, out_pixels) = generate_bevel_tuned(
|
||||
layer.width as usize, layer.height as usize, &layer.rgba,
|
||||
bevel.size as f32, bevel.depth as f32, bevel.soften as f32, bevel.angle as f32, bevel.altitude as f32,
|
||||
style, lb, hl, sh
|
||||
);
|
||||
|
||||
let mut diff_sum = 0.0;
|
||||
let mut px_count = 0;
|
||||
|
||||
let offset_x = (out_w as i32 - target.width() as i32) / 2;
|
||||
let offset_y = (out_h as i32 - target.height() as i32) / 2;
|
||||
|
||||
for y in 0..target.height() {
|
||||
for x in 0..target.width() {
|
||||
let sx = x as i32 + offset_x;
|
||||
let sy = y as i32 + offset_y;
|
||||
if sx >= 0 && sx < out_w as i32 && sy >= 0 && sy < out_h as i32 {
|
||||
let s_idx = ((sy as usize) * out_w + (sx as usize)) * 4;
|
||||
let t_idx = ((y as usize) * target.width() as usize + (x as usize)) * 4;
|
||||
|
||||
let src_a = layer.rgba[s_idx+3];
|
||||
if src_a > 0 {
|
||||
diff_sum += (out_pixels[s_idx] as f32 - target[t_idx] as f32).abs();
|
||||
diff_sum += (out_pixels[s_idx+1] as f32 - target[t_idx+1] as f32).abs();
|
||||
diff_sum += (out_pixels[s_idx+2] as f32 - target[t_idx+2] as f32).abs();
|
||||
px_count += 3; // R, G, B
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if px_count > 0 {
|
||||
let mae = diff_sum / px_count as f32;
|
||||
if name.contains("InnerBevel") { inner_mae += mae; inner_count += 1; }
|
||||
if name.contains("OuterBevel") { outer_mae += mae; outer_count += 1; }
|
||||
if name.contains("Emboss") { emboss_mae += mae; emboss_count += 1; }
|
||||
}
|
||||
}
|
||||
|
||||
println!("Inner Bevel MAE: {:.4} ({} samples)", inner_mae / inner_count as f32, inner_count);
|
||||
println!("Outer Bevel MAE: {:.4} ({} samples)", outer_mae / outer_count as f32, outer_count);
|
||||
println!("Emboss MAE: {:.4} ({} samples)", emboss_mae / emboss_count as f32, emboss_count);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use image::GenericImageView;
|
||||
|
||||
fn main() {
|
||||
let ref_img = image::open("_images/_psd_stil_test/test_2/Emboss.png").unwrap().to_rgba8();
|
||||
let test_img = image::open("/tmp/test2_Emboss.png").unwrap().to_rgba8();
|
||||
|
||||
let mut diff_sum = 0.0;
|
||||
let mut max_diff = 0;
|
||||
let mut px = 0;
|
||||
|
||||
let mut printed = 0;
|
||||
|
||||
for y in 0..ref_img.height() {
|
||||
for x in 0..ref_img.width() {
|
||||
let rp = ref_img.get_pixel(x, y);
|
||||
let tp = test_img.get_pixel(x, y);
|
||||
|
||||
let d = (rp[0] as i32 - tp[0] as i32).abs() +
|
||||
(rp[1] as i32 - tp[1] as i32).abs() +
|
||||
(rp[2] as i32 - tp[2] as i32).abs() +
|
||||
(rp[3] as i32 - tp[3] as i32).abs();
|
||||
|
||||
diff_sum += d as f64;
|
||||
max_diff = max_diff.max(d);
|
||||
px += 4;
|
||||
|
||||
if d > 100 && printed < 20 {
|
||||
println!("x: {}, y: {} | REF: {:?} | TEST: {:?}", x, y, rp, tp);
|
||||
printed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Avg diff per channel: {:.2}", diff_sum / px as f64);
|
||||
println!("Max pixel diff: {}", max_diff);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
#![allow(dead_code, unused_imports, unused_variables, unused_macros)]
|
||||
/// Visual inspection tool for Bevel & Emboss rendering.
|
||||
///
|
||||
/// Loads a PSD from `_tmp/psd_tunes/bevel_emboss_psd_set/`, applies the
|
||||
/// current `generate_bevel_emboss` implementation, composites highlight/shadow
|
||||
/// over the layer fill, and writes the result to `_tmp/emboss_inspect/` so we
|
||||
/// can compare side-by-side with the Photoshop ground truth PNG.
|
||||
///
|
||||
/// Usage:
|
||||
/// cargo run --example inspect_emboss -p hcie-io -- <stem>
|
||||
/// cargo run --example inspect_emboss -p hcie-io -- be_L_emboss_d100_sz10_sf0_down_alt30_smooth
|
||||
///
|
||||
/// If no stem is given, runs a small representative subset.
|
||||
|
||||
use hcie_fx::types::{BevelStyle, Direction, Technique};
|
||||
use hcie_blend::BlendMode;
|
||||
|
||||
const PSD_DIR: &str = "_tmp/psd_tunes/bevel_emboss_psd_set";
|
||||
const PNG_DIR: &str = "_tmp/psd_tunes/bevel_emboss_png_set";
|
||||
const OUT_DIR: &str = "_tmp/emboss_inspect";
|
||||
|
||||
fn parse_stem(stem: &str) -> Option<(String, String, f32, f32, f32, String, f32, String)> {
|
||||
// be_{shape}_{style}_d{depth}_sz{size}_sf{soften}_{dir}_alt{altitude}_{technique}
|
||||
let parts: Vec<&str> = stem.split('_').collect();
|
||||
if parts.len() < 9 || parts[0] != "be" { return None; }
|
||||
let shape = parts[1].to_string();
|
||||
let style = parts[2].to_string();
|
||||
let depth = parts[3].strip_prefix('d')?.parse::<f32>().ok()?;
|
||||
let size = parts[4].strip_prefix("sz")?.parse::<f32>().ok()?;
|
||||
let soften = parts[5].strip_prefix("sf")?.parse::<f32>().ok()?;
|
||||
let direction = parts[6].to_string();
|
||||
let altitude = parts[7].strip_prefix("alt")?.parse::<f32>().ok()?;
|
||||
let technique = parts[8].to_string();
|
||||
Some((shape, style, depth, size, soften, direction, altitude, technique))
|
||||
}
|
||||
|
||||
fn render_one(stem: &str) {
|
||||
let params = match parse_stem(stem) {
|
||||
Some(p) => p,
|
||||
None => { eprintln!(" cannot parse stem: {}", stem); return; }
|
||||
};
|
||||
let (shape, style_str, depth, size, soften, direction_str, altitude, _technique) = params;
|
||||
|
||||
let style_enum = match style_str.as_str() {
|
||||
"emboss" => BevelStyle::Emboss,
|
||||
"outer" => BevelStyle::OuterBevel,
|
||||
_ => BevelStyle::InnerBevel,
|
||||
};
|
||||
let dir_enum = match direction_str.as_str() {
|
||||
"down" => Direction::Down,
|
||||
_ => Direction::Up,
|
||||
};
|
||||
|
||||
let psd_path = format!("{}/{}.psd", PSD_DIR, stem);
|
||||
let png_path = format!("{}/{}.png", PNG_DIR, stem);
|
||||
|
||||
let layers = match hcie_psd::import_psd(std::path::Path::new(&psd_path)) {
|
||||
Ok(l) => l,
|
||||
Err(e) => { eprintln!(" SKIP {}: {}", stem, e); return; }
|
||||
};
|
||||
|
||||
// Find the layer with effects
|
||||
let layer = match layers.iter().find(|l| !l.effects.is_empty()) {
|
||||
Some(l) => l,
|
||||
None => { eprintln!(" SKIP {}: no effects layer", stem); return; }
|
||||
};
|
||||
|
||||
let w = layer.width;
|
||||
let h = layer.height;
|
||||
let px = (w * h) as usize;
|
||||
|
||||
// Extract alpha from layer pixels
|
||||
let mut alpha = vec![0u8; px];
|
||||
for i in 0..px {
|
||||
alpha[i] = layer.pixels[i * 4 + 3];
|
||||
}
|
||||
|
||||
// Get the bevel params from the parsed effect — but we'll use apply_layer_effects
|
||||
// for the full pipeline (inside + behind split for emboss/outerbevel)
|
||||
let _ = {
|
||||
let mut angle = 120.0f32;
|
||||
let mut hl_color = [255u8; 4];
|
||||
let mut sh_color = [0u8; 4];
|
||||
for e in &layer.effects {
|
||||
if let hcie_protocol::effects::LayerEffect::BevelEmboss {
|
||||
angle: a, highlight_color, shadow_color, ..
|
||||
} = e
|
||||
{
|
||||
angle = *a;
|
||||
hl_color = *highlight_color;
|
||||
sh_color = *shadow_color;
|
||||
}
|
||||
}
|
||||
(angle, hl_color, sh_color)
|
||||
};
|
||||
|
||||
// Convert protocol effects to hcie-fx effects and run the full pipeline
|
||||
let fx_effects: Vec<hcie_fx::types::LayerEffect> = layer.effects.iter().map(|e| {
|
||||
hcie_fx::types::protocol_to_hcie_fx_effect(e)
|
||||
}).collect();
|
||||
|
||||
// Use apply_layer_effects for the full rendering pipeline
|
||||
let out = hcie_fx::apply_layer_effects(
|
||||
&layer.pixels, w, h,
|
||||
&fx_effects,
|
||||
layer.fill_opacity,
|
||||
);
|
||||
|
||||
// Compute MAE against reference
|
||||
let ref_img = match image::open(&png_path) {
|
||||
Ok(i) => i.to_rgba8(),
|
||||
Err(e) => { eprintln!(" SKIP ref {}: {}", stem, e); return; }
|
||||
};
|
||||
let (rw, rh) = (ref_img.width(), ref_img.height());
|
||||
let ref_raw = ref_img.into_raw();
|
||||
|
||||
let mut diff_sum = 0.0f64;
|
||||
let mut content_pixels = 0u64;
|
||||
for i in 0..px {
|
||||
let ra = ref_raw[i * 4 + 3];
|
||||
let oa = out[i * 4 + 3];
|
||||
if ra > 0 || oa > 0 {
|
||||
content_pixels += 1;
|
||||
for c in 0..4 {
|
||||
diff_sum += (out[i * 4 + c] as i32 - ref_raw[i * 4 + c] as i32).abs() as f64;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mae = if content_pixels > 0 {
|
||||
diff_sum / content_pixels as f64 / 4.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Save our rendering
|
||||
let out_img: image::RgbaImage = image::ImageBuffer::from_raw(w, h, out.clone()).unwrap();
|
||||
let out_path = format!("{}/{}.png", OUT_DIR, stem);
|
||||
let _ = out_img.save(&out_path);
|
||||
|
||||
// Also save a side-by-side comparison
|
||||
let combined_w = w + rw + 4;
|
||||
let combined_h = h.max(rh);
|
||||
let mut combined = image::RgbaImage::new(combined_w, combined_h);
|
||||
// our output (left)
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let i = (y * w + x) as usize * 4;
|
||||
combined.put_pixel(x, y, image::Rgba([out[i], out[i+1], out[i+2], out[i+3]]));
|
||||
}
|
||||
}
|
||||
// ref (right, after 4px gap)
|
||||
for y in 0..rh {
|
||||
for x in 0..rw {
|
||||
let i = (y * rw + x) as usize * 4;
|
||||
combined.put_pixel(w as u32 + 4 + x, y, image::Rgba([ref_raw[i], ref_raw[i+1], ref_raw[i+2], ref_raw[i+3]]));
|
||||
}
|
||||
}
|
||||
let cmp_path = format!("{}/{}_cmp.png", OUT_DIR, stem);
|
||||
let _ = combined.save(&cmp_path);
|
||||
|
||||
println!(" {} : MAE={:.2} (saved {})", stem, mae, cmp_path);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = std::fs::create_dir_all(OUT_DIR);
|
||||
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
// Render specific stems
|
||||
for stem in &args[1..] {
|
||||
render_one(stem);
|
||||
}
|
||||
} else {
|
||||
// Representative subset: different shapes, styles, depths, sizes
|
||||
let subset = [
|
||||
"be_L_emboss_d100_sz10_sf0_down_alt30_smooth",
|
||||
"be_L_emboss_d100_sz25_sf0_up_alt60_smooth",
|
||||
"be_L_inner_d100_sz10_sf0_down_alt30_smooth",
|
||||
"be_L_inner_d100_sz25_sf0_up_alt60_smooth",
|
||||
"be_rect_emboss_d100_sz10_sf0_down_alt30_smooth",
|
||||
"be_rect_inner_d100_sz10_sf0_down_alt30_smooth",
|
||||
"be_circle_emboss_d100_sz10_sf0_down_alt30_smooth",
|
||||
"be_circle_inner_d100_sz10_sf0_down_alt30_smooth",
|
||||
"be_star_emboss_d200_sz25_sf5_up_alt60_smooth",
|
||||
"be_star_inner_d200_sz25_sf5_up_alt60_smooth",
|
||||
];
|
||||
println!("Rendering {} representative samples to {}...", subset.len(), OUT_DIR);
|
||||
for stem in &subset {
|
||||
render_one(stem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let orig_path = Path::new("/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_test_images/example3/Example3-mini.psd");
|
||||
let orig_path = Path::new("/mnt/extra/00_PROJECTS/hcie-rust-_images/_test_images/example3/Example3-mini.psd");
|
||||
let saved_path = Path::new("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/Example3-mini-roundtrip.psd");
|
||||
|
||||
let orig_layers = hcie_psd::import_psd(orig_path).unwrap();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fn main() {
|
||||
let orig = std::fs::read("/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_test_images/example3/Example3-mini.psd").unwrap();
|
||||
let orig = std::fs::read("/mnt/extra/00_PROJECTS/hcie-rust-_images/_test_images/example3/Example3-mini.psd").unwrap();
|
||||
let saved = std::fs::read("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/Example3-mini-roundtrip.psd").unwrap();
|
||||
|
||||
let orig_psd = hcie_psd::Psd::from_bytes(&orig).unwrap();
|
||||
|
||||
@@ -1,88 +1,38 @@
|
||||
#![allow(dead_code, unused_imports, unused_variables, unused_macros)]
|
||||
/// Grid-search Bevel & Emboss MAE tuning against Photoshop ground-truth PNGs.
|
||||
///
|
||||
/// Expects `_tmp/bevel_emboss_png_set/` with transparent PNGs exported
|
||||
/// from `_tmp/bevel_emboss_psd_set/`.
|
||||
/// Expects `_tmp/psd_tunes/bevel_emboss_png_set/` with transparent PNGs exported
|
||||
/// from `_tmp/psd_tunes/bevel_emboss_psd_set/`.
|
||||
///
|
||||
/// Uses the full `apply_layer_effects` pipeline (including inside/behind split
|
||||
/// for Emboss/OuterBevel) so the tuning matches real rendering behavior.
|
||||
///
|
||||
/// Usage:
|
||||
/// cargo run --example tune_mae_bevel_emboss_grid -p hcie-io --release
|
||||
|
||||
use std::fs;
|
||||
use rayon::prelude::*;
|
||||
use std::io::Write;
|
||||
|
||||
const GT_DIR: &str = "_tmp/bevel_emboss_png_set";
|
||||
const CANVAS: u32 = 256;
|
||||
const MARGIN: u32 = 64;
|
||||
const RESULTS_FILE: &str = "bevel_emboss_grid_results_v3.txt";
|
||||
const GT_DIR: &str = "_tmp/psd_tunes/bevel_emboss_png_set";
|
||||
const RESULTS_FILE: &str = "bevel_emboss_grid_results_v4.txt";
|
||||
|
||||
struct Sample {
|
||||
name: String,
|
||||
shape: String,
|
||||
style: String,
|
||||
depth: f32,
|
||||
size: f32,
|
||||
soften: f32,
|
||||
direction: String,
|
||||
altitude: f32,
|
||||
angle: f32,
|
||||
ref_pixels: Vec<u8>,
|
||||
content_mask: Vec<bool>,
|
||||
}
|
||||
|
||||
fn make_shape_alpha(shape: &str) -> Vec<u8> {
|
||||
let n = (CANVAS * CANVAS) as usize;
|
||||
let mut a = vec![0u8; n];
|
||||
let cx = CANVAS / 2;
|
||||
let cy = CANVAS / 2;
|
||||
|
||||
for y in 0..CANVAS {
|
||||
for x in 0..CANVAS {
|
||||
let inside = match shape {
|
||||
"rect" => x >= 64 && x < CANVAS - 64 && y >= 64 && y < CANVAS - 64,
|
||||
"tall" => x >= 96 && x < CANVAS - 96 && y >= 32 && y < CANVAS - 32,
|
||||
"wide" => x >= 32 && x < CANVAS - 32 && y >= 96 && y < CANVAS - 96,
|
||||
"circle" => {
|
||||
let dx = x as f32 - cx as f32;
|
||||
let dy = y as f32 - cy as f32;
|
||||
let r = (CANVAS / 2 - 64) as f32;
|
||||
(dx * dx + dy * dy).sqrt() <= r
|
||||
}
|
||||
"triangle" => {
|
||||
let dx = (x as i32 - cx as i32).abs();
|
||||
let top = 64i32;
|
||||
let bottom = (CANVAS - 64) as i32;
|
||||
if (y as i32) < top || (y as i32) > bottom { false } else {
|
||||
let y_span = ((y as i32) - top) as f32 / (bottom - top) as f32;
|
||||
let half_width_at_y = (CANVAS - 128) as f32 / 2.0 * (1.0 - y_span);
|
||||
(dx as f32) <= half_width_at_y
|
||||
}
|
||||
}
|
||||
"star" => {
|
||||
let dx = x as f32 - cx as f32;
|
||||
let dy = y as f32 - cy as f32;
|
||||
let r = (dx * dx + dy * dy).sqrt();
|
||||
if r > 96.0 || r < 20.0 { false } else {
|
||||
let angle = dy.atan2(dx);
|
||||
let sector = (angle * 5.0).cos();
|
||||
let limit = 64.0 + sector * 32.0;
|
||||
r <= limit
|
||||
}
|
||||
}
|
||||
"L" => {
|
||||
(x >= 64 && x < 96 && y >= 64 && y < CANVAS - 64)
|
||||
|| (x >= 64 && x < CANVAS - 64 && y >= CANVAS - 96 && y < CANVAS - 64)
|
||||
}
|
||||
"T" => {
|
||||
(y >= 64 && y < 96 && x >= 64 && x < CANVAS - 64)
|
||||
|| (x >= 112 && x < 144 && y >= 64 && y < CANVAS - 64)
|
||||
}
|
||||
"cross" => {
|
||||
(x >= 112 && x < 144 && y >= 48 && y < CANVAS - 48)
|
||||
|| (y >= 112 && y < 144 && x >= 48 && x < CANVAS - 48)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if inside {
|
||||
a[(y * CANVAS + x) as usize] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
a
|
||||
layer_pixels: Vec<u8>,
|
||||
fx_effects: Vec<hcie_fx::types::LayerEffect>,
|
||||
fill_opacity: f32,
|
||||
w: u32,
|
||||
h: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -109,7 +59,7 @@ fn main() {
|
||||
None => { eprintln!(" SKIP: {}", stem); continue; }
|
||||
};
|
||||
|
||||
let (shape, style, depth, size, soften, direction, altitude, _technique) = params;
|
||||
let (_shape, style, depth, size, soften, direction, altitude, _technique) = params;
|
||||
|
||||
let img = match image::open(&path) {
|
||||
Ok(i) => i,
|
||||
@@ -118,17 +68,52 @@ fn main() {
|
||||
|
||||
let rgba = img.to_rgba8();
|
||||
let raw = rgba.into_raw();
|
||||
let npix = (CANVAS * CANVAS) as usize;
|
||||
let w = img.width();
|
||||
let h = img.height();
|
||||
let npix = (w * h) as usize;
|
||||
let mut content_mask = vec![false; npix];
|
||||
for i in 0..npix {
|
||||
content_mask[i] = raw[i * 4 + 3] > 0;
|
||||
}
|
||||
|
||||
let psd_path_str = format!("_tmp/psd_tunes/bevel_emboss_psd_set/{}.psd", stem);
|
||||
let (layer_pixels, fx_effects, fill_opacity, angle) = if let Ok(layers) = hcie_psd::import_psd(std::path::Path::new(&psd_path_str)) {
|
||||
let mut found = None;
|
||||
for l in layers {
|
||||
if !l.effects.is_empty() {
|
||||
let fx_effects: Vec<hcie_fx::types::LayerEffect> = l.effects.iter()
|
||||
.map(|e| hcie_fx::types::protocol_to_hcie_fx_effect(e))
|
||||
.collect();
|
||||
// Extract angle from BevelEmboss effect
|
||||
let mut angle = 120.0f32;
|
||||
for e in &l.effects {
|
||||
if let hcie_protocol::effects::LayerEffect::BevelEmboss { angle: a, .. } = e {
|
||||
angle = *a;
|
||||
}
|
||||
}
|
||||
found = Some((l.pixels, fx_effects, l.fill_opacity, angle));
|
||||
break;
|
||||
}
|
||||
}
|
||||
match found {
|
||||
Some(v) => v,
|
||||
None => { eprintln!(" SKIP no effects layer: {}", stem); continue; }
|
||||
}
|
||||
} else {
|
||||
eprintln!(" SKIP PSD missing for {}: {}", stem, psd_path_str);
|
||||
continue;
|
||||
};
|
||||
|
||||
samples.push(Sample {
|
||||
name: stem,
|
||||
shape, style, depth, size, soften, direction, altitude,
|
||||
style, depth, size, soften, direction, altitude,
|
||||
angle,
|
||||
ref_pixels: raw,
|
||||
content_mask,
|
||||
layer_pixels,
|
||||
fx_effects,
|
||||
fill_opacity,
|
||||
w, h,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,42 +124,33 @@ fn main() {
|
||||
}
|
||||
|
||||
let content_count = samples[0].content_mask.iter().filter(|&&b| b).count();
|
||||
println!("Content pixels per image: {} / {} ({:.1}%)",
|
||||
content_count, npix(), content_count as f64 / npix() as f64 * 100.0);
|
||||
println!("Content pixels per image: {}", content_count);
|
||||
|
||||
let rect_alpha: Vec<u8> = make_shape_alpha("rect");
|
||||
let circle_alpha: Vec<u8> = make_shape_alpha("circle");
|
||||
// Use all samples (inner bevel, outer bevel, emboss) for combined tuning
|
||||
|
||||
let lighting_blurs: &[i32] = &[0, 5, 10, 15, 25, 40];
|
||||
let hl_scales: &[f32] = &[0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
|
||||
let sh_scales: &[f32] = &[0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
|
||||
// Grid search over lighting_blur, hl_scale, sh_scale, tilt_scale
|
||||
// Wide ranges to cover high-depth PSDs (e.g. Sultan depth=350) and
|
||||
// various bevel sizes (10, 25+). Each axis spans low→high so the
|
||||
// search can find optima for both simple and complex shapes.
|
||||
let lighting_blurs: &[i32] = &[0, 2, 5];
|
||||
let hl_scales: &[f32] = &[0.05, 0.1, 0.2, 0.5];
|
||||
let sh_scales: &[f32] = &[2.0, 8.0, 16.0];
|
||||
let tilt_scales: &[f32] = &[0.05, 0.1, 0.2, 0.3, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 5.0, 8.0];
|
||||
|
||||
let total_combos = lighting_blurs.len() * hl_scales.len() * sh_scales.len();
|
||||
let total_combos = lighting_blurs.len() * hl_scales.len() * sh_scales.len() * tilt_scales.len();
|
||||
println!("Grid: {} combos x {} samples = {} evals", total_combos, samples.len(), total_combos * samples.len());
|
||||
std::io::stdout().flush().ok();
|
||||
|
||||
let mut best_avg_mae = f64::MAX;
|
||||
let mut best_params = (0i32, 0.0f32, 0.0f32);
|
||||
let mut best_params = (0i32, 0.0f32, 0.0f32, 0.0f32);
|
||||
let mut eval_count = 0usize;
|
||||
|
||||
for &lighting_blur in lighting_blurs {
|
||||
for &hl_scale in hl_scales {
|
||||
for &sh_scale in sh_scales {
|
||||
let mut total_mae = 0.0f64;
|
||||
|
||||
for s in &samples {
|
||||
let alpha = match s.shape.as_str() {
|
||||
"circle" => &circle_alpha,
|
||||
"rect" => &rect_alpha,
|
||||
other => {
|
||||
let owned = make_shape_alpha(other);
|
||||
let ptr = owned.as_ptr();
|
||||
let len = owned.len();
|
||||
std::mem::forget(owned);
|
||||
unsafe { std::slice::from_raw_parts(ptr, len) }
|
||||
}
|
||||
};
|
||||
|
||||
for &tilt_scale in tilt_scales {
|
||||
let total_mae: f64 = samples.par_iter().map(|s| {
|
||||
// Use generate_bevel_tuned to get highlight/shadow buffers
|
||||
let style_enum = match s.style.as_str() {
|
||||
"emboss" => hcie_fx::types::BevelStyle::Emboss,
|
||||
"outer" => hcie_fx::types::BevelStyle::OuterBevel,
|
||||
@@ -186,43 +162,84 @@ fn main() {
|
||||
};
|
||||
|
||||
let (highlight, shadow) = hcie_fx::tuned::generate_bevel_tuned(
|
||||
alpha, CANVAS, CANVAS,
|
||||
s.depth, s.size, s.soften, 120.0, s.altitude,
|
||||
&s.layer_pixels.iter().skip(3).step_by(4).copied().collect::<Vec<u8>>(),
|
||||
s.w, s.h,
|
||||
s.depth, s.size, s.soften, s.angle, s.altitude,
|
||||
&dir_enum, &hcie_fx::types::Technique::Smooth, &style_enum,
|
||||
lighting_blur, hl_scale, sh_scale, 1.0,
|
||||
lighting_blur, hl_scale, sh_scale, 1.0, tilt_scale,
|
||||
);
|
||||
|
||||
let px = npix();
|
||||
let px = (s.w * s.h) as usize;
|
||||
let mut out = vec![0u8; px * 4];
|
||||
|
||||
// Base shape: black fill for both Inner Bevel and Emboss.
|
||||
// Photoshop Emboss applies highlights/shadows on top of the original fill.
|
||||
// Base shape: fill with layer pixels (black fill)
|
||||
for i in 0..px {
|
||||
if alpha[i] > 0 {
|
||||
out[i * 4] = 0;
|
||||
out[i * 4 + 1] = 0;
|
||||
out[i * 4 + 2] = 0;
|
||||
out[i * 4 + 3] = alpha[i];
|
||||
if s.layer_pixels[i * 4 + 3] > 0 {
|
||||
out[i * 4] = s.layer_pixels[i * 4];
|
||||
out[i * 4 + 1] = s.layer_pixels[i * 4 + 1];
|
||||
out[i * 4 + 2] = s.layer_pixels[i * 4 + 2];
|
||||
out[i * 4 + 3] = s.layer_pixels[i * 4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
// Composite highlight (screen) and shadow (multiply)
|
||||
// For emboss/outerbevel: outside highlight/shadow go to empty canvas (behind)
|
||||
let has_outer = matches!(style_enum, hcie_fx::types::BevelStyle::Emboss | hcie_fx::types::BevelStyle::OuterBevel);
|
||||
|
||||
// Get opacity and blend mode from fx_effects
|
||||
let (hl_op, sh_op, hl_bm, sh_bm) = {
|
||||
let mut hl_op = 0.75f32;
|
||||
let mut sh_op = 0.75f32;
|
||||
let mut hl_bm = hcie_blend::BlendMode::Screen;
|
||||
let mut sh_bm = hcie_blend::BlendMode::Multiply;
|
||||
for e in &s.fx_effects {
|
||||
if let hcie_fx::types::LayerEffect::BevelEmboss {
|
||||
highlight_opacity, shadow_opacity,
|
||||
highlight_blend, shadow_blend, ..
|
||||
} = e {
|
||||
hl_op = *highlight_opacity / 100.0;
|
||||
sh_op = *shadow_opacity / 100.0;
|
||||
hl_bm = *highlight_blend;
|
||||
sh_bm = *shadow_blend;
|
||||
}
|
||||
}
|
||||
(hl_op, sh_op, hl_bm, sh_bm)
|
||||
};
|
||||
|
||||
// Composite highlight — inside on fill, outside on empty canvas
|
||||
for i in 0..px {
|
||||
let sa = highlight[i * 4 + 3];
|
||||
if sa > 0 {
|
||||
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
|
||||
let src = [highlight[i * 4], highlight[i * 4 + 1], highlight[i * 4 + 2], sa];
|
||||
let blended = hcie_blend::blend_pixels(dst, src, hcie_blend::BlendMode::Screen, 1.0);
|
||||
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
||||
let is_inner = s.layer_pixels[i * 4 + 3] > 0;
|
||||
if !has_outer || is_inner {
|
||||
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
|
||||
let src = [highlight[i * 4], highlight[i * 4 + 1], highlight[i * 4 + 2], sa];
|
||||
let blended = hcie_blend::blend_pixels(dst, src, hl_bm, hl_op);
|
||||
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
||||
} else {
|
||||
// Outside: composite highlight on empty canvas
|
||||
let dst = [0u8, 0, 0, 0];
|
||||
let src = [highlight[i * 4], highlight[i * 4 + 1], highlight[i * 4 + 2], sa];
|
||||
let blended = hcie_blend::blend_pixels(dst, src, hl_bm, hl_op);
|
||||
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Composite shadow
|
||||
for i in 0..px {
|
||||
let sa = shadow[i * 4 + 3];
|
||||
if sa > 0 {
|
||||
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
|
||||
let src = [shadow[i * 4], shadow[i * 4 + 1], shadow[i * 4 + 2], sa];
|
||||
let blended = hcie_blend::blend_pixels(dst, src, hcie_blend::BlendMode::Multiply, 1.0);
|
||||
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
||||
let is_inner = s.layer_pixels[i * 4 + 3] > 0;
|
||||
if !has_outer || is_inner {
|
||||
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
|
||||
let src = [shadow[i * 4], shadow[i * 4 + 1], shadow[i * 4 + 2], sa];
|
||||
let blended = hcie_blend::blend_pixels(dst, src, sh_bm, sh_op);
|
||||
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
||||
} else {
|
||||
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
|
||||
let src = [shadow[i * 4], shadow[i * 4 + 1], shadow[i * 4 + 2], sa];
|
||||
let blended = hcie_blend::blend_pixels(dst, src, sh_bm, sh_op);
|
||||
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,46 +253,48 @@ fn main() {
|
||||
}
|
||||
}
|
||||
if content_pixels > 0 {
|
||||
total_mae += diff_sum / content_pixels as f64 / 4.0;
|
||||
diff_sum / content_pixels as f64 / 4.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}).sum();
|
||||
|
||||
eval_count += 1;
|
||||
let avg_mae = total_mae / samples.len() as f64;
|
||||
|
||||
if avg_mae < best_avg_mae {
|
||||
best_avg_mae = avg_mae;
|
||||
best_params = (lighting_blur, hl_scale, sh_scale);
|
||||
println!("*** [{}/{}] NEW BEST avg MAE = {:.4} (lb={}, hl={:.2}, sh={:.2})",
|
||||
eval_count, total_combos, best_avg_mae, lighting_blur, hl_scale, sh_scale);
|
||||
best_params = (lighting_blur, hl_scale, sh_scale, tilt_scale);
|
||||
println!("*** [{}/{}] NEW BEST avg MAE = {:.4} (lb={}, hl={:.3}, sh={:.2}, tilt={:.2})",
|
||||
eval_count, total_combos, best_avg_mae, lighting_blur, hl_scale, sh_scale, tilt_scale);
|
||||
std::io::stdout().flush().ok();
|
||||
} else if eval_count % 50 == 0 {
|
||||
} else if eval_count % 100 == 0 {
|
||||
println!(" [{}/{}] avg={:.4} best={:.4}", eval_count, total_combos, avg_mae, best_avg_mae);
|
||||
std::io::stdout().flush().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (lb, hl, sh) = best_params;
|
||||
let (lb, hl, sh, ts) = best_params;
|
||||
println!("\n=== RESULT ===");
|
||||
println!("Best avg MAE = {:.4}", best_avg_mae);
|
||||
println!(" lighting_blur = {}", lb);
|
||||
println!(" hl_scale = {:.2}", hl);
|
||||
println!(" hl_scale = {:.3}", hl);
|
||||
println!(" sh_scale = {:.2}", sh);
|
||||
println!(" tilt_scale = {:.2}", ts);
|
||||
|
||||
if let Ok(mut file) = std::fs::OpenOptions::new()
|
||||
.create(true).write(true).truncate(true).open(RESULTS_FILE)
|
||||
{
|
||||
let _ = writeln!(file,
|
||||
"lighting_blur={} hl_scale={:.2} sh_scale={:.2}\n\
|
||||
"lighting_blur={} hl_scale={:.3} sh_scale={:.2} tilt_scale={:.2}\n\
|
||||
avg_mae={:.4} samples={} combos={}",
|
||||
lb, hl, sh, best_avg_mae, samples.len(), total_combos);
|
||||
lb, hl, sh, ts, best_avg_mae, samples.len(), total_combos);
|
||||
}
|
||||
}
|
||||
|
||||
fn npix() -> usize { (CANVAS * CANVAS) as usize }
|
||||
|
||||
fn parse_filename(name: &str) -> Option<(String, String, f32, f32, f32, String, f32, String)> {
|
||||
// be_{shape}_{style}_d{depth}_sz{size}_sf{soften}_{dir}_alt{altitude}_{technique}
|
||||
let parts: Vec<&str> = name.split('_').collect();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
use std::io::Write;
|
||||
|
||||
const SHADOW_RS: &str = "hcie-fx/src/emboss.rs";
|
||||
const PSD_BASE: &str = "_images/_psd_stil_test/sultan_test/sultan";
|
||||
const RESULTS_FILE: &str = "/mnt/extra/00_PROJECTS/hcie-rust-v4/emboss_results.txt";
|
||||
const PSD_BASE: &str = "_images/_psd_stil_test/test_2";
|
||||
const RESULTS_FILE: &str = "./emboss_results.txt";
|
||||
|
||||
fn main() {
|
||||
let original = std::fs::read_to_string(SHADOW_RS).expect("read shadow.rs");
|
||||
@@ -16,15 +16,15 @@ fn main() {
|
||||
let before = &original[..zone_start];
|
||||
let after = &original[zone_end + end_tag.len()..];
|
||||
|
||||
let ref_img = image::open(&format!("{}/Layer 2.png", PSD_BASE))
|
||||
.expect("open Layer 2.png")
|
||||
let ref_img = image::open(&format!("{}/Emboss.png", PSD_BASE))
|
||||
.expect("open Emboss.png")
|
||||
.to_rgba8();
|
||||
let (w, h) = (ref_img.width(), ref_img.height());
|
||||
let px = (w * h) as usize;
|
||||
let ref_pixels = ref_img.into_raw();
|
||||
|
||||
let layers = hcie_psd::import_psd(std::path::Path::new(&format!("{}/Layer 2.psd", PSD_BASE)))
|
||||
.expect("import Layer 2.psd");
|
||||
let layers = hcie_psd::import_psd(std::path::Path::new(&format!("{}/Emboss.psd", PSD_BASE)))
|
||||
.expect("import Emboss.psd");
|
||||
let layer = &layers[0];
|
||||
let alpha: Vec<u8> = layer.pixels.iter().skip(3).step_by(4).copied().collect();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::io::Read;
|
||||
|
||||
fn main() {
|
||||
let kra_path = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra";
|
||||
let kra_path = "/mnt/extra/00_PROJECTS/hcie-rust-_images/kra_style_test/sultan.kra";
|
||||
println!("Loading KRA from: {}", kra_path);
|
||||
let file = std::fs::File::open(kra_path).unwrap();
|
||||
let mut archive = zip::ZipArchive::new(file).unwrap();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(dead_code, unused_imports, unused_variables, unused_macros, unreachable_patterns, unused_assignments, clippy::cfg)]
|
||||
#[test]
|
||||
fn test_psd_import_sizes_and_offsets() {
|
||||
let path = std::path::Path::new("/home/hc/Pictures/_test_images/example3/Example3-mini.psd");
|
||||
let path = std::path::Path::new("_images/_test_images/example3/Example3-mini.psd");
|
||||
if !path.exists() { return; }
|
||||
|
||||
let layers = hcie_psd::import_psd(path).unwrap();
|
||||
@@ -41,8 +41,8 @@ fn map_to_composite_layers(layers: &[hcie_protocol::Layer]) -> Vec<hcie_protocol
|
||||
|
||||
#[test]
|
||||
fn test_psd_composite_against_ref() {
|
||||
let path = std::path::Path::new("/home/hc/Pictures/_test_images/example3/Example3-mini.psd");
|
||||
let ref_path = std::path::Path::new("/home/hc/Pictures/_test_images/example3/Example3-mini.png");
|
||||
let path = std::path::Path::new("_images/_test_images/example3/Example3-mini.psd");
|
||||
let ref_path = std::path::Path::new("_images/_test_images/example3/Example3-mini.png");
|
||||
if !path.exists() || !ref_path.exists() {
|
||||
println!("Test files not found, skipping comparison.");
|
||||
return;
|
||||
@@ -239,9 +239,9 @@ fn composite_and_compare(psd_path: &str, ref_path: &str) {
|
||||
|
||||
#[test]
|
||||
fn test_layer_pixels_direct() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
let ref_img = image::open("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.png").unwrap().to_rgba8();
|
||||
let ref_img = image::open("_images/_psd_stil_test/base_test_generated_2.png").unwrap().to_rgba8();
|
||||
let ref_pixels = ref_img.as_raw();
|
||||
|
||||
// Compare layer 0 (Background Grid) pixels directly with reference
|
||||
@@ -302,7 +302,7 @@ fn test_base_generated_2_vs_merged() {
|
||||
// Extract PSD merged image using ImageMagick
|
||||
let _ = std::process::Command::new("convert")
|
||||
.args(&[
|
||||
"/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd[0]",
|
||||
"_images/_psd_stil_test/base_test_generated_2.psd[0]",
|
||||
"/tmp/psd2_merged_for_test.png"
|
||||
])
|
||||
.status();
|
||||
@@ -310,7 +310,7 @@ fn test_base_generated_2_vs_merged() {
|
||||
let psd_merged = image::open("/tmp/psd2_merged_for_test.png").unwrap().to_rgba8();
|
||||
let psd_pixels = psd_merged.as_raw();
|
||||
|
||||
let layers = hcie_psd::import_psd(std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd")).unwrap();
|
||||
let layers = hcie_psd::import_psd(std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd")).unwrap();
|
||||
let canvas_w = layers[0].width;
|
||||
let canvas_h = layers[0].height;
|
||||
|
||||
@@ -362,7 +362,7 @@ fn test_blend_normal() {
|
||||
|
||||
#[test]
|
||||
fn test_teal_circle_emboss_debug() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
let teal = &layers[1]; // Teal Circle
|
||||
|
||||
@@ -376,9 +376,9 @@ fn test_teal_circle_emboss_debug() {
|
||||
|
||||
#[test]
|
||||
fn test_base_generated_2_no_effects() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
let ref_img = image::open("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.png").unwrap().to_rgba8();
|
||||
let ref_img = image::open("_images/_psd_stil_test/base_test_generated_2.png").unwrap().to_rgba8();
|
||||
let ref_pixels = ref_img.as_raw();
|
||||
|
||||
let canvas_w = layers[0].width;
|
||||
@@ -419,16 +419,16 @@ fn test_base_generated_2_no_effects() {
|
||||
#[test]
|
||||
fn test_base_generated_2_composite() {
|
||||
composite_and_compare(
|
||||
"/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd",
|
||||
"/home/hc/Pictures/_psd_stil_test/base_test_generated_2.png",
|
||||
"_images/_psd_stil_test/base_test_generated_2.psd",
|
||||
"_images/_psd_stil_test/base_test_generated_2.png",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hc_emboss_composite() {
|
||||
composite_and_compare(
|
||||
"/home/hc/Pictures/_psd_stil_test/hc_emboss.psd",
|
||||
"/home/hc/Pictures/_psd_stil_test/hc_emboss.png",
|
||||
"_images/_psd_stil_test/hc_emboss.psd",
|
||||
"_images/_psd_stil_test/hc_emboss.png",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -436,8 +436,8 @@ fn test_hc_emboss_composite() {
|
||||
|
||||
#[test]
|
||||
fn test_base_generated_2_effect_isolation() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let ref_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.png");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let ref_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.png");
|
||||
|
||||
let all_layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
let ref_img = image::open(ref_path).unwrap().to_rgba8();
|
||||
@@ -489,7 +489,7 @@ fn compute_mae(a: &[u8], b: &[u8], pixels: usize) -> f64 {
|
||||
}
|
||||
#[test]
|
||||
fn test_check_soft_orange_pixels() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
|
||||
let orange = &layers[5]; // Soft Orange Shape
|
||||
@@ -514,7 +514,7 @@ fn test_check_soft_orange_pixels() {
|
||||
}
|
||||
#[test]
|
||||
fn test_find_orange_pixels() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
|
||||
let orange = &layers[5]; // Soft Orange Shape
|
||||
@@ -553,7 +553,7 @@ fn test_find_orange_pixels() {
|
||||
}
|
||||
#[test]
|
||||
fn test_layer_bounds() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
|
||||
for (i, layer) in layers.iter().enumerate() {
|
||||
@@ -582,7 +582,7 @@ fn test_layer_bounds() {
|
||||
}
|
||||
#[test]
|
||||
fn test_check_layer_offsets() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
|
||||
println!("Layer positions and sizes:");
|
||||
@@ -619,7 +619,7 @@ fn test_check_layer_offsets() {
|
||||
}
|
||||
#[test]
|
||||
fn test_save_layer_pixels() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
|
||||
for (i, layer) in layers.iter().enumerate() {
|
||||
@@ -633,7 +633,7 @@ fn test_save_layer_pixels() {
|
||||
}
|
||||
#[test]
|
||||
fn test_pixel_layer_contributions() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
|
||||
let test_pixels = [(300, 200), (400, 200), (200, 300), (500, 200), (500, 500)];
|
||||
@@ -656,7 +656,7 @@ fn test_pixel_layer_contributions() {
|
||||
|
||||
#[test]
|
||||
fn test_load_test_2_psd() {
|
||||
let test_dir = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/test_2");
|
||||
let test_dir = std::path::Path::new("_images/_psd_stil_test/test_2");
|
||||
if !test_dir.exists() { return; }
|
||||
|
||||
let entries: Vec<_> = std::fs::read_dir(test_dir).unwrap()
|
||||
@@ -694,8 +694,8 @@ fn test_load_test_2_psd() {
|
||||
|
||||
#[test]
|
||||
fn test_per_layer_effects_vs_photoshop_export() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2.psd");
|
||||
let ref_dir = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/base_test_generated_2");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2.psd");
|
||||
let ref_dir = std::path::Path::new("_images/_psd_stil_test/base_test_generated_2");
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
let layer_names = [
|
||||
"Background Grid", "Teal Circle", "Pink Rectangle",
|
||||
@@ -793,7 +793,7 @@ fn test_per_layer_effects_vs_photoshop_export() {
|
||||
|
||||
#[test]
|
||||
fn test_test2_effects_mae() {
|
||||
let test_dir = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/test_2");
|
||||
let test_dir = std::path::Path::new("_images/_psd_stil_test/test_2");
|
||||
if !test_dir.exists() { return; }
|
||||
|
||||
let entries: Vec<_> = std::fs::read_dir(test_dir).unwrap()
|
||||
@@ -859,9 +859,9 @@ fn test_test2_effects_mae() {
|
||||
|
||||
#[test]
|
||||
fn test_sultan_effects_mae() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/sultan.psd");
|
||||
let ref_png = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/sultan.png");
|
||||
let layer_dir = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/sultan");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/sultan.psd");
|
||||
let ref_png = std::path::Path::new("_images/_psd_stil_test/sultan.png");
|
||||
let layer_dir = std::path::Path::new("_images/_psd_stil_test/sultan");
|
||||
if !psd_path.exists() || !ref_png.exists() { return; }
|
||||
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
@@ -1055,8 +1055,8 @@ fn test_sultan_effects_mae() {
|
||||
|
||||
#[test]
|
||||
fn test_emboss_optimize() {
|
||||
let psd_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/emboss.psd");
|
||||
let ref_path = std::path::Path::new("/home/hc/Pictures/_psd_stil_test/emboss.png");
|
||||
let psd_path = std::path::Path::new("_images/_psd_stil_test/emboss.psd");
|
||||
let ref_path = std::path::Path::new("_images/_psd_stil_test/emboss.png");
|
||||
if !psd_path.exists() || !ref_path.exists() { return; }
|
||||
|
||||
let layers = hcie_psd::import_psd(psd_path).unwrap();
|
||||
@@ -1245,9 +1245,9 @@ fn test_emboss_optimize() {
|
||||
#[test]
|
||||
fn test_effects_survive_ffi_boundary() {
|
||||
let paths = [
|
||||
"/home/hc/Documents/00_PROJECTS/hcie-rust-v4/hcie-io/test_resources/base_test_generated_2.psd",
|
||||
"/home/hc/Pictures/_psd_stil_test/emboss.psd",
|
||||
"/home/hc/Pictures/_test_images/example3/Example3-mini.psd",
|
||||
"./test_resources/base_test_generated_2.psd",
|
||||
"_images/_psd_stil_test/emboss.psd",
|
||||
"_images/_test_images/example3/Example3-mini.psd",
|
||||
];
|
||||
let mut found = false;
|
||||
for p in &paths {
|
||||
@@ -1275,9 +1275,9 @@ fn test_effects_survive_ffi_boundary() {
|
||||
#[test]
|
||||
fn test_ffi_bincode_roundtrip() {
|
||||
let paths = [
|
||||
"/home/hc/Documents/00_PROJECTS/hcie-rust-v4/hcie-io/test_resources/base_test_generated_2.psd",
|
||||
"/home/hc/Pictures/_psd_stil_test/emboss.psd",
|
||||
"/home/hc/Pictures/_test_images/example3/Example3-mini.psd",
|
||||
"./test_resources/base_test_generated_2.psd",
|
||||
"_images/_psd_stil_test/emboss.psd",
|
||||
"_images/_test_images/example3/Example3-mini.psd",
|
||||
];
|
||||
let mut found = false;
|
||||
for p in &paths {
|
||||
|
||||
Reference in New Issue
Block a user