142 lines
5.8 KiB
Rust
142 lines
5.8 KiB
Rust
//! Test: read Example3-mini.psd, then save it as hcie PSD and compare
|
|
//! layer records with the original.
|
|
|
|
fn main() {
|
|
let input_path = "/mnt/extra/00_PROJECTS/hcie-rust-_images/_test_images/example3/Example3-mini.psd";
|
|
let output_path = "/mnt/extra/00_PROJECTS/hcie-rust-_images/_test_images/example3/Example3-mini-hcie-v2.psd";
|
|
|
|
println!("Reading original PSD...");
|
|
let orig_bytes = std::fs::read(input_path).unwrap();
|
|
|
|
// Import via hcie-psd
|
|
let layers = hcie_psd::psd_import::import_psd(std::path::Path::new(input_path)).unwrap();
|
|
println!("Imported {} layers", layers.len());
|
|
|
|
for (i, l) in layers.iter().enumerate() {
|
|
println!(" Layer {}: \"{}\" blend={:?} opacity={} visible={} type={:?} mask={:?} adj={} div={}",
|
|
i, l.name, l.blend_mode, l.opacity, l.visible, l.layer_type,
|
|
l.mask_bounds.is_some(), l.adjustment_raw.is_some(), l.is_section_divider);
|
|
}
|
|
|
|
// Read canvas from PSD header
|
|
let psd = hcie_psd::Psd::from_bytes(&orig_bytes).unwrap();
|
|
let canvas_w = psd.width();
|
|
let canvas_h = psd.height();
|
|
println!("Canvas: {}x{}", canvas_w, canvas_h);
|
|
|
|
// Create composited image (simple white background for now)
|
|
let composited = vec![255u8; (canvas_w * canvas_h * 4) as usize];
|
|
|
|
// Save
|
|
println!("Saving to {}...", output_path);
|
|
hcie_psd_saver::save_psd(&layers, canvas_w, canvas_h, &composited, std::path::Path::new(output_path)).unwrap();
|
|
println!("Saved!");
|
|
|
|
// Now compare the saved output with original
|
|
let saved_bytes = std::fs::read(output_path).unwrap();
|
|
|
|
println!("\n=== Comparison ===");
|
|
println!("Original: {} bytes", orig_bytes.len());
|
|
println!("Saved: {} bytes", saved_bytes.len());
|
|
|
|
// Compare layer records
|
|
println!("\n--- Original ---");
|
|
dump_layer_meta(&orig_bytes, "ORIG");
|
|
println!("\n--- Saved ---");
|
|
dump_layer_meta(&saved_bytes, "SAVE");
|
|
}
|
|
|
|
fn dump_layer_meta(data: &[u8], label: &str) {
|
|
let mut pos = 26;
|
|
let cm_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
pos += 4 + cm_len;
|
|
let ir_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
pos += 4 + ir_len;
|
|
let _lm_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
pos += 4;
|
|
let _li_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
pos += 4;
|
|
let layer_count = i16::from_be_bytes([data[pos], data[pos+1]]).unsigned_abs() as usize;
|
|
pos += 2;
|
|
|
|
for idx in 0..layer_count {
|
|
pos += 16; // bounds
|
|
let ch_count = u16::from_be_bytes([data[pos], data[pos+1]]) as usize;
|
|
pos += 2;
|
|
let mut channels = Vec::new();
|
|
for _ in 0..ch_count {
|
|
let id = i16::from_be_bytes([data[pos], data[pos+1]]);
|
|
let len = u32::from_be_bytes([data[pos+2], data[pos+3], data[pos+4], data[pos+5]]) as usize;
|
|
channels.push((id, len));
|
|
pos += 6;
|
|
}
|
|
pos += 4; // sig
|
|
let blend_key = std::str::from_utf8(&data[pos..pos+4]).unwrap_or("????");
|
|
pos += 4;
|
|
let opacity = data[pos]; pos += 1;
|
|
let clipping = data[pos]; pos += 1;
|
|
let flags = data[pos]; pos += 1;
|
|
pos += 1; // filler
|
|
|
|
let extra_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
pos += 4;
|
|
let extra_start = pos;
|
|
|
|
let mask_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
pos += 4;
|
|
let mask_hex = if mask_len > 0 {
|
|
let show = mask_len.min(30);
|
|
let hex: Vec<String> = data[pos..pos+show].iter().map(|b| format!("{:02x}", b)).collect();
|
|
hex.join(" ")
|
|
} else { "none".to_string() };
|
|
pos += mask_len;
|
|
|
|
let br_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
pos += 4 + br_len;
|
|
|
|
let name_len = data[pos] as usize;
|
|
pos += 1;
|
|
let name = String::from_utf8_lossy(&data[pos..pos + name_len]).to_string();
|
|
pos += name_len;
|
|
let pascal_total = 1 + name_len;
|
|
let pad = (4 - (pascal_total % 4)) % 4;
|
|
pos += pad;
|
|
|
|
let ch_str = channels.iter().map(|(id, len)| format!("{}:{}", id, len)).collect::<Vec<_>>().join(",");
|
|
print!("[{}] L{}: \"{}\" blend={} op={} clip={} flags={:08b} ch=[{}] mask_len={}",
|
|
label, idx, name, blend_key, opacity, clipping, flags, ch_str, mask_len);
|
|
|
|
// Check for lsct
|
|
let mut tag_pos = pos;
|
|
while tag_pos < extra_start + extra_len {
|
|
let tag_sig = &data[tag_pos..tag_pos+4];
|
|
if tag_sig != b"8BIM" && tag_sig != b"8B64" { break; }
|
|
tag_pos += 4;
|
|
let key = std::str::from_utf8(&data[tag_pos..tag_pos+4]).unwrap_or("????");
|
|
tag_pos += 4;
|
|
let block_len = u32::from_be_bytes([data[tag_pos], data[tag_pos+1], data[tag_pos+2], data[tag_pos+3]]) as usize;
|
|
tag_pos += 4;
|
|
|
|
if key == "lsct" {
|
|
let section_type = if block_len >= 4 {
|
|
u32::from_be_bytes([data[tag_pos], data[tag_pos+1], data[tag_pos+2], data[tag_pos+3]])
|
|
} else { 999 };
|
|
let blend = if block_len >= 12 {
|
|
std::str::from_utf8(&data[tag_pos+8..tag_pos+12]).unwrap_or("????").to_string()
|
|
} else { "n/a".to_string() };
|
|
print!(" lsct:type={},blend={}", section_type, blend);
|
|
}
|
|
|
|
tag_pos += block_len;
|
|
if block_len % 2 != 0 { tag_pos += 1; }
|
|
}
|
|
|
|
println!();
|
|
if mask_len > 0 {
|
|
println!(" mask: {}", mask_hex);
|
|
}
|
|
|
|
pos = extra_start + extra_len;
|
|
}
|
|
}
|