use hcie_protocol::Layer as IoLayer; use hcie_protocol; use hcie_composite; use image; fn test_file(psd_path: &str, ref_path: &str, out_path: &str) { println!("\n=== {} ===", psd_path); let layers: Vec = match hcie_psd::import_psd(std::path::Path::new(psd_path)) { Ok(l) => l, Err(e) => { println!("import_psd failed: {}", e); return; } }; if layers.is_empty() { println!("No layers imported."); return; } for (i, layer) in layers.iter().enumerate() { let effs = if layer.effects.is_empty() { "no effects".to_string() } else { format!("{} effects: {}", layer.effects.len(), layer.effects.iter().map(|e| e.effect_name()).collect::>().join(", ")) }; println!("Layer {}: '{}' {}x{} visible={} {}", i, layer.name, layer.width, layer.height, layer.visible, effs); } let comp_layers: Vec = layers.iter().map(|l| hcie_composite::Layer { name: l.name.clone(), layer_type: l.layer_type.clone(), data: l.data.clone(), pixels: l.pixels.clone(), width: l.width, height: l.height, visible: l.visible, opacity: l.opacity, blend_mode: l.blend_mode, locked: l.locked, dirty: l.dirty, id: l.id, parent_id: l.parent_id, styles: l.styles.clone(), clipping_mask: l.clipping_mask, collapsed: l.collapsed, adjustment: l.adjustment.clone(), mask_pixels: l.mask_pixels.clone(), mask_bounds: l.mask_bounds, mask_default_color: l.mask_default_color, curve_points: l.curve_points.clone(), fill_opacity: l.fill_opacity, effects: l.effects.clone(), effects_dirty: std::sync::atomic::AtomicBool::new(false), effects_cache: std::sync::Mutex::new(None), adjustment_raw: None, is_section_divider: false, image_resources_raw: None, }).collect(); let canvas_w = comp_layers[0].width; let canvas_h = comp_layers[0].height; let output = hcie_composite::composite_layers(&comp_layers, canvas_w, canvas_h); { let img = image::RgbaImage::from_raw(canvas_w, canvas_h, output.clone()) .expect("Failed to create image from buffer"); img.save(out_path).expect("Failed to save output image"); } println!("Saved composite to {}", out_path); // Diff against reference PNG if std::path::Path::new(ref_path).exists() { let ref_img = image::open(ref_path).unwrap().to_rgba8(); if ref_img.width() == canvas_w && ref_img.height() == canvas_h { let mut diff_sum = 0.0f64; let mut max_diff = 0u32; let comp_img = image::open(out_path).unwrap().to_rgba8(); for (p1, p2) in comp_img.pixels().zip(ref_img.pixels()) { let d = p1.0.iter().zip(p2.0.iter()) .map(|(a, b)| (*a as i32 - *b as i32).abs() as f64) .sum::(); diff_sum += d; max_diff = max_diff.max(d as u32); } let px_cnt = (canvas_w * canvas_h) as f64; println!("Avg diff per channel: {:.2}", diff_sum / px_cnt); println!("Max diff per pixel: {}", max_diff); } else { println!("Reference image dimensions differ."); } } else { println!("Reference not found: {}", ref_path); } } fn main() { test_file( "_images/_psd_stil_test/sultan.psd", "_images/_psd_stil_test/sultan.png", "/tmp/sultan_out.png", ); test_file( "_images/_psd_stil_test/inner_shadow.psd", "_images/_psd_stil_test/inner_shadow.png", "/tmp/inner_shadow_out.png", ); test_file( "_images/_psd_stil_test/inner_bevel.psd", "_images/_psd_stil_test/inner_bevel.png", "/tmp/inner_bevel_out.png", ); }