bevel and emboss geliştirme

This commit is contained in:
2026-07-13 20:33:39 +03:00
parent 2284a7d81f
commit 5e03065165
25 changed files with 1165 additions and 405 deletions
+2 -2
View File
@@ -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();
+100
View File
@@ -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);
}
+36
View File
@@ -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);
}
+192
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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();
+147 -128
View File
@@ -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();
@@ -289,4 +308,4 @@ fn parse_filename(name: &str) -> Option<(String, String, f32, f32, f32, 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))
}
}
+6 -6
View File
@@ -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 -1
View File
@@ -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();