Files
2026-07-09 02:59:53 +03:00

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);
}
}