Files
hcie-rust-v3.05/hcie-psd/src/psd_import.rs
T
2026-07-09 02:59:53 +03:00

895 lines
36 KiB
Rust

use std::path::Path;
use std::collections::HashMap;
use log::{warn, error};
use crate::psd_reader::PsdReader;
use crate::Psd;
fn packbits_decompress(src: &[u8], dst: &mut [u8]) -> Result<(), String> {
let mut src_idx = 0;
let mut dst_idx = 0;
while src_idx < src.len() && dst_idx < dst.len() {
let header = src[src_idx] as i8;
src_idx += 1;
if header >= 0 {
let len = (header as usize) + 1;
if src_idx + len > src.len() || dst_idx + len > dst.len() {
return Err("PackBits: Out of bounds literal copy".to_string());
}
dst[dst_idx..dst_idx + len].copy_from_slice(&src[src_idx..src_idx + len]);
src_idx += len;
dst_idx += len;
} else if header != -128 {
let len = (-header as usize) + 1;
if src_idx >= src.len() || dst_idx + len > dst.len() {
return Err("PackBits: Out of bounds repeat copy".to_string());
}
let val = src[src_idx];
src_idx += 1;
for _ in 0..len {
dst[dst_idx] = val;
dst_idx += 1;
}
}
}
Ok(())
}
fn rle_decompress_rows(src: &[u8], width: u32, height: u32) -> Result<Vec<u8>, String> {
let mut dst = vec![0u8; (width * height) as usize];
if src.is_empty() { return Ok(dst); }
let mut r = PsdReader::new(src);
let mut scanline_lens = Vec::with_capacity(height as usize);
for _ in 0..height {
scanline_lens.push(r.read_u16()? as usize);
}
let compressed_start = r.pos();
let mut src_offset = compressed_start;
for row in 0..height {
let row_len = scanline_lens[row as usize];
if src_offset + row_len > src.len() {
return Err("PackBits: Row length exceeds source".to_string());
}
let row_data = &src[src_offset..src_offset + row_len];
src_offset += row_len;
let dst_offset = (row * width) as usize;
let dst_row = &mut dst[dst_offset..dst_offset + width as usize];
packbits_decompress(row_data, dst_row)?;
}
Ok(dst)
}
fn cubic_spline_lut(points: &[(u16, u16)]) -> Vec<u8> {
let mut lut = vec![0u8; 256];
if points.is_empty() {
for i in 0..256 { lut[i] = i as u8; }
return lut;
}
let mut pts: Vec<(f32, f32)> = points.iter()
.map(|p| (p.1 as f32, p.0 as f32))
.collect();
pts.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
pts.dedup_by(|a, b| (a.0 - b.0).abs() < 1e-4);
if pts.len() < 2 {
let val = pts.first().map(|p| p.1).unwrap_or(0.0) as u8;
return vec![val; 256];
}
let n = pts.len();
let mut h = vec![0.0; n - 1];
for i in 0..n - 1 {
h[i] = pts[i + 1].0 - pts[i].0;
}
let mut a = vec![0.0; n];
let mut b = vec![0.0; n];
let mut c = vec![0.0; n];
let mut d = vec![0.0; n];
b[0] = 1.0;
for i in 1..n - 1 {
a[i] = h[i - 1] / 6.0;
b[i] = (h[i - 1] + h[i]) / 3.0;
c[i] = h[i] / 6.0;
d[i] = (pts[i + 1].1 - pts[i].1) / h[i] - (pts[i].1 - pts[i - 1].1) / h[i - 1];
}
b[n - 1] = 1.0;
let mut c_prime = vec![0.0; n];
let mut d_prime = vec![0.0; n];
c_prime[0] = c[0] / b[0];
d_prime[0] = d[0] / b[0];
for i in 1..n {
let denom = b[i] - a[i] * c_prime[i - 1];
if denom.abs() > 1e-6 {
c_prime[i] = c[i] / denom;
d_prime[i] = (d[i] - a[i] * d_prime[i - 1]) / denom;
}
}
let mut m = vec![0.0; n];
m[n - 1] = d_prime[n - 1];
for i in (0..n - 1).rev() {
m[i] = d_prime[i] - c_prime[i] * m[i + 1];
}
for i in 0..256 {
let x = i as f32;
if x <= pts[0].0 {
lut[i] = pts[0].1.clamp(0.0, 255.0).round() as u8;
} else if x >= pts[n - 1].0 {
lut[i] = pts[n - 1].1.clamp(0.0, 255.0).round() as u8;
} else {
for j in 0..n - 1 {
let x0 = pts[j].0;
let x1 = pts[j + 1].0;
if x >= x0 && x <= x1 {
let hj = h[j];
let mj = m[j];
let mj1 = m[j + 1];
let y0 = pts[j].1;
let y1 = pts[j + 1].1;
let term1 = mj * (x1 - x).powi(3) / (6.0 * hj);
let term2 = mj1 * (x - x0).powi(3) / (6.0 * hj);
let term3 = (y0 / hj - hj * mj / 6.0) * (x1 - x);
let term4 = (y1 / hj - hj * mj1 / 6.0) * (x - x0);
let y = term1 + term2 + term3 + term4;
lut[i] = y.clamp(0.0, 255.0).round() as u8;
break;
}
}
}
}
lut
}
struct MaskInfo {
top: i32,
left: i32,
bottom: i32,
right: i32,
default_color: u8,
}
struct ChannelInfo {
id: i16,
len: u32,
}
struct LayerRecord {
name: String,
channels: Vec<ChannelInfo>,
mask_info: Option<MaskInfo>,
adjustment: Option<hcie_blend::Adjustment>,
adjustment_raw: Option<([u8; 4], Vec<u8>)>,
is_group: bool,
is_section_divider: bool,
section_divider_blend: Option<String>,
curve_points: Vec<(u16, u16)>,
effects: Vec<hcie_fx::LayerEffect>,
fill_opacity: f32,
is_text: bool,
text_data: Option<hcie_protocol::LayerData>,
}
pub struct ParsedLayerData {
pub name: String,
pub is_group: bool,
pub is_section_divider: bool,
pub section_divider_blend: Option<String>,
pub adjustment: Option<hcie_blend::Adjustment>,
pub adjustment_raw: Option<([u8; 4], Vec<u8>)>,
pub mask_pixels: Option<Vec<u8>>,
pub mask_bounds: Option<[i32; 4]>,
pub mask_default_color: u8,
pub curve_points: Vec<(u16, u16)>,
pub effects: Vec<hcie_fx::LayerEffect>,
pub fill_opacity: f32,
pub is_text: bool,
pub text_data: Option<hcie_protocol::LayerData>,
}
fn build_gradient_map_luts(
color_stops: &[(u32, u32, [u8; 3])],
) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let mut lut_r = vec![0u8; 256];
let mut lut_g = vec![0u8; 256];
let mut lut_b = vec![0u8; 256];
if color_stops.is_empty() {
return (lut_r, lut_g, lut_b);
}
let mut stops = color_stops.to_vec();
stops.sort_by_key(|s| s.0);
for i in 0..256 {
let x = (i as f32 * 16.0627) as u32;
if x <= stops[0].0 {
lut_r[i] = stops[0].2[0];
lut_g[i] = stops[0].2[1];
lut_b[i] = stops[0].2[2];
} else if x >= stops[stops.len() - 1].0 {
let last = stops.len() - 1;
lut_r[i] = stops[last].2[0];
lut_g[i] = stops[last].2[1];
lut_b[i] = stops[last].2[2];
} else {
for j in 0..stops.len() - 1 {
let x0 = stops[j].0;
let x1 = stops[j + 1].0;
if x >= x0 && x <= x1 {
let t = (x - x0) as f32 / (x1 - x0) as f32;
let midpoint = (stops[j].1 as f32 / 100.0).clamp(0.01, 0.99);
let t_prime = if (midpoint - 0.5).abs() < 1e-4 {
t
} else {
t.powf(0.5_f32.ln() / midpoint.ln())
};
let c0 = stops[j].2;
let c1 = stops[j + 1].2;
lut_r[i] = (c0[0] as f32 + t_prime * (c1[0] as f32 - c0[0] as f32)).clamp(0.0, 255.0).round() as u8;
lut_g[i] = (c0[1] as f32 + t_prime * (c1[1] as f32 - c0[1] as f32)).clamp(0.0, 255.0).round() as u8;
lut_b[i] = (c0[2] as f32 + t_prime * (c1[2] as f32 - c0[2] as f32)).clamp(0.0, 255.0).round() as u8;
break;
}
}
}
}
(lut_r, lut_g, lut_b)
}
pub struct ParsedPsdData {
pub layers: Vec<ParsedLayerData>,
pub image_resources_raw: Vec<u8>,
}
pub fn parse_psd_sequential(bytes: &[u8]) -> Result<Vec<ParsedLayerData>, String> {
parse_psd_sequential_full(bytes).map(|parsed| parsed.layers)
}
pub fn parse_psd_sequential_full(bytes: &[u8]) -> Result<ParsedPsdData, String> {
let mut r = PsdReader::new(bytes);
r.read(26)?;
let color_mode_len = r.read_u32()? as usize;
r.read(color_mode_len)?;
let img_res_start = r.pos();
let img_res_len = r.read_u32()? as usize;
let image_resources_raw = if img_res_len > 0 {
bytes[img_res_start + 4..img_res_start + 4 + img_res_len].to_vec()
} else {
Vec::new()
};
r.read(img_res_len)?;
let _layer_mask_len_val = r.read_u32()? as usize;
let _layer_info_len = r.read_u32()? as usize;
let _layer_info_start = r.pos();
let layer_count_raw = r.read_i16()?;
let layer_count = layer_count_raw.abs() as usize;
let mut layers_meta = Vec::with_capacity(layer_count);
for _idx in 0..layer_count {
let _top = r.read_i32()?;
let _left = r.read_i32()?;
let _bottom = r.read_i32()?;
let _right = r.read_i32()?;
let channel_count = r.read_u16()? as usize;
let mut channels = Vec::new();
for _ in 0..channel_count {
let id = r.read_i16()?;
let len = r.read_u32()?;
channels.push(ChannelInfo { id, len });
}
r.read(4)?;
r.read(4)?;
let _opacity = r.read_u8()?;
let _clipping = r.read_u8()?;
let _flags = r.read_u8()?;
r.read(1)?;
let extra_data_len = r.read_u32()? as usize;
let extra_start = r.pos();
let layer_mask_len = r.read_u32()? as usize;
let mask_info = if layer_mask_len >= 18 {
let top = r.read_i32()?;
let left = r.read_i32()?;
let bottom = r.read_i32()?;
let right = r.read_i32()?;
let default_color = r.read_u8()?;
let _flags = r.read_u8()?;
r.read(layer_mask_len - 18)?;
Some(MaskInfo { top, left, bottom, right, default_color })
} else {
r.read(layer_mask_len)?;
None
};
let layer_blending_ranges_len = r.read_u32()? as usize;
r.read(layer_blending_ranges_len)?;
let name_len = r.read_u8()? as usize;
let name_bytes = r.read(name_len)?;
let name = String::from_utf8_lossy(name_bytes).to_string();
let bytes_mod_4 = (name_len + 1) % 4;
let padding = (4 - bytes_mod_4) % 4;
r.read(padding)?;
let mut is_group = false;
let mut is_section_divider = false;
let mut section_divider_blend: Option<String> = None;
let mut adjustment = None;
let mut adjustment_raw: Option<([u8; 4], Vec<u8>)> = None;
let mut curve_points: Vec<(u16, u16)> = vec![];
let mut effects: Vec<hcie_fx::LayerEffect> = vec![];
let mut fill_opacity = 1.0f32;
let mut is_text = false;
let mut text_data = None;
while r.pos() < extra_start + extra_data_len {
let sig = r.read(4)?;
if sig != b"8BIM" && sig != b"8B64" {
break;
}
let key_bytes = r.read(4)?;
let key = String::from_utf8_lossy(key_bytes).to_string();
let block_len = r.read_u32()? as usize;
let block_start = r.pos();
if key == "lsct" {
let block_data = r.read(block_len)?;
if block_data.len() >= 4 {
let section_type = u32::from_be_bytes([block_data[0], block_data[1], block_data[2], block_data[3]]);
if section_type == 1 || section_type == 3 {
// Type 3 is bounding section; in HCIE it behaves as a group open marker.
is_group = true;
if section_type == 3 {
is_section_divider = false;
}
} else if section_type == 2 || section_type == 4 {
is_group = false;
is_section_divider = true;
if block_data.len() >= 16 {
// lsct layout: 4 bytes type + "8BIM" + 4-byte blend key
let bm = String::from_utf8_lossy(
&block_data[8..12]
).trim().to_string();
section_divider_blend = Some(bm);
}
}
}
} else if key == "curv" {
let block_data = r.read(block_len)?;
adjustment_raw = Some((*b"curv", block_data.to_vec()));
let crv_sig = b"Crv ";
let mut crv_offset = None;
for i in 0..block_data.len().saturating_sub(3) {
if &block_data[i..i+4] == crv_sig {
crv_offset = Some(i + 4);
break;
}
}
if let Some(off) = crv_offset {
let src = &block_data[off..];
if src.len() >= 10 {
let version = u16::from_be_bytes([src[4], src[5]]);
let point_count = u16::from_be_bytes([src[8], src[9]]) as usize;
if version == 1 && src.len() >= 10 + point_count * 4 {
let mut points = Vec::with_capacity(point_count);
let mut poff = 10;
for _ in 0..point_count {
let out_val = u16::from_be_bytes([src[poff], src[poff + 1]]);
let in_val = u16::from_be_bytes([src[poff + 2], src[poff + 3]]);
points.push((out_val, in_val));
poff += 4;
}
let lut_m = cubic_spline_lut(&points);
let lut_r = lut_m.clone();
let lut_g = lut_m.clone();
let lut_b = lut_m.clone();
curve_points = points;
adjustment = Some(hcie_blend::Adjustment::Curves { lut_r, lut_g, lut_b });
}
}
} else if block_data.len() >= 8 {
let version = u16::from_le_bytes([block_data[2], block_data[3]]);
let bitmap = u16::from_le_bytes([block_data[6], block_data[7]]);
if version == 1 {
let mut sub_r = PsdReader::new(&block_data[8..]);
let mut curves = Vec::new();
for bit in 0..16 {
if (bitmap & (1 << bit)) != 0 {
if let Ok(point_count) = sub_r.read_u16_le() {
let mut points = Vec::with_capacity(point_count as usize);
for _ in 0..point_count {
if let (Ok(out_val), Ok(in_val)) = (sub_r.read_u16_le(), sub_r.read_u16_le()) {
points.push((out_val, in_val));
}
}
curves.push(points);
}
}
}
if !curves.is_empty() {
let master_pts = &curves[0];
let lut_m = cubic_spline_lut(master_pts);
let mut lut_r = lut_m.clone();
let mut lut_g = lut_m.clone();
let mut lut_b = lut_m.clone();
if curves.len() >= 4 {
let c_r = cubic_spline_lut(&curves[1]);
let c_g = cubic_spline_lut(&curves[2]);
let c_b = cubic_spline_lut(&curves[3]);
for idx in 0..256 {
lut_r[idx] = c_r[lut_r[idx] as usize];
lut_g[idx] = c_g[lut_g[idx] as usize];
lut_b[idx] = c_b[lut_b[idx] as usize];
}
}
adjustment = Some(hcie_blend::Adjustment::Curves { lut_r, lut_g, lut_b });
}
}
}
} else if key == "grdm" {
let block_data = r.read(block_len)?;
adjustment_raw = Some((*b"grdm", block_data.to_vec()));
if block_data.len() >= 26 {
let mut sub_r = PsdReader::new(&block_data);
let _version = sub_r.read_u16()?;
let _reversed = sub_r.read_u8()?;
let _dithered = sub_r.read_u8()?;
let peek = sub_r.read(4)?;
let grad_name_len = if peek == b"Perc" {
sub_r.read_u32()? as usize
} else {
u32::from_be_bytes([peek[0], peek[1], peek[2], peek[3]]) as usize
};
sub_r.read(grad_name_len * 2)?;
if let Ok(stops_count) = sub_r.read_u16() {
let mut color_stops = Vec::new();
for _ in 0..stops_count {
if let (Ok(loc), Ok(mid), Ok(mode), Ok(c1), Ok(c2), Ok(c3), Ok(_)) = (
sub_r.read_u32(), sub_r.read_u32(), sub_r.read_u16(),
sub_r.read_u16(), sub_r.read_u16(), sub_r.read_u16(), sub_r.read_u16()
) {
let _ = sub_r.read(2);
let r_val = (c1 as f64 / 257.0).round() as u8;
let g_val = (c2 as f64 / 257.0).round() as u8;
let b_val = (c3 as f64 / 257.0).round() as u8;
if mode == 0 {
color_stops.push((loc, mid, [r_val, g_val, b_val]));
}
}
}
let (lut_r, lut_g, lut_b) = build_gradient_map_luts(&color_stops);
adjustment = Some(hcie_blend::Adjustment::GradientMap { lut_r, lut_g, lut_b });
}
}
} else if key == "hue2" {
let block_data = r.read(block_len)?;
adjustment_raw = Some((*b"hue2", block_data.to_vec()));
if block_data.len() >= 16 {
let hue = i16::from_be_bytes([block_data[10], block_data[11]]) as i32;
let saturation = i16::from_be_bytes([block_data[12], block_data[13]]) as i32;
let lightness = i16::from_be_bytes([block_data[14], block_data[15]]) as i32;
adjustment = Some(hcie_blend::Adjustment::HueSaturation { hue, saturation, lightness });
}
} else if key == "iOpa" {
let block_data = r.read(block_len)?;
if !block_data.is_empty() {
fill_opacity = block_data[0] as f32 / 255.0;
}
} else if key == "TySh" {
let block_data = r.read(block_len)?;
is_text = true;
if block_data.len() >= 50 {
let version = u16::from_be_bytes([block_data[0], block_data[1]]);
if version == 1 {
let xx = f64::from_be_bytes(block_data[2..10].try_into().unwrap());
let xy = f64::from_be_bytes(block_data[10..18].try_into().unwrap());
let tx = f64::from_be_bytes(block_data[34..42].try_into().unwrap());
let ty = f64::from_be_bytes(block_data[42..50].try_into().unwrap());
let angle = xy.atan2(xx).to_degrees();
let mut text = String::new();
for idx in 0..block_data.len().saturating_sub(12) {
if &block_data[idx..idx+4] == b"Txt " && &block_data[idx+4..idx+8] == b"TEXT" {
let char_count = u32::from_be_bytes([block_data[idx+8], block_data[idx+9], block_data[idx+10], block_data[idx+11]]) as usize;
if block_data.len() >= idx + 12 + char_count * 2 {
let mut s = String::new();
for j in 0..char_count {
let c = u16::from_be_bytes([
block_data[idx + 12 + j * 2],
block_data[idx + 12 + j * 2 + 1],
]);
if let Some(ch) = std::char::from_u32(c as u32) {
s.push(ch);
}
}
text = s.trim().to_string();
break;
}
}
}
if text.is_empty() {
text = name.clone();
}
text_data = Some(hcie_protocol::LayerData::Text {
text,
font: "Arial".to_string(),
size: 24.0,
color: [0, 0, 0, 255],
x: tx as f32,
y: ty as f32,
angle: angle as f32,
alignment: hcie_protocol::tools::TextAlignment::Left,
orientation: hcie_protocol::tools::TextOrientation::Horizontal,
effects: vec![],
offset_x: 0.0,
offset_y: 0.0,
unrotated_w: 0.0,
unrotated_h: 0.0,
});
}
}
} else if key == "lfx2" {
let block_data = r.read(block_len)?;
match hcie_fx::parser::parse_lfx2(&block_data) {
Ok(fx) => {
if !fx.is_empty() {
effects = fx;
}
}
Err(e) => { warn!("parse_lfx2 failed for layer '{}': {}", name, e); }
}
} else if key == "lrFX" {
let block_data = r.read(block_len)?;
if effects.is_empty() {
match hcie_fx::parser::parse_lrFX(&block_data) {
Ok(fx) => { effects = fx; }
Err(e) => { warn!("parse_lrFX failed for layer '{}': {}", name, e); }
}
}
}
r.seek(block_start + block_len)?;
if block_len % 2 != 0 {
r.read(1)?;
}
}
layers_meta.push(LayerRecord {
name,
channels,
mask_info,
adjustment,
adjustment_raw,
is_group,
is_section_divider,
section_divider_blend,
curve_points,
effects,
fill_opacity,
is_text,
text_data,
});
r.seek(extra_start + extra_data_len)?;
}
let mut parsed_layers = Vec::with_capacity(layer_count);
for layer in layers_meta {
let mut decompressed_mask = None;
let mut mask_bounds = None;
let mut mask_default_color = 0;
for channel in &layer.channels {
if channel.id == -2 {
if let Some(mask) = &layer.mask_info {
let w = (mask.right - mask.left) as u32;
let h = (mask.bottom - mask.top) as u32;
if w > 0 && h > 0 && channel.len > 2 {
let compression = r.read_u16()?;
let raw_channel_data = r.read(channel.len as usize - 2)?;
if compression == 1 {
if let Ok(decompressed) = rle_decompress_rows(raw_channel_data, w, h) {
decompressed_mask = Some(decompressed);
mask_bounds = Some([mask.top, mask.left, mask.bottom, mask.right]);
mask_default_color = mask.default_color;
}
} else if compression == 0 {
decompressed_mask = Some(raw_channel_data.to_vec());
mask_bounds = Some([mask.top, mask.left, mask.bottom, mask.right]);
mask_default_color = mask.default_color;
}
} else {
r.read(channel.len as usize)?;
}
} else {
r.read(channel.len as usize)?;
}
} else {
r.read(channel.len as usize)?;
}
}
let is_group = layer.is_group;
let is_section_divider = layer.is_section_divider;
parsed_layers.push(ParsedLayerData {
name: layer.name,
is_group,
is_section_divider,
section_divider_blend: layer.section_divider_blend,
adjustment: layer.adjustment,
adjustment_raw: layer.adjustment_raw,
mask_pixels: decompressed_mask,
mask_bounds,
mask_default_color,
curve_points: layer.curve_points,
effects: layer.effects,
fill_opacity: layer.fill_opacity,
is_text: layer.is_text,
text_data: layer.text_data,
});
}
Ok(ParsedPsdData {
layers: parsed_layers,
image_resources_raw,
})
}
fn psd_blend_mode_to_hcie(mode: &str) -> hcie_protocol::BlendMode {
let bm = match mode {
s if s.contains("VividLight") => hcie_blend::BlendMode::VividLight,
s if s.contains("LinearLight") => hcie_blend::BlendMode::LinearLight,
s if s.contains("PinLight") => hcie_blend::BlendMode::PinLight,
s if s.contains("HardMix") => hcie_blend::BlendMode::HardMix,
s if s.contains("SoftLight") => hcie_blend::BlendMode::SoftLight,
s if s.contains("HardLight") => hcie_blend::BlendMode::HardLight,
s if s.contains("LighterColor") => hcie_blend::BlendMode::LighterColor,
s if s.contains("DarkerColor") => hcie_blend::BlendMode::DarkerColor,
s if s.contains("LinearBurn") => hcie_blend::BlendMode::LinearBurn,
s if s.contains("LinearDodge") => hcie_blend::BlendMode::LinearDodge,
s if s.contains("ColorBurn") => hcie_blend::BlendMode::ColorBurn,
s if s.contains("ColorDodge") => hcie_blend::BlendMode::ColorDodge,
s if s.contains("Dissolve") => hcie_blend::BlendMode::Dissolve,
s if s.contains("Multiply") => hcie_blend::BlendMode::Multiply,
s if s.contains("Screen") => hcie_blend::BlendMode::Screen,
s if s.contains("Overlay") => hcie_blend::BlendMode::Overlay,
s if s.contains("Subtract") => hcie_blend::BlendMode::Subtract,
s if s.contains("Difference") => hcie_blend::BlendMode::Difference,
s if s.contains("Exclusion") => hcie_blend::BlendMode::Exclusion,
s if s.contains("Divide") => hcie_blend::BlendMode::Divide,
s if s.contains("Saturation") => hcie_blend::BlendMode::Saturation,
s if s.contains("Luminosity") => hcie_blend::BlendMode::Luminosity,
s if s.contains("PassThrough") => hcie_blend::BlendMode::PassThrough,
s if s.contains("Darken") => hcie_blend::BlendMode::Darken,
s if s.contains("Lighten") => hcie_blend::BlendMode::Lighten,
s if s.contains("Normal") => hcie_blend::BlendMode::Normal,
s if s.contains("Hue") => hcie_blend::BlendMode::Hue,
s if s.contains("Color") => hcie_blend::BlendMode::Color,
_ => hcie_blend::BlendMode::Normal,
};
bm.into()
}
fn get_group_depth(group_id: u32, groups: &HashMap<u32, crate::PsdGroup>) -> u32 {
let mut depth = 1;
let mut current_id = group_id;
while let Some(g) = groups.get(&current_id) {
if let Some(parent) = g.parent_id() {
depth += 1;
current_id = parent;
} else {
break;
}
}
depth
}
fn is_ancestor_of_layer(
ancestor_group_id: u32,
mut current_parent_id: Option<u32>,
groups: &HashMap<u32, crate::PsdGroup>,
) -> bool {
while let Some(pid) = current_parent_id {
if pid == ancestor_group_id {
return true;
}
current_parent_id = groups.get(&pid).and_then(|g| g.parent_id());
}
false
}
pub fn import_psd(path: &Path) -> Result<Vec<hcie_protocol::Layer>, String> {
let bytes = std::fs::read(path).map_err(|e| e.to_string())?;
let psd = Psd::from_bytes(&bytes).map_err(|e| e.to_string())?;
if psd.layers().is_empty() {
return Err("No layers found in PSD".to_string());
}
let canvas_w = psd.width() as u32;
let canvas_h = psd.height() as u32;
let parsed_result = parse_psd_sequential_full(&bytes);
let (parsed_layers, image_resources_raw) = match parsed_result {
Ok(parsed) => (parsed.layers, parsed.image_resources_raw),
Err(e) => {
error!("parse_psd_sequential failed: {}. Effects will not be imported.", e);
(Vec::new(), Vec::new())
}
};
let non_group_parsed: Vec<&ParsedLayerData> = parsed_layers.iter()
.filter(|p| !p.is_group && !p.is_section_divider)
.collect();
// Build a mapping from PSD layer index → non_group_parsed index
// Both parsed_layers and psd.layers() are in the same order
let mut parsed_to_non_group: Vec<Option<usize>> = Vec::with_capacity(parsed_layers.len());
let mut ng_idx = 0;
for p in &parsed_layers {
if !p.is_group {
parsed_to_non_group.push(Some(ng_idx));
ng_idx += 1;
} else {
parsed_to_non_group.push(None);
}
}
let mut group_id_map = HashMap::new();
for (&psd_group_id, _) in psd.groups().iter() {
group_id_map.insert(psd_group_id, rand::random::<u64>());
}
let mut flat_layers = Vec::new();
for (i, psd_layer) in psd.layers().iter().enumerate() {
let name = psd_layer.name().to_string();
let layer_rgba = psd_layer.rgba();
let layer_opacity = psd_layer.opacity() as f32 / 255.0;
let visible = !psd_layer.visible();
let blend_mode = psd_blend_mode_to_hcie(&format!("{:?}", psd_layer.blend_mode()));
let clipping_mask = !psd_layer.is_clipping_mask();
let mut layer = hcie_protocol::Layer::from_rgba(name, canvas_w, canvas_h, layer_rgba);
layer.id = rand::random::<u64>();
layer.visible = visible;
layer.opacity = layer_opacity;
layer.blend_mode = blend_mode;
layer.clipping_mask = clipping_mask;
if let Some(parent_u32) = psd_layer.parent_id() {
layer.parent_id = group_id_map.get(&parent_u32).copied();
}
let parsed_idx = non_group_parsed.len().saturating_sub(1).saturating_sub(i);
if parsed_idx < non_group_parsed.len() {
let parsed = non_group_parsed[parsed_idx];
println!("MAP: i={} psd_layer.name='{}' -> parsed_idx={} parsed.name='{}'", i, psd_layer.name(), parsed_idx, parsed.name);
if parsed.name != psd_layer.name() {
log::warn!("PSD import layer name mismatch at index {}: psd.layers name='{}' vs parsed name='{}'", i, psd_layer.name(), parsed.name);
}
layer.adjustment = parsed.adjustment.clone();
layer.adjustment_raw = parsed.adjustment_raw.clone();
layer.mask_pixels = parsed.mask_pixels.clone();
layer.mask_bounds = parsed.mask_bounds;
layer.mask_default_color = parsed.mask_default_color;
layer.curve_points = parsed.curve_points.clone();
let protocol_effects: Vec<hcie_protocol::effects::LayerEffect> =
parsed.effects.iter().map(hcie_fx::hcie_fx_effect_to_protocol).collect();
layer.effects = protocol_effects;
layer.fill_opacity = parsed.fill_opacity;
if parsed.is_text {
layer.layer_type = hcie_protocol::LayerType::Text;
layer.data = parsed.text_data.clone().unwrap_or(hcie_protocol::LayerData::Raster);
}
}
layer.image_resources_raw = Some(image_resources_raw.clone());
flat_layers.push(layer);
}
// Insert group open and section-divider markers into flat_layers.
//
// flat_layers is currently in Photoshop bottom-to-top order (index 0 is bottom).
// psd_group.contained_layers is a Range in the same bottom-to-top layer order.
// We place the group open marker just before the first child and the section
// divider just after the last child.
let mut group_markers: Vec<(usize, hcie_protocol::Layer)> = Vec::new();
for (&psd_group_id, psd_group) in psd.groups().iter() {
let name = psd_group.name().to_string();
let group_opacity = psd_group.opacity() as f32 / 255.0;
let visible = !psd_group.visible();
let blend_mode = psd_blend_mode_to_hcie(&format!("{:?}", psd_group.blend_mode()));
let group_id = *group_id_map.get(&psd_group_id).unwrap();
let parent_id = psd_group.parent_id().and_then(|p| group_id_map.get(&p).copied());
// Group open marker
let mut open_layer = hcie_protocol::Layer::new_transparent(name.clone(), canvas_w, canvas_h);
open_layer.layer_type = hcie_protocol::LayerType::Group;
open_layer.id = group_id;
open_layer.visible = visible;
open_layer.opacity = group_opacity;
open_layer.blend_mode = blend_mode;
open_layer.parent_id = parent_id;
// Section-divider (group end marker)
let divider_name = "</Layer group>".to_string();
let mut divider = hcie_protocol::Layer::new_transparent(divider_name, canvas_w, canvas_h);
divider.layer_type = hcie_protocol::LayerType::Group;
divider.id = rand::random::<u64>();
divider.visible = visible;
divider.opacity = group_opacity;
// Photoshop group pass-through is encoded in the original section-divider lsct block.
// If any PSD layer record with the same name reported pass-through, use it.
let has_pass_through = parsed_layers.iter().any(|p| {
p.name == name && p.section_divider_blend.as_deref() == Some("pass")
});
if has_pass_through {
divider.blend_mode = hcie_protocol::BlendMode::PassThrough;
} else {
divider.blend_mode = blend_mode;
}
divider.is_section_divider = true;
divider.parent_id = parent_id;
let range = &psd_group.contained_layers;
let open_idx = range.start;
let divider_idx = range.end;
group_markers.push((open_idx, open_layer));
group_markers.push((divider_idx, divider));
}
// Insert from high indices to low so earlier insertions do not shift later ones.
group_markers.sort_by(|a, b| b.0.cmp(&a.0));
for (insert_idx, marker) in group_markers {
flat_layers.insert(insert_idx, marker);
}
flat_layers.reverse();
let is_bg_at_end = flat_layers.last().map(|l| l.name.trim().to_lowercase() == "background").unwrap_or(false);
let is_bg_at_start = flat_layers.first().map(|l| l.name.trim().to_lowercase() == "background").unwrap_or(false);
if is_bg_at_end && !is_bg_at_start {
flat_layers.reverse();
}
Ok(flat_layers)
}