/// Generate an expanded set of stroke PSDs with asymmetric shapes. /// /// Shapes: square, tall-rect, wide-rect, L, T, cross, triangle, star. /// This tests stroke rendering on non-uniform geometry and sharp corners. /// Output: `_tmp/stroke_v2_psd_set/` use hcie_protocol::{Layer, LayerType, LayerData, BlendMode, effects::{LayerEffect, StrokePosition}}; use std::sync::atomic::{AtomicBool}; use std::sync::Mutex; fn make_shape(shape: &str, canvas: u32) -> Vec { let mut pixels = vec![0u8; (canvas * canvas * 4) as usize]; let cx = canvas / 2; let cy = canvas / 2; for y in 0..canvas { for x in 0..canvas { let i = (y * canvas + x) as usize * 4; 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 } "L" => { // left vertical bar + bottom horizontal bar (x >= 64 && x < 96 && y >= 64 && y < canvas - 64) || (x >= 64 && x < canvas - 64 && y >= canvas - 96 && y < canvas - 64) } "T" => { // top horizontal + center vertical (y >= 64 && y < 96 && x >= 64 && x < canvas - 64) || (x >= 112 && x < 144 && y >= 64 && y < canvas - 64) } "cross" => { // vertical + horizontal bar (x >= 112 && x < 144 && y >= 48 && y < canvas - 48) || (y >= 112 && y < 144 && x >= 48 && x < canvas - 48) } "triangle" => { // filled triangle pointing up 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" => { // simple 5-point star via radial test 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 points = 5.0f32; let sector = (angle * points).cos(); let limit = 64.0 + sector * 32.0; r <= limit } } _ => false, }; if inside { pixels[i] = 0; pixels[i + 1] = 0; pixels[i + 2] = 0; pixels[i + 3] = 255; } } } pixels } fn main() { let out_dir = std::path::Path::new("_tmp/stroke_v2_psd_set"); std::fs::create_dir_all(out_dir).expect("create output dir"); let canvas: u32 = 256; let shapes: &[(&str, &str)] = &[ ("rect", "rect"), ("tall", "tall"), ("wide", "wide"), ("L", "L"), ("T", "T"), ("cross", "cross"), ("triangle", "triangle"), ("star", "star"), ]; let positions: &[(StrokePosition, &str)] = &[ (StrokePosition::Inside, "inside"), (StrokePosition::Center, "center"), (StrokePosition::Outside, "outside"), ]; let sizes: &[f32] = &[5.0, 15.0]; let fx_opacities: &[f32] = &[0.75, 1.0]; let stroke_colors: &[([u8; 4], &str)] = &[ ([220, 30, 30, 255], "red"), ([30, 180, 30, 255], "green"), ([30, 60, 220, 255], "blue"), ([220, 220, 220, 255], "white"), ]; let total = shapes.len() * positions.len() * sizes.len() * fx_opacities.len() * stroke_colors.len(); println!("Generating {} stroke PSDs...", total); let mut count = 0usize; let mut errors = 0usize; for &(shape_name, shape_tag) in shapes { for &(position, pos_name) in positions { for &size in sizes { for &opacity in fx_opacities { for &(color, color_name) in stroke_colors { count += 1; let filename = format!( "st_{}_{}_sz{}_op{}_{}.psd", shape_tag, pos_name, size as u32, (opacity * 100.0) as u32, color_name ); let path = out_dir.join(&filename); if path.exists() { continue; } let pixels = make_shape(shape_name, canvas); let effect = LayerEffect::Stroke { enabled: true, position, blend_mode: "normal".to_string(), color, opacity, size, fill_type: hcie_protocol::effects::StrokeFillType::Color, }; let layer = Layer { name: "shape".to_string(), layer_type: LayerType::Raster, data: LayerData::Raster, pixels: pixels.clone(), width: canvas, height: canvas, visible: true, opacity: 1.0, blend_mode: BlendMode::Normal, locked: false, dirty: false, id: 1, parent_id: None, styles: vec![], clipping_mask: false, collapsed: false, adjustment: None, mask_pixels: None, mask_bounds: None, mask_default_color: 0, curve_points: vec![], fill_opacity: 1.0, effects: vec![effect], effects_dirty: AtomicBool::new(false), effects_cache: Mutex::new(None), adjustment_raw: None, is_section_divider: false, image_resources_raw: None, }; match hcie_psd::save_psd(&[layer], canvas, canvas, &pixels, &path) { Ok(()) => {} Err(e) => { eprintln!(" ERROR {}: {}", filename, e); errors += 1; } } if count % 20 == 0 { println!(" [{}/{}] errors={}", count, total, errors); } } } } } } println!("Done. {} PSDs generated, {} errors.", count - errors, errors); }