fn main() { let orig_bytes = std::fs::read("_images/_psd_stil_test/inner_shadow/black_triangle_green_inner_shadow_normal_blend.psd").unwrap(); let saved_bytes = std::fs::read("_tmp/roundtrip_output.psd").unwrap(); // Parse effects from both files println!("=== ORIGINAL PSD EFFECTS ==="); let orig_layers = hcie_psd::import_psd(std::path::Path::new("_images/_psd_stil_test/inner_shadow/black_triangle_green_inner_shadow_normal_blend.psd")).unwrap(); for (i, layer) in orig_layers.iter().enumerate() { println!("Layer {}: '{}' effects={}", i, layer.name, layer.effects.len()); for (j, fx) in layer.effects.iter().enumerate() { print_effect(j, fx); } } println!("\n=== SAVED PSD EFFECTS ==="); let saved_layers = hcie_psd::import_psd(std::path::Path::new("_tmp/roundtrip_output.psd")).unwrap(); for (i, layer) in saved_layers.iter().enumerate() { println!("Layer {}: '{}' effects={}", i, layer.name, layer.effects.len()); for (j, fx) in layer.effects.iter().enumerate() { print_effect(j, fx); } } // Compare lfx2 raw bytes more carefully println!("\n=== LFX2 RAW DEEP DIVE ==="); let orig_lfx2 = extract_lfx2_from_layer1(&orig_bytes); let saved_lfx2 = extract_lfx2_from_layer1(&saved_bytes); if let (Some(o), Some(s)) = (&orig_lfx2, &saved_lfx2) { println!("ORIG lfx2: {} bytes", o.len()); println!("SAVED lfx2: {} bytes", s.len()); // Find ALL differences let min_len = o.len().min(s.len()); let mut diffs = Vec::new(); for i in 0..min_len { if o[i] != s[i] { diffs.push(i); } } println!("Total differing bytes: {} (in shared {} bytes)", diffs.len(), min_len); // Show context around each diff region let mut i = 0; while i < diffs.len() { let start = diffs[i]; let mut end = start; while i < diffs.len() && diffs[i] - end < 4 { end = diffs[i]; i += 1; } let ctx_start = start.saturating_sub(8); let ctx_end = (end + 24).min(min_len); println!("\n Diff region @{}..{}:", start, end); println!(" ORIG : {}", hex_dump_range(&o[ctx_start..ctx_end], start - ctx_start)); println!(" SAVED: {}", hex_dump_range(&s[ctx_start..ctx_end], start - ctx_start)); } // Show extra bytes in ORIG if o.len() > s.len() { println!("\n ORIG extra tail ({} bytes):", o.len() - s.len()); println!(" {}", hex_dump(&o[s.len()..])); } } } fn print_effect(idx: usize, fx: &hcie_protocol::effects::LayerEffect) { use hcie_protocol::effects::LayerEffect::*; let name = match fx { DropShadow { .. } => "DropShadow", InnerShadow { .. } => "InnerShadow", OuterGlow { .. } => "OuterGlow", InnerGlow { .. } => "InnerGlow", BevelEmboss { .. } => "BevelEmboss", Satin { .. } => "Satin", ColorOverlay { .. } => "ColorOverlay", GradientOverlay { .. } => "GradientOverlay", PatternOverlay { .. } => "PatternOverlay", Stroke { .. } => "Stroke", }; match fx { InnerShadow { enabled, blend_mode, color, opacity, angle, distance, choke, size, noise, .. } => { println!(" [{}] {} enabled={} blend={:?} color={:?} op={:.2} angle={:.1} dist={:.1} choke={:.1} size={:.1} noise={:.1}", idx, name, enabled, blend_mode, color, opacity, angle, distance, choke, size, noise); } BevelEmboss { enabled, style, technique, depth, direction, size, soften, angle, altitude, .. } => { println!(" [{}] {} enabled={:?} style={:?} technique={:?} depth={:.1} dir={:?} size={:.1} soften={:.1} angle={:.1} alt={:.1}", idx, name, enabled, style, technique, depth, direction, size, soften, angle, altitude); } _ => { println!(" [{}] {}", idx, name); } } } fn extract_lfx2_from_layer1(bytes: &[u8]) -> Option> { let cmd_len = u32::from_be_bytes([bytes[26], bytes[27], bytes[28], bytes[29]]) as usize; let ir_start = 30 + cmd_len; let ir_len = u32::from_be_bytes([bytes[ir_start], bytes[ir_start+1], bytes[ir_start+2], bytes[ir_start+3]]) as usize; let lmi_start = ir_start + 4 + ir_len; let lmi_len = u32::from_be_bytes([bytes[lmi_start], bytes[lmi_start+1], bytes[lmi_start+2], bytes[lmi_start+3]]) as usize; let lmi = &bytes[lmi_start+4..lmi_start+4+lmi_len]; let li_len = u32::from_be_bytes([lmi[0], lmi[1], lmi[2], lmi[3]]) as usize; let li = &lmi[4..4+li_len]; let layer_count = i16::from_be_bytes([li[0], li[1]]).unsigned_abs() as usize; let mut pos = 2; for _ in 0..layer_count { if pos + 18 > li.len() { return None; } let ch_count = u16::from_be_bytes([li[pos+16], li[pos+17]]) as usize; pos += 18; pos += ch_count * 6; if pos + 12 > li.len() { return None; } pos += 12; let extra_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize; pos += 4; let extra_end = pos + extra_len; // Skip mask + blend + name if pos + 4 > extra_end { return None; } let mask_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize; pos += 4 + mask_len; if pos + 4 > extra_end { return None; } let blend_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize; pos += 4 + blend_len; if pos >= extra_end { return None; } let name_len = li[pos] as usize; pos += 1 + name_len; pos += (4 - ((name_len + 1) % 4)) % 4; while pos + 8 <= extra_end { let sig = &li[pos..pos+4]; if sig != b"8BIM" && sig != b"8B64" { break; } let key = &li[pos+4..pos+8]; let block_len = u32::from_be_bytes([li[pos+8], li[pos+9], li[pos+10], li[pos+11]]) as usize; if key == b"lfx2" { return Some(li[pos+12..pos+12+block_len].to_vec()); } pos += 12 + block_len; if block_len % 2 != 0 { pos += 1; } } pos = extra_end; } None } fn hex_dump(data: &[u8]) -> String { data.iter().take(64).map(|b| format!("{:02X}", b)).collect::>().join(" ") } fn hex_dump_range(data: &[u8], highlight_offset: usize) -> String { data.iter().enumerate().map(|(i, b)| { let s = format!("{:02X}", b); if i == highlight_offset { format!("[{}]", s) } else { s } }).collect::>().join(" ") }