228 lines
9.8 KiB
Rust
228 lines
9.8 KiB
Rust
|
|
|
||
|
|
fn main() {
|
||
|
|
let args: Vec<String> = std::env::args().collect();
|
||
|
|
let input = args.get(1).map(|s| s.as_str()).unwrap_or("_images/_psd_stil_test/inner_shadow/black_triangle_green_inner_shadow_normal_blend.psd");
|
||
|
|
let saved = args.get(2).map(|s| s.as_str()).unwrap_or("_tmp/roundtrip_output.psd");
|
||
|
|
|
||
|
|
let orig_bytes = std::fs::read(input).expect("read input");
|
||
|
|
let saved_bytes = std::fs::read(saved).expect("read saved");
|
||
|
|
|
||
|
|
println!("=== DETAILED PSD STRUCTURE COMPARISON ===\n");
|
||
|
|
|
||
|
|
// Parse sections from both
|
||
|
|
println!("ORIG: {} bytes", orig_bytes.len());
|
||
|
|
dump_psd_structure("ORIG", &orig_bytes);
|
||
|
|
println!();
|
||
|
|
println!("SAVED: {} bytes", saved_bytes.len());
|
||
|
|
dump_psd_structure("SAVED", &saved_bytes);
|
||
|
|
|
||
|
|
// Detailed image resources comparison
|
||
|
|
println!("\n=== IMAGE RESOURCES DETAIL ===");
|
||
|
|
let (o_ir_off, o_ir_len) = get_section_offset_len(&orig_bytes, "ir");
|
||
|
|
let (s_ir_off, s_ir_len) = get_section_offset_len(&saved_bytes, "ir");
|
||
|
|
|
||
|
|
println!("ORIG image resources ({} bytes at offset {}):", o_ir_len, o_ir_off);
|
||
|
|
dump_resources(&orig_bytes[o_ir_off..o_ir_off + o_ir_len]);
|
||
|
|
|
||
|
|
println!("\nSAVED image resources ({} bytes at offset {}):", s_ir_len, s_ir_off);
|
||
|
|
dump_resources(&saved_bytes[s_ir_off..s_ir_off + s_ir_len]);
|
||
|
|
|
||
|
|
// Detailed layer records comparison
|
||
|
|
println!("\n=== LAYER RECORDS DETAIL ===");
|
||
|
|
let (o_lmi_off, o_lmi_len) = get_section_offset_len(&orig_bytes, "lmi");
|
||
|
|
let (s_lmi_off, s_lmi_len) = get_section_offset_len(&saved_bytes, "lmi");
|
||
|
|
|
||
|
|
let o_lmi = &orig_bytes[o_lmi_off..o_lmi_off + o_lmi_len];
|
||
|
|
let s_lmi = &saved_bytes[s_lmi_off..s_lmi_off + s_lmi_len];
|
||
|
|
|
||
|
|
println!("ORIG Layer&Mask ({} bytes):", o_lmi_len);
|
||
|
|
dump_layer_info("ORIG", o_lmi);
|
||
|
|
println!();
|
||
|
|
println!("SAVED Layer&Mask ({} bytes):", s_lmi_len);
|
||
|
|
dump_layer_info("SAVED", s_lmi);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn get_section_offset_len(bytes: &[u8], section: &str) -> (usize, usize) {
|
||
|
|
// Header: 26 bytes
|
||
|
|
// Color Mode Data: 4 + len
|
||
|
|
let cmd_len = u32::from_be_bytes([bytes[26], bytes[27], bytes[28], bytes[29]]) as usize;
|
||
|
|
let ir_start = 26 + 4 + 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;
|
||
|
|
|
||
|
|
match section {
|
||
|
|
"cmd" => (30, cmd_len),
|
||
|
|
"ir" => (ir_start + 4, ir_len),
|
||
|
|
"lmi" => (lmi_start + 4, lmi_len),
|
||
|
|
"mid" => (lmi_start + 4 + lmi_len, bytes.len() - lmi_start - 4 - lmi_len),
|
||
|
|
_ => (0, 0),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn dump_psd_structure(label: &str, bytes: &[u8]) {
|
||
|
|
let cmd_len = u32::from_be_bytes([bytes[26], bytes[27], bytes[28], bytes[29]]) as usize;
|
||
|
|
let ir_start = 26 + 4 + 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 mid_start = lmi_start + 4 + lmi_len;
|
||
|
|
let mid_len = bytes.len() - mid_start;
|
||
|
|
|
||
|
|
// Header detail
|
||
|
|
let ch = u16::from_be_bytes([bytes[12], bytes[13]]);
|
||
|
|
let h = u32::from_be_bytes([bytes[14], bytes[15], bytes[16], bytes[17]]);
|
||
|
|
let w = u32::from_be_bytes([bytes[18], bytes[19], bytes[20], bytes[21]]);
|
||
|
|
let depth = u16::from_be_bytes([bytes[22], bytes[23]]);
|
||
|
|
let cmode = u16::from_be_bytes([bytes[24], bytes[25]]);
|
||
|
|
|
||
|
|
println!("{}: Header ch={} {}x{} depth={} cmode={}", label, ch, w, h, depth, cmode);
|
||
|
|
println!(" Color Mode Data: {} bytes @ offset 30", cmd_len);
|
||
|
|
println!(" Image Resources: {} bytes @ offset {}", ir_len, ir_start + 4);
|
||
|
|
println!(" Layer & Mask: {} bytes @ offset {}", lmi_len, lmi_start + 4);
|
||
|
|
println!(" Merged Image: {} bytes @ offset {}", mid_len, mid_start);
|
||
|
|
|
||
|
|
// Layer info detail
|
||
|
|
if lmi_len >= 4 {
|
||
|
|
let li_len = u32::from_be_bytes([bytes[lmi_start+4], bytes[lmi_start+5], bytes[lmi_start+6], bytes[lmi_start+7]]) as usize;
|
||
|
|
let glmi_len = lmi_len - 4 - li_len;
|
||
|
|
println!(" Layer Info: {} bytes", li_len);
|
||
|
|
println!(" Global Mask: {} bytes", glmi_len);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn dump_resources(data: &[u8]) {
|
||
|
|
let mut pos = 0;
|
||
|
|
while pos + 12 <= data.len() {
|
||
|
|
if &data[pos..pos+4] != b"8BIM" {
|
||
|
|
println!(" @{}: bad signature {:02X?}", pos, &data[pos..pos+4.min(data.len()-pos)]);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
let res_id = u16::from_be_bytes([data[pos+4], data[pos+5]]);
|
||
|
|
let name_len = data[pos+6] as usize;
|
||
|
|
let total_name = 1 + name_len;
|
||
|
|
let name_pad = if total_name % 2 == 1 { 1 } else { 0 };
|
||
|
|
let data_len_off = pos + 7 + name_len + name_pad;
|
||
|
|
if data_len_off + 4 > data.len() { break; }
|
||
|
|
let data_len = u32::from_be_bytes([data[data_len_off], data[data_len_off+1], data[data_len_off+2], data[data_len_off+3]]) as usize;
|
||
|
|
let data_start = data_len_off + 4;
|
||
|
|
let data_pad = if data_len % 2 != 0 { 1 } else { 0 };
|
||
|
|
let total = data_start + data_len + data_pad;
|
||
|
|
println!(" 8BIM 0x{:04X} name_len={} data_len={} @{}..{}", res_id, name_len, data_len, pos, total);
|
||
|
|
pos = total;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn dump_layer_info(_label: &str, lmi: &[u8]) {
|
||
|
|
if lmi.len() < 4 {
|
||
|
|
println!(" too short");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let li_len = u32::from_be_bytes([lmi[0], lmi[1], lmi[2], lmi[3]]) as usize;
|
||
|
|
println!(" Layer info length: {}", li_len);
|
||
|
|
if li_len < 2 || li_len > lmi.len() - 4 {
|
||
|
|
println!(" invalid length");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let li = &lmi[4..4+li_len];
|
||
|
|
let layer_count_raw = i16::from_be_bytes([li[0], li[1]]);
|
||
|
|
let layer_count = layer_count_raw.unsigned_abs() as usize;
|
||
|
|
println!(" Layer count raw: {} ({} layers)", layer_count_raw, layer_count);
|
||
|
|
|
||
|
|
let mut pos = 2;
|
||
|
|
for i in 0..layer_count {
|
||
|
|
if pos + 40 > li.len() { println!(" layer {}: truncated", i); break; }
|
||
|
|
let top = i32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]);
|
||
|
|
let left = i32::from_be_bytes([li[pos+4], li[pos+5], li[pos+6], li[pos+7]]);
|
||
|
|
let bottom = i32::from_be_bytes([li[pos+8], li[pos+9], li[pos+10], li[pos+11]]);
|
||
|
|
let right = i32::from_be_bytes([li[pos+12], li[pos+13], li[pos+14], li[pos+15]]);
|
||
|
|
let ch_count = u16::from_be_bytes([li[pos+16], li[pos+17]]) as usize;
|
||
|
|
pos += 18;
|
||
|
|
|
||
|
|
print!(" Layer {}: rect=({},{},{},{}) channels={}", i, top, left, bottom, right, ch_count);
|
||
|
|
let mut ch_infos = Vec::new();
|
||
|
|
for _c in 0..ch_count {
|
||
|
|
if pos + 6 > li.len() { break; }
|
||
|
|
let ch_id = i16::from_be_bytes([li[pos], li[pos+1]]);
|
||
|
|
let ch_len = u32::from_be_bytes([li[pos+2], li[pos+3], li[pos+4], li[pos+5]]);
|
||
|
|
ch_infos.push((ch_id, ch_len));
|
||
|
|
pos += 6;
|
||
|
|
}
|
||
|
|
print!(" ch_data=[");
|
||
|
|
for (j, (id, len)) in ch_infos.iter().enumerate() {
|
||
|
|
if j > 0 { print!(", "); }
|
||
|
|
print!("id={}len={}", id, len);
|
||
|
|
}
|
||
|
|
println!("]");
|
||
|
|
|
||
|
|
// Blend mode, opacity, flags
|
||
|
|
if pos + 12 > li.len() { println!(" truncated at blend"); break; }
|
||
|
|
let sig = &li[pos..pos+4];
|
||
|
|
let bm = &li[pos+4..pos+8];
|
||
|
|
let opacity = li[pos+8];
|
||
|
|
let clipping = li[pos+9];
|
||
|
|
let flags = li[pos+10];
|
||
|
|
pos += 12;
|
||
|
|
println!(" blend_sig={} blend={} op={} clip={} flags={}",
|
||
|
|
String::from_utf8_lossy(sig), String::from_utf8_lossy(bm), opacity, clipping, flags);
|
||
|
|
|
||
|
|
// Extra data
|
||
|
|
if pos + 4 > li.len() { println!(" truncated at extra"); break; }
|
||
|
|
let extra_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize;
|
||
|
|
pos += 4;
|
||
|
|
let extra_start = pos;
|
||
|
|
println!(" extra_data_len={}", extra_len);
|
||
|
|
|
||
|
|
// Parse extra data blocks
|
||
|
|
let extra_end = extra_start + extra_len;
|
||
|
|
// Skip mask data
|
||
|
|
if pos + 4 > li.len() || pos + 4 > extra_end { println!(" truncated"); break; }
|
||
|
|
let mask_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize;
|
||
|
|
pos += 4 + mask_len;
|
||
|
|
|
||
|
|
// Skip blending ranges
|
||
|
|
if pos + 4 > li.len() || pos + 4 > extra_end { break; }
|
||
|
|
let blend_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize;
|
||
|
|
pos += 4 + blend_len;
|
||
|
|
|
||
|
|
// Layer name (Pascal string, padded to 4)
|
||
|
|
if pos >= extra_end || pos >= li.len() { break; }
|
||
|
|
let name_len = li[pos] as usize;
|
||
|
|
pos += 1;
|
||
|
|
if pos + name_len > li.len() { break; }
|
||
|
|
let name = String::from_utf8_lossy(&li[pos..pos+name_len]);
|
||
|
|
pos += name_len;
|
||
|
|
let bytes_mod_4 = (name_len + 1) % 4;
|
||
|
|
let pad = (4 - bytes_mod_4) % 4;
|
||
|
|
pos += pad;
|
||
|
|
println!(" name='{}'", name);
|
||
|
|
|
||
|
|
// Parse tagged blocks
|
||
|
|
while pos + 8 <= extra_end && pos + 8 <= li.len() {
|
||
|
|
let sig = &li[pos..pos+4];
|
||
|
|
if sig != b"8BIM" && sig != b"8B64" {
|
||
|
|
println!(" bad block sig at +{}: {:02X?}", pos - extra_start, &li[pos..pos+4.min(li.len()-pos)]);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
let key = &li[pos+4..pos+8];
|
||
|
|
if pos + 12 > li.len() { break; }
|
||
|
|
let block_len = u32::from_be_bytes([li[pos+8], li[pos+9], li[pos+10], li[pos+11]]) as usize;
|
||
|
|
let block_start = pos + 12;
|
||
|
|
println!(" block {} len={} @+{}", String::from_utf8_lossy(key), block_len, pos - extra_start);
|
||
|
|
pos = block_start + block_len;
|
||
|
|
if block_len % 2 != 0 { pos += 1; }
|
||
|
|
}
|
||
|
|
|
||
|
|
pos = extra_end;
|
||
|
|
println!();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Global Mask Info
|
||
|
|
if 4 + li_len + 4 <= lmi.len() {
|
||
|
|
let glmi_off = 4 + li_len;
|
||
|
|
let glmi_len_val = u32::from_be_bytes([lmi[glmi_off], lmi[glmi_off+1], lmi[glmi_off+2], lmi[glmi_off+3]]) as usize;
|
||
|
|
println!(" Global Mask Info: {} bytes", glmi_len_val);
|
||
|
|
}
|
||
|
|
}
|