Files
hcie-rust-v3.05/hcie-io/examples/psd_struct_compare.rs
T

201 lines
8.8 KiB
Rust
Raw Normal View History

2026-07-09 02:59:53 +03:00
//! PSD structural comparison tool — compares original vs saved byte by byte at section level.
fn main() {
let args: Vec<String> = std::env::args().collect();
let orig_path = args.get(1).map(|s| s.as_str()).unwrap_or("_images/_test_images/example3/Example3-mini.psd");
let saved_path = args.get(2).map(|s| s.as_str()).unwrap_or("_images/_test_images/example3/Example3-mini-hcie.psd");
let orig = std::fs::read(orig_path).expect("read orig");
let saved = std::fs::read(saved_path).expect("read saved");
println!("=== File sizes ===");
println!(" orig: {} bytes", orig.len());
println!(" saved: {} bytes", saved.len());
// Parse section boundaries for both files
let o_sec = parse_sections(&orig);
let s_sec = parse_sections(&saved);
println!("\n=== Section Layout ===");
println!("Section | Original | Saved | Match");
println!("-------------------------|-------------------------|-------------------------|------");
compare_section("File Header (0..26)", 0, 26, 0, 26, &orig, &saved);
compare_section("Color Mode Data", o_sec.color_start, o_sec.color_end, s_sec.color_start, s_sec.color_end, &orig, &saved);
compare_section("Image Resources", o_sec.imgres_start, o_sec.imgres_end, s_sec.imgres_start, s_sec.imgres_end, &orig, &saved);
compare_section("Layer&Mask Info", o_sec.lmi_start, o_sec.lmi_end, s_sec.lmi_start, s_sec.lmi_end, &orig, &saved);
compare_section("Layer Info (inner)", o_sec.li_start, o_sec.li_end, s_sec.li_start, s_sec.li_end, &orig, &saved);
compare_section("Image Data", o_sec.imgdata_start, orig.len(), s_sec.imgdata_start, saved.len(), &orig, &saved);
// Detailed image data comparison
println!("\n=== Image Data Section Detail ===");
let o_img = &orig[o_sec.imgdata_start..];
let s_img = &saved[s_sec.imgdata_start..];
if o_img.len() >= 2 && s_img.len() >= 2 {
let o_comp = u16::from_be_bytes([o_img[0], o_img[1]]);
let s_comp = u16::from_be_bytes([s_img[0], s_img[1]]);
println!(" Compression: orig={} saved={}", o_comp, s_comp);
}
// Compare header bytes
println!("\n=== Header Byte Differences ===");
for i in 0..26.min(orig.len()).min(saved.len()) {
if orig[i] != saved[i] {
println!(" byte[{}]: orig=0x{:02X} saved=0x{:02X} ({})", i, orig[i], saved[i], header_field_name(i));
}
}
// Check color profile in image resources
println!("\n=== Color Profile Check ===");
let o_res = &orig[o_sec.imgres_start..o_sec.imgres_end];
let s_res = &saved[s_sec.imgres_start..s_sec.imgres_end];
check_icc_profile("orig", o_res);
check_icc_profile("saved", s_res);
// Check if saved has correct resolution info
println!("\n=== Resolution Info (0x03ED) ===");
check_resolution_info("orig", o_res);
check_resolution_info("saved", s_res);
// Layer and mask info structure
println!("\n=== Layer & Mask Info Structure ===");
let o_lmi = &orig[o_sec.lmi_start..o_sec.lmi_end];
let s_lmi = &saved[s_sec.lmi_start..s_sec.lmi_end];
if o_lmi.len() >= 8 && s_lmi.len() >= 8 {
let o_lmi_len = u32::from_be_bytes([o_lmi[0], o_lmi[1], o_lmi[2], o_lmi[3]]);
let o_li_len = u32::from_be_bytes([o_lmi[4], o_lmi[5], o_lmi[6], o_lmi[7]]);
let s_lmi_len = u32::from_be_bytes([s_lmi[0], s_lmi[1], s_lmi[2], s_lmi[3]]);
let s_li_len = u32::from_be_bytes([s_lmi[4], s_lmi[5], s_lmi[6], s_lmi[7]]);
println!(" LMI length field: orig={} saved={}", o_lmi_len, s_lmi_len);
println!(" LI length field: orig={} saved={}", o_li_len, s_li_len);
println!(" LMI data bytes: orig={} saved={}", o_lmi.len(), s_lmi.len());
let o_layer_count = i16::from_be_bytes([o_lmi[8], o_lmi[9]]).abs();
let s_layer_count = i16::from_be_bytes([s_lmi[8], s_lmi[9]]).abs();
println!(" Layer count: orig={} saved={}", o_layer_count, s_layer_count);
}
}
struct Sections {
color_start: usize,
color_end: usize,
imgres_start: usize,
imgres_end: usize,
lmi_start: usize,
lmi_end: usize,
li_start: usize,
li_end: usize,
imgdata_start: usize,
}
fn parse_sections(bytes: &[u8]) -> Sections {
let mut o = 26usize;
let cmd_len = u32::from_be_bytes([bytes[o], bytes[o+1], bytes[o+2], bytes[o+3]]) as usize;
o += 4;
let color_start = o;
o += cmd_len;
let color_end = o;
let ir_len = u32::from_be_bytes([bytes[o], bytes[o+1], bytes[o+2], bytes[o+3]]) as usize;
o += 4;
let imgres_start = o;
o += ir_len;
let imgres_end = o;
let lmi_len = u32::from_be_bytes([bytes[o], bytes[o+1], bytes[o+2], bytes[o+3]]) as usize;
o += 4;
let lmi_start = o;
o += lmi_len;
let lmi_end = o;
let li_len = u32::from_be_bytes([bytes[lmi_start], bytes[lmi_start+1], bytes[lmi_start+2], bytes[lmi_start+3]]) as usize;
let li_start = lmi_start + 4;
let li_end = li_start + li_len;
Sections {
color_start, color_end,
imgres_start, imgres_end,
lmi_start, lmi_end,
li_start, li_end,
imgdata_start: lmi_end,
}
}
fn compare_section(name: &str, o_start: usize, o_end: usize, s_start: usize, s_end: usize, orig: &[u8], saved: &[u8]) {
let o_data = &orig[o_start..o_end];
let s_data = &saved[s_start..s_end];
let len_match = o_data.len() == s_data.len();
let content_match = o_data == s_data;
let match_str = if content_match {
"YES".to_string()
} else if len_match {
format!("len OK, content diff ({} bytes)", o_data.len())
} else {
format!("orig={} saved={}", o_data.len(), s_data.len())
};
println!(" {:<24}| {}..{} ({}) | {}..{} ({}) | {}", name, o_start, o_end, o_data.len(), s_start, s_end, s_data.len(), match_str);
}
fn header_field_name(i: usize) -> &'static str {
match i {
0..=3 => "signature",
4..=5 => "version",
6..=11 => "reserved",
12..=13 => "channels",
14..=17 => "height",
18..=21 => "width",
22..=23 => "depth",
24..=25 => "color_mode",
_ => "?",
}
}
fn check_icc_profile(label: &str, resources: &[u8]) {
let mut pos = 0;
while pos + 12 <= resources.len() {
if &resources[pos..pos+4] != b"8BIM" { break; }
let id = u16::from_be_bytes([resources[pos+4], resources[pos+5]]);
let name_len = resources[pos+6] as usize;
let name_end = pos + 7 + name_len;
let pad = if (name_len + 1) % 2 == 1 { 1 } else { 0 };
let data_len_pos = name_end + pad;
let data_len = u32::from_be_bytes([resources[data_len_pos], resources[data_len_pos+1], resources[data_len_pos+2], resources[data_len_pos+3]]) as usize;
if id == 0x040F {
println!(" {}: ICC Profile found (id=0x040F, data_len={})", label, data_len);
return;
}
let total = 6 + 1 + name_len + pad + 4 + data_len + (data_len % 2);
pos += total;
}
println!(" {}: No ICC profile (0x040F) found!", label);
}
fn check_resolution_info(label: &str, resources: &[u8]) {
let mut pos = 0;
while pos + 12 <= resources.len() {
if &resources[pos..pos+4] != b"8BIM" { break; }
let id = u16::from_be_bytes([resources[pos+4], resources[pos+5]]);
let name_len = resources[pos+6] as usize;
let name_end = pos + 7 + name_len;
let pad = if (name_len + 1) % 2 == 1 { 1 } else { 0 };
let data_len_pos = name_end + pad;
let data_len = u32::from_be_bytes([resources[data_len_pos], resources[data_len_pos+1], resources[data_len_pos+2], resources[data_len_pos+3]]) as usize;
if id == 0x03ED {
let data_start = data_len_pos + 4;
if data_len >= 16 {
let hres = u32::from_be_bytes([resources[data_start], resources[data_start+1], resources[data_start+2], resources[data_start+3]]);
let hres_unit = u16::from_be_bytes([resources[data_start+4], resources[data_start+5]]);
let width_unit = u16::from_be_bytes([resources[data_start+6], resources[data_start+7]]);
let vres = u32::from_be_bytes([resources[data_start+8], resources[data_start+9], resources[data_start+10], resources[data_start+11]]);
let vres_unit = u16::from_be_bytes([resources[data_start+12], resources[data_start+13]]);
let height_unit = u16::from_be_bytes([resources[data_start+14], resources[data_start+15]]);
println!(" {}: hRes=0x{:08X} ({:.1} PPI) hResUnit={} widthUnit={} vRes=0x{:08X} ({:.1} PPI) vResUnit={} heightUnit={}",
label, hres, (hres as f64) / 65536.0, hres_unit, width_unit, vres, (vres as f64) / 65536.0, vres_unit, height_unit);
return;
}
}
let total = 6 + 1 + name_len + pad + 4 + data_len + (data_len % 2);
pos += total;
}
println!(" {}: No resolution info (0x03ED) found", label);
}