use hcie_protocol::Layer; use hcie_protocol::effects::{LayerEffect, BevelStyle, Technique, Direction, StrokePosition, StrokeFillType, GradientDef}; use std::path::Path; fn create_layer(name: &str, w: u32, h: u32, fill_r: u8, fill_g: u8, fill_b: u8) -> Layer { create_layer_at(name, w, h, fill_r, fill_g, fill_b, w / 2, h / 2) } fn create_layer_at(name: &str, w: u32, h: u32, fill_r: u8, fill_g: u8, fill_b: u8, offset_x: u32, offset_y: u32) -> Layer { let px_count = (w * h) as usize; let mut pixels = vec![0u8; px_count * 4]; for y in 0..h { for x in 0..w { let idx = (y * w + x) as usize * 4; let cx = offset_x as f32; let cy = offset_y as f32; let rx = (w as f32 * 0.2) + (y as f32 * 0.1).sin() * 20.0; let ry = (h as f32 * 0.2) + (x as f32 * 0.1).cos() * 20.0; let dx = (x as f32 - cx).abs(); let dy = (y as f32 - cy).abs(); if dx < rx && dy < ry { pixels[idx] = fill_r; pixels[idx + 1] = fill_g; pixels[idx + 2] = fill_b; pixels[idx + 3] = 255; } } } Layer::from_rgba(name, w, h, pixels) } fn create_layer_with_effect(name: &str, w: u32, h: u32, effect: LayerEffect) -> Layer { let mut layer = create_layer(name, w, h, 120, 140, 200); layer.effects = vec![effect]; layer } fn save_png(path: &str, pixels: &[u8], w: u32, h: u32) { if let Some(img) = image::ImageBuffer::, _>::from_raw(w, h, pixels.to_vec()) { let _ = img.save(path); } } fn main() { let out_dir = "_images/_psd_stil_test"; let w: u32 = 512; let h: u32 = 512; let tests: Vec<(&str, Layer)> = vec![ ( "fx_drop_shadow", create_layer_with_effect("Drop Shadow", w, h, LayerEffect::DropShadow { enabled: true, blend_mode: "Multiply".to_string(), color: [0, 0, 0, 255], opacity: 0.75, angle: 120.0, distance: 15.0, spread: 0.0, size: 25.0, noise: 0.0, contour: None, }) ), ( "fx_inner_shadow", create_layer_with_effect("Inner Shadow", w, h, LayerEffect::InnerShadow { enabled: true, blend_mode: "Multiply".to_string(), color: [0, 0, 0, 255], opacity: 0.75, angle: 120.0, distance: 15.0, choke: 0.0, size: 25.0, noise: 0.0, contour: None, }) ), ( "fx_outer_glow", create_layer_with_effect("Outer Glow", w, h, LayerEffect::OuterGlow { enabled: true, blend_mode: "Normal".to_string(), color: [255, 100, 0, 255], opacity: 0.75, spread: 0.0, size: 30.0, noise: 0.0, contour: None, }) ), ( "fx_inner_glow", create_layer_with_effect("Inner Glow", w, h, LayerEffect::InnerGlow { enabled: true, blend_mode: "Normal".to_string(), color: [0, 200, 255, 255], opacity: 0.75, choke: 0.0, size: 15.0, noise: 0.0, contour: None, source: 0, }) ), ( "fx_bevel_inner", create_layer_with_effect("Bevel Inner", w, h, LayerEffect::BevelEmboss { enabled: true, style: BevelStyle::InnerBevel, technique: Technique::Smooth, depth: 100.0, direction: Direction::Up, size: 15.0, soften: 0.0, angle: 120.0, altitude: 30.0, highlight_blend: "Screen".to_string(), highlight_color: [255, 255, 255, 255], highlight_opacity: 0.75, shadow_blend: "Multiply".to_string(), shadow_color: [0, 0, 0, 255], shadow_opacity: 0.75, contour: None, }) ), ( "fx_bevel_outer", create_layer_with_effect("Bevel Outer", w, h, LayerEffect::BevelEmboss { enabled: true, style: BevelStyle::OuterBevel, technique: Technique::Smooth, depth: 100.0, direction: Direction::Up, size: 15.0, soften: 0.0, angle: 120.0, altitude: 30.0, highlight_blend: "Screen".to_string(), highlight_color: [255, 255, 255, 255], highlight_opacity: 0.75, shadow_blend: "Multiply".to_string(), shadow_color: [0, 0, 0, 255], shadow_opacity: 0.75, contour: None, }) ), ( "fx_emboss", create_layer_with_effect("Emboss", w, h, LayerEffect::BevelEmboss { enabled: true, style: BevelStyle::Emboss, technique: Technique::Smooth, depth: 100.0, direction: Direction::Up, size: 15.0, soften: 0.0, angle: 120.0, altitude: 30.0, highlight_blend: "Screen".to_string(), highlight_color: [255, 255, 255, 255], highlight_opacity: 0.75, shadow_blend: "Multiply".to_string(), shadow_color: [0, 0, 0, 255], shadow_opacity: 0.75, contour: None, }) ), ( "fx_pillow_emboss", create_layer_with_effect("Pillow Emboss", w, h, LayerEffect::BevelEmboss { enabled: true, style: BevelStyle::PillowEmboss, technique: Technique::Smooth, depth: 100.0, direction: Direction::Up, size: 15.0, soften: 0.0, angle: 120.0, altitude: 30.0, highlight_blend: "Screen".to_string(), highlight_color: [255, 255, 255, 255], highlight_opacity: 0.75, shadow_blend: "Multiply".to_string(), shadow_color: [0, 0, 0, 255], shadow_opacity: 0.75, contour: None, }) ), ( "fx_satin", create_layer_with_effect("Satin", w, h, LayerEffect::Satin { enabled: true, blend_mode: "Multiply".to_string(), color: [0, 0, 0, 255], opacity: 0.5, angle: 120.0, distance: 15.0, size: 25.0, invert: false, contour: None, }) ), ( "fx_color_overlay", create_layer_with_effect("Color Overlay", w, h, LayerEffect::ColorOverlay { enabled: true, blend_mode: "Normal".to_string(), color: [255, 0, 180, 255], opacity: 0.6, }) ), ( "fx_gradient_overlay", create_layer_with_effect("Gradient Overlay", w, h, LayerEffect::GradientOverlay { enabled: true, blend_mode: "Normal".to_string(), opacity: 0.8, angle: 90.0, scale: 100.0, gradient: GradientDef { color_stops: vec![ (0, [255, 80, 0, 255]), (4096, [255, 220, 0, 255]), ], transparency_stops: vec![ (0, 100), (4096, 100), ], midpoint: 50.0, angle: 90.0, scale: 100.0, gradient_type: 0, }, }) ), ( "fx_pattern_overlay", create_layer_with_effect("Pattern Overlay", w, h, LayerEffect::PatternOverlay { enabled: true, blend_mode: "Normal".to_string(), opacity: 0.6, scale: 100.0, pattern_id: "b4d43394-d71c-11e5-b1ae-a548a96ef5f9".to_string(), }) ), ( "fx_stroke_inside", create_layer_with_effect("Stroke Inside", w, h, LayerEffect::Stroke { enabled: true, position: StrokePosition::Inside, fill_type: StrokeFillType::Color, blend_mode: "Normal".to_string(), opacity: 1.0, size: 12.0, color: [255, 220, 0, 255], }) ), ( "fx_stroke_center", create_layer_with_effect("Stroke Center", w, h, LayerEffect::Stroke { enabled: true, position: StrokePosition::Center, fill_type: StrokeFillType::Color, blend_mode: "Normal".to_string(), opacity: 1.0, size: 12.0, color: [255, 220, 0, 255], }) ), ( "fx_stroke_outside", create_layer_with_effect("Stroke Outside", w, h, LayerEffect::Stroke { enabled: true, position: StrokePosition::Outside, fill_type: StrokeFillType::Color, blend_mode: "Normal".to_string(), opacity: 1.0, size: 12.0, color: [255, 220, 0, 255], }) ), ]; // Generate PLAIN PSDs (no effects) — user will apply effects manually in Photopea let effect_labels: Vec<&str> = tests.iter().map(|(n, _)| *n).collect(); for name in &effect_labels { let layer = create_layer(name, w, h, 120, 140, 200); let composited = layer.pixels.clone(); let plain_path = format!("{}/plain_{}.psd", out_dir, name); if let Err(e) = hcie_psd::save_psd(&[layer.clone()], w, h, &composited, Path::new(&plain_path)) { eprintln!("Failed to save {}: {}", name, e); } else { println!("Generated: {}", plain_path); } } // Also generate baseline with no effects let baseline = create_layer("Baseline", w, h, 100, 180, 140); let composited = baseline.pixels.clone(); let path = format!("{}/fx_baseline.psd", out_dir); if let Err(e) = hcie_psd::save_psd(&[baseline.clone()], w, h, &composited, Path::new(&path)) { eprintln!("Failed to save fx_baseline: {}", e); } else { println!("Generated: {}", path); } let png_path = format!("{}/fx_baseline.png", out_dir); save_png(&png_path, &composited, w, h); println!(" PNG: {}", png_path); }