#![allow(dead_code, unused_imports, unused_variables, unused_macros)] use hcie_blend::BlendMode; /// 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 -- /// 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}; 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::().ok()?; let size = parts[4].strip_prefix("sz")?.parse::().ok()?; let soften = parts[5].strip_prefix("sf")?.parse::().ok()?; let direction = parts[6].to_string(); let altitude = parts[7].strip_prefix("alt")?.parse::().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 = 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 = 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); } } }