diff --git a/Cargo.toml b/Cargo.toml index eea4f5d..0bf36dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ musica = [] musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"] qlie = [] qlie-arc = ["qlie", "utils-mmx", "rand"] -qlie-img = ["qlie", "image"] +qlie-img = ["qlie", "image", "utils-psd"] silky = [] softpal = ["int-enum"] softpal-arc = ["softpal"] @@ -121,6 +121,7 @@ utils-crc32 = [] utils-escape = ["fancy-regex"] utils-mmx = [] utils-pcm = [] +utils-psd = ["image"] utils-rc4 = [] utils-serde-base64bytes = ["base64"] utils-str = [] diff --git a/README.md b/README.md index 44b0794..4546e83 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,7 @@ msg-tool create -t |---|---|---|---|---|---|---|---|---|---|---| | `qlie` | `qlie` | Qlie Engine Scenario script (.s) | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | | | `qlie-abmp10` / `qlie-abmp11` / `qlie-abmp12` | `qlie-img` | Qlie Abmp10/11/12 image (.b) | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | | +| `qlie-dpng` | `qlie-img` | Qlie tiled PNG image (.png) | ❌ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | `--qlie-dpng-psd` is required. | | Archive Type | Feature Name | Name | Unpack | Pack | Remarks | |---|---|---|---|---|---| diff --git a/src/args.rs b/src/args.rs index 67e9ee5..80cc464 100644 --- a/src/args.rs +++ b/src/args.rs @@ -652,6 +652,10 @@ pub struct Arg { /// Whether to use PNG file directly for Qlie DPNG images when importing. /// Enable this will disable reencoding PNG files. Useful when the PNG files are already optimized by other tools. pub qlie_dpng_use_raw_png: bool, + #[cfg(feature = "qlie-img")] + #[arg(long, global = true, action = ArgAction::SetTrue)] + /// Export Qlie DPNG images as PSD files. + pub qlie_dpng_psd: bool, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/ext/io.rs b/src/ext/io.rs index 9350026..dc09452 100644 --- a/src/ext/io.rs +++ b/src/ext/io.rs @@ -2002,6 +2002,11 @@ impl StreamRegion { let end_pos = stream.stream_length()?; Self::new(stream, start_pos, end_pos) } + + /// Returns the current position within the region. + pub fn cur_pos(&self) -> u64 { + self.cur_pos + } } impl Read for StreamRegion { diff --git a/src/main.rs b/src/main.rs index 7ae690a..fe17977 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3363,6 +3363,8 @@ fn main() { qlie_pack_compress_files: arg.qlie_pack_compress_files, #[cfg(feature = "qlie-img")] qlie_dpng_use_raw_png: arg.qlie_dpng_use_raw_png, + #[cfg(feature = "qlie-img")] + qlie_dpng_psd: arg.qlie_dpng_psd, }); match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/qlie/image/dpng.rs b/src/scripts/qlie/image/dpng.rs index 6a4dc9a..618456d 100644 --- a/src/scripts/qlie/image/dpng.rs +++ b/src/scripts/qlie/image/dpng.rs @@ -3,6 +3,7 @@ use crate::ext::io::*; use crate::scripts::base::*; use crate::types::*; use crate::utils::img::*; +use crate::utils::psd::*; use crate::utils::struct_pack::*; use anyhow::Result; use msg_tool_macro::*; @@ -130,15 +131,27 @@ impl DpngImage { impl Script for DpngImage { fn default_output_script_type(&self) -> OutputScriptType { - OutputScriptType::Json + OutputScriptType::Custom + } + + fn is_output_supported(&self, output: OutputScriptType) -> bool { + matches!(output, OutputScriptType::Custom) } fn default_format_type(&self) -> FormatOptions { FormatOptions::None } + fn custom_output_extension<'a>(&'a self) -> &'a str { + "psd" + } + fn is_image(&self) -> bool { - true + if self.config.qlie_dpng_psd { + false + } else { + true + } } fn export_image(&self) -> Result { @@ -207,6 +220,47 @@ impl Script for DpngImage { } Ok(()) } + + fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> { + let mut psd = PsdWriter::new( + self.img.header.image_width, + self.img.header.image_height, + ImageColorType::Rgba, + 8, + )?; + let (idx, tile) = self + .img + .tiles + .iter() + .enumerate() + .find(|(_, t)| t.size != 0) + .ok_or_else(|| anyhow::anyhow!("DPNG image has no valid tiles with PNG data"))?; + let mut base = load_png(MemReaderRef::new(&tile.png_data))?; + psd.add_layer(&format!("layer_{}", idx), tile.x, tile.y, base.clone())?; + convert_to_rgba(&mut base)?; + let mut base = draw_on_canvas( + base, + self.img.header.image_width, + self.img.header.image_height, + tile.x, + tile.y, + )?; + let mut idx2 = idx; + for tile in &self.img.tiles[idx + 1..] { + idx2 += 1; + if tile.size == 0 { + continue; + } + let mut diff = load_png(MemReaderRef::new(&tile.png_data))?; + psd.add_layer(&format!("layer_{}", idx2), tile.x, tile.y, diff.clone())?; + convert_to_rgba(&mut diff)?; + draw_on_image(&mut base, &diff, tile.x, tile.y)?; + } + let file = std::fs::File::create(filename)?; + let mut writer = std::io::BufWriter::new(file); + psd.save(base, &mut writer, encoding)?; + Ok(()) + } } fn create_raw_png_image<'a>( diff --git a/src/types.rs b/src/types.rs index 6bbc14b..1533900 100644 --- a/src/types.rs +++ b/src/types.rs @@ -606,6 +606,9 @@ pub struct ExtraConfig { /// Whether to use PNG file directly for Qlie DPNG images when importing. /// Enable this will disable reencoding PNG files. Useful when the PNG files are already optimized by other tools. pub qlie_dpng_use_raw_png: bool, + #[cfg(feature = "qlie-img")] + /// Export Qlie DPNG images as PSD files. + pub qlie_dpng_psd: bool, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3183b27..5a33f95 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -27,6 +27,8 @@ pub mod name_replacement; pub mod num_range; #[cfg(feature = "utils-pcm")] pub mod pcm; +#[cfg(feature = "utils-psd")] +pub mod psd; #[cfg(feature = "utils-rc4")] pub mod rc4; #[cfg(feature = "utils-serde-base64bytes")] diff --git a/src/utils/psd/mod.rs b/src/utils/psd/mod.rs new file mode 100644 index 0000000..aa5c22e --- /dev/null +++ b/src/utils/psd/mod.rs @@ -0,0 +1,227 @@ +//! A simple PSD writer +mod types; + +use crate::types::*; +use crate::utils::img::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use std::io::Write; +use types::*; + +/// A simple PSD writer. +pub struct PsdWriter { + psd: PsdFile, + color_type: ImageColorType, +} + +impl PsdWriter { + /// Creates a new PSD writer with the specified dimensions, color type, and bit depth. + pub fn new(width: u32, height: u32, color_type: ImageColorType, depth: u8) -> Result { + let color_type = match color_type { + ImageColorType::Bgr => ImageColorType::Rgb, + ImageColorType::Bgra => ImageColorType::Rgba, + _ => color_type, + }; + let depth = match depth { + 1 | 8 | 16 | 32 => depth, + _ => anyhow::bail!("Unsupported bit depth: {}", depth), + }; + let psd = PsdFile { + header: PsdHeader { + signature: *PSD_SIGNATURE, + version: 1, + reserved: [0; 6], + channels: color_type.bpp(1), + height, + width, + depth: depth as u16, + color_mode: if color_type == ImageColorType::Grayscale { + 1 + } else { + 3 + }, + }, + color_mode_data: ColorModeData { data: vec![] }, + image_resource: ImageResourceSection { resources: vec![] }, + layer_and_mask_info: LayerAndMaskInfo { + layer_info: LayerInfo { + layer_count: 0, + layer_records: vec![], + channel_image_data: vec![], + }, + global_layer_mask_info: GlobalLayerMaskInfo { + overlays_color_space: 0, + overlays_color_components: [0; 4], + opacity: 0, + kind: 128, + filler: vec![0], + }, + tagged_blocks: vec![], + }, + image_data: ImageDataSection { + compression: 0, + image_data: vec![], + }, + }; + Ok(Self { psd, color_type }) + } + + /// Add a visible layer to the PSD file. + pub fn add_layer(&mut self, name: &str, x: u32, y: u32, mut data: ImageData) -> Result<()> { + if data.color_type == ImageColorType::Bgr { + convert_bgr_to_rgb(&mut data)?; + } + if data.color_type == ImageColorType::Bgra { + convert_bgra_to_rgba(&mut data)?; + } + let length = data.width as u32 * data.height as u32; + let mut channel_infos = Vec::new(); + if data.color_type == ImageColorType::Grayscale { + channel_infos.push(ChannelInfo { + channel_id: 0, + length: length + 2, + }); + } else { + channel_infos.push(ChannelInfo { + channel_id: 0, + length: length + 2, + }); + channel_infos.push(ChannelInfo { + channel_id: 1, + length: length + 2, + }); + channel_infos.push(ChannelInfo { + channel_id: 2, + length: length + 2, + }); + if data.color_type == ImageColorType::Rgba { + channel_infos.push(ChannelInfo { + channel_id: -1, + length: length + 2, + }); + } + } + let layer_base = LayerRecordBase { + top: y as i32, + left: x as i32, + bottom: (y + data.height) as i32, + right: (x + data.width) as i32, + channels: data.color_type.bpp(1) as u16, + channel_infos: channel_infos, + blend_mode_signature: *IMAGE_RESOURCE_SIGNATURE, + blend_mode_key: *b"norm", + opacity: 255, + clipping: 0, + flags: 1, + filler: 0, + }; + let mut channel_ranges = Vec::new(); + for _ in 0..layer_base.channels { + channel_ranges.push(ChannelRange { + source_range: 0xFFFF, + dest_range: 0xFFFF, + }); + } + let layer_blending_ranges = LayerBlendingRanges { + gray_blend_dest: 0xFFFF, + gray_blend_source: 0xFFFF, + channel_ranges, + }; + let mut image_data = Vec::new(); + for i in 0..layer_base.channels { + let mut d = Vec::with_capacity(length as usize); + for y in 0..data.height { + for x in 0..data.width { + let index = + (y * data.width + x) as usize * layer_base.channels as usize + i as usize; + d.push(data.data[index]); + } + } + image_data.push(ChannelImageData { + compression: 0, + image_data: d, + }); + } + let layer = LayerRecord { + base: layer_base, + layer_mask: None, + layer_blending_ranges, + layer_name: PascalString4(name.to_string()), + infos: vec![], + }; + self.psd + .layer_and_mask_info + .layer_info + .layer_records + .push(layer); + + // Update layer count + self.psd.layer_and_mask_info.layer_info.layer_count = + self.psd.layer_and_mask_info.layer_info.layer_records.len() as u16; + + self.psd + .layer_and_mask_info + .layer_info + .channel_image_data + .extend(image_data); + Ok(()) + } + + /// Saves the PSD file to the specified writer with the given encoding. + /// + /// * `data` - The final composite image data to be saved in the PSD file. + pub fn save( + &mut self, + data: ImageData, + mut writer: T, + encoding: Encoding, + ) -> Result<()> { + if data.color_type == ImageColorType::Bgr { + convert_bgr_to_rgb(&mut data.clone())?; + } + if data.color_type == ImageColorType::Bgra { + convert_bgra_to_rgba(&mut data.clone())?; + } + if self.color_type != data.color_type { + anyhow::bail!( + "Image color type does not match PSD color type: {:?} != {:?}", + self.color_type, + data.color_type + ); + } + if data.width != self.psd.header.width || data.height != self.psd.header.height { + anyhow::bail!( + "Image dimensions do not match PSD dimensions: {}x{} != {}x{}", + data.width, + data.height, + self.psd.header.width, + self.psd.header.height + ); + } + + // Convert interleaved data to planar data (RRR...GGG...BBB...) + let channels = self.psd.header.channels as usize; + let width = self.psd.header.width as usize; + let height = self.psd.header.height as usize; + let total_pixels = width * height; + let expected_len = total_pixels * channels; // assuming 8-bit depth + + if data.data.len() != expected_len { + anyhow::bail!("Data length mismatch for planar conversion"); + } + + let mut planar_data = Vec::with_capacity(expected_len); + for c in 0..channels { + for i in 0..total_pixels { + // Interleaved index: pixel_index * channels + channel_index + let val = data.data[i * channels + c]; + planar_data.push(val); + } + } + + self.psd.image_data.image_data = planar_data; + self.psd.image_data.compression = 0; // Raw + self.psd.pack(&mut writer, true, encoding, &None)?; + Ok(()) + } +} diff --git a/src/utils/psd/types.rs b/src/utils/psd/types.rs new file mode 100644 index 0000000..5946e13 --- /dev/null +++ b/src/utils/psd/types.rs @@ -0,0 +1,921 @@ +use crate::ext::io::*; +use crate::types::*; +use crate::utils::encoding::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use msg_tool_macro::*; +use std::any::Any; +use std::io::{Read, Seek, Write}; + +pub const PSD_SIGNATURE: &[u8; 4] = b"8BPS"; +pub const IMAGE_RESOURCE_SIGNATURE: &[u8; 4] = b"8BIM"; + +// #[derive(Debug, Clone)] +// pub struct UnicodeString(pub String); + +// impl StructPack for UnicodeString { +// fn pack( +// &self, +// writer: &mut W, +// big: bool, +// encoding: Encoding, +// info: &Option>, +// ) -> Result<()> { +// let mut encoded: Vec<_> = self.0.encode_utf16().collect(); +// encoded.push(0); // null-terminator +// let len = encoded.len() as u32; +// len.pack(writer, big, encoding, info)?; +// for c in encoded { +// c.pack(writer, big, encoding, info)?; +// } +// Ok(()) +// } +// } + +// impl StructUnpack for UnicodeString { +// fn unpack( +// reader: &mut R, +// big: bool, +// encoding: Encoding, +// info: &Option>, +// ) -> Result { +// let len = u32::unpack(reader, big, encoding, info)?; +// if len == 0 { +// return Ok(UnicodeString(String::new())); +// } +// let mut encoded: Vec = Vec::with_capacity((len as usize) - 1); +// for _ in 0..len - 1 { +// let c = u16::unpack(reader, big, encoding, info)?; +// encoded.push(c); +// } +// // null-terminator +// let null = u16::unpack(reader, big, encoding, info)?; +// if null != 0 { +// return Err(anyhow::anyhow!("Expected null-terminator in UnicodeString")); +// } +// let string = String::from_utf16(&encoded) +// .map_err(|e| anyhow::anyhow!("Failed to decode UTF-16 string: {}", e))?; +// Ok(UnicodeString(string)) +// } +// } + +#[derive(Debug, Clone)] +pub struct PascalString(pub String); + +impl StructPack for PascalString { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut encoded = encode_string(encoding, &self.0, true)?; + let len = encoded.len() as u8; + len.pack(writer, big, encoding, info)?; + if len % 2 == 0 { + encoded.push(0); // padding byte + } + writer.write_all(&encoded)?; + Ok(()) + } +} + +impl StructUnpack for PascalString { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let len = u8::unpack(reader, big, encoding, info)?; + let encoded = reader.read_exact_vec(len as usize)?; + if len % 2 == 0 { + reader.read_u8()?; // padding byte + } + let string = decode_to_string(encoding, &encoded, true)?; + Ok(PascalString(string)) + } +} + +#[derive(Debug, Clone)] +pub struct PascalString4(pub String); + +impl StructPack for PascalString4 { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut encoded = encode_string(encoding, &self.0, true)?; + let len = encoded.len() as u8; + len.pack(writer, big, encoding, info)?; + while (len as usize + 1) % 4 != 0 { + encoded.push(0); // padding bytes + } + writer.write_all(&encoded)?; + Ok(()) + } +} + +impl StructUnpack for PascalString4 { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let len = u8::unpack(reader, big, encoding, info)?; + let encoded = reader.read_exact_vec(len as usize)?; + while (len as usize + 1) % 4 != 0 { + reader.read_u8()?; // padding bytes + } + let string = decode_to_string(encoding, &encoded, true)?; + Ok(PascalString4(string)) + } +} + +#[derive(Debug, Clone, StructPack, StructUnpack)] +pub struct PsdHeader { + pub signature: [u8; 4], + pub version: u16, + pub reserved: [u8; 6], + pub channels: u16, + pub height: u32, + pub width: u32, + pub depth: u16, + pub color_mode: u16, +} + +#[derive(Debug, Clone, StructPack, StructUnpack)] +pub struct ColorModeData { + #[pvec(u32)] + pub data: Vec, +} + +#[derive(Debug, Clone)] +pub struct ImageResourceSection { + pub resources: Vec, +} + +impl StructUnpack for ImageResourceSection { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let length = u32::unpack(reader, big, encoding, info)?; + let mut stream_region = StreamRegion::with_size(reader, length as u64)?; + let mut resources = Vec::new(); + while stream_region.cur_pos() < length as u64 { + let resource = ImageResourceBlock::unpack(&mut stream_region, big, encoding, info)?; + resources.push(resource); + } + Ok(ImageResourceSection { resources }) + } +} + +impl StructPack for ImageResourceSection { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut mem = MemWriter::new(); + for resource in &self.resources { + resource.pack(&mut mem, big, encoding, info)?; + } + let data = mem.into_inner(); + let length = data.len() as u32; + length.pack(writer, big, encoding, info)?; + writer.write_all(&data)?; + Ok(()) + } +} + +#[derive(Debug, Clone, StructPack, StructUnpack)] +pub struct ImageResourceBlock { + pub signature: [u8; 4], + pub resource_id: u16, + pub name: PascalString, + #[pvec(u32)] + pub data: Vec, +} + +#[derive(Debug, Clone)] +pub struct LayerAndMaskInfo { + pub layer_info: LayerInfo, + pub global_layer_mask_info: GlobalLayerMaskInfo, + pub tagged_blocks: Vec, +} + +impl StructUnpack for LayerAndMaskInfo { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let length = u32::unpack(reader, big, encoding, info)?; + let mut stream_region = StreamRegion::with_size(reader, length as u64)?; + let layer_info = LayerInfo::unpack(&mut stream_region, big, encoding, info)?; + let global_layer_mask_info = + GlobalLayerMaskInfo::unpack(&mut stream_region, big, encoding, info)?; + let mut tagged_blocks = Vec::new(); + stream_region.read_to_end(&mut tagged_blocks)?; + Ok(LayerAndMaskInfo { + layer_info, + global_layer_mask_info, + tagged_blocks, + }) + } +} + +impl StructPack for LayerAndMaskInfo { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut mem = MemWriter::new(); + self.layer_info.pack(&mut mem, big, encoding, info)?; + self.global_layer_mask_info + .pack(&mut mem, big, encoding, info)?; + mem.write_all(&self.tagged_blocks)?; + let data = mem.into_inner(); + let length = data.len() as u32; + length.pack(writer, big, encoding, info)?; + writer.write_all(&data)?; + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct LayerInfo { + pub layer_count: u16, + pub layer_records: Vec, + pub channel_image_data: Vec, +} + +impl StructUnpack for LayerInfo { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let length = u32::unpack(reader, big, encoding, info)?; + let mut stream_region = StreamRegion::with_size(reader, length as u64)?; + let layer_count = u16::unpack(&mut stream_region, big, encoding, info)?; + let mut layer_records = Vec::new(); + for _ in 0..layer_count { + let layer_record = LayerRecord::unpack(&mut stream_region, big, encoding, info)?; + layer_records.push(layer_record); + } + let mut channel_image_data = Vec::new(); + for i in 0..layer_count { + let layer = &layer_records[i as usize]; + let info = Some(Box::new(layer.clone()) as Box); + for _ in 0..layer.base.channels { + let data = ChannelImageData::unpack(&mut stream_region, big, encoding, &info)?; + channel_image_data.push(data); + } + } + Ok(LayerInfo { + layer_count, + layer_records, + channel_image_data, + }) + } +} + +impl StructPack for LayerInfo { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut mem = MemWriter::new(); + self.layer_count.pack(&mut mem, big, encoding, info)?; + for layer_record in &self.layer_records { + layer_record.pack(&mut mem, big, encoding, info)?; + } + for i in 0..self.layer_count { + let layer = &self.layer_records[i as usize]; + let info = Some(Box::new(layer.clone()) as Box); + for j in 0..layer.base.channels { + let index = (i as usize) * (layer.base.channels as usize) + (j as usize); + let data = &self.channel_image_data[index]; + data.pack(&mut mem, big, encoding, &info)?; + } + } + let data = mem.into_inner(); + + // Pad to 2 bytes + let mut length = data.len() as u32; + let need_pad = length % 2 != 0; + if need_pad { + length += 1; + } + length.pack(writer, big, encoding, info)?; + writer.write_all(&data)?; + if need_pad { + writer.write_u8(0)?; // padding byte + } + Ok(()) + } +} + +#[derive(Debug, Clone, StructPack, StructUnpack)] +pub struct LayerRecordBase { + pub top: i32, + pub left: i32, + pub bottom: i32, + pub right: i32, + pub channels: u16, + #[unpack_vec_len(channels)] + #[pack_vec_len(self.channels)] + pub channel_infos: Vec, + pub blend_mode_signature: [u8; 4], + pub blend_mode_key: [u8; 4], + pub opacity: u8, + pub clipping: u8, + pub flags: u8, + pub filler: u8, +} + +#[derive(Debug, Clone)] +pub struct LayerRecord { + pub base: LayerRecordBase, + pub layer_mask: Option, + pub layer_blending_ranges: LayerBlendingRanges, + pub layer_name: PascalString4, + pub infos: Vec, +} + +impl StructPack for LayerRecord { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + self.base.pack(writer, big, encoding, info)?; + let mut mem = MemWriter::new(); + if let Some(layer_mask) = &self.layer_mask { + layer_mask.pack(&mut mem, big, encoding, info)?; + } else { + 0u32.pack(&mut mem, big, encoding, info)?; // no layer mask + } + self.layer_blending_ranges + .pack(&mut mem, big, encoding, info)?; + self.layer_name.pack(&mut mem, big, encoding, info)?; + for additional_info in &self.infos { + additional_info.pack(&mut mem, big, encoding, info)?; + } + let data = mem.into_inner(); + let extra_data_length = data.len() as u32; + extra_data_length.pack(writer, big, encoding, info)?; + writer.write_all(&data)?; + Ok(()) + } +} + +impl StructUnpack for LayerRecord { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let base = LayerRecordBase::unpack(reader, big, encoding, info)?; + let extra_data_length = u32::unpack(reader, big, encoding, info)?; + let mut stream_region = StreamRegion::with_size(reader, extra_data_length as u64)?; + let layer_mask_len = u32::unpack(&mut stream_region, big, encoding, info)?; + let layer_mask = if layer_mask_len > 0 { + stream_region.seek_relative(-4)?; + Some(LayerMask::unpack(&mut stream_region, big, encoding, info)?) + } else { + None + }; + let layer_blending_ranges = + LayerBlendingRanges::unpack(&mut stream_region, big, encoding, info)?; + let layer_name = PascalString4::unpack(&mut stream_region, big, encoding, info)?; + let mut infos = Vec::new(); + while stream_region.cur_pos() < extra_data_length as u64 { + let additional_info = + AdditionalLayerInfo::unpack(&mut stream_region, big, encoding, info)?; + infos.push(additional_info); + } + Ok(LayerRecord { + base, + layer_mask, + layer_blending_ranges, + layer_name, + infos, + }) + } +} + +#[derive(Debug, Clone)] +pub struct LayerMask { + pub top: i32, + pub left: i32, + pub bottom: i32, + pub right: i32, + pub default_color: u8, + pub flags: u8, + pub mask_parameters: Option, + pub mask_data: Option>, + pub real_flags: Option, + pub real_user_mask_background: Option, + pub mask_top: Option, + pub mask_left: Option, + pub mask_bottom: Option, + pub mask_right: Option, +} + +impl StructPack for LayerMask { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut mem = MemWriter::new(); + self.top.pack(&mut mem, big, encoding, info)?; + self.left.pack(&mut mem, big, encoding, info)?; + self.bottom.pack(&mut mem, big, encoding, info)?; + self.right.pack(&mut mem, big, encoding, info)?; + self.default_color.pack(&mut mem, big, encoding, info)?; + self.flags.pack(&mut mem, big, encoding, info)?; + if self.flags == 4 { + if let Some(mask_parameters) = self.mask_parameters { + mask_parameters.pack(&mut mem, big, encoding, info)?; + } else { + return Err(anyhow::anyhow!( + "mask_parameters is required when flags == 4" + )); + } + } + if let Some(mask_data) = &self.mask_data { + mem.write_all(mask_data)?; + } + if let Some(real_flags) = self.real_flags { + real_flags.pack(&mut mem, big, encoding, info)?; + let real_user_mask_background = self + .real_user_mask_background + .ok_or_else(|| anyhow::anyhow!("real_user_mask_background is required"))?; + real_user_mask_background.pack(&mut mem, big, encoding, info)?; + let mask_top = self + .mask_top + .ok_or_else(|| anyhow::anyhow!("mask_top is required"))?; + mask_top.pack(&mut mem, big, encoding, info)?; + let mask_left = self + .mask_left + .ok_or_else(|| anyhow::anyhow!("mask_left is required"))?; + mask_left.pack(&mut mem, big, encoding, info)?; + let mask_bottom = self + .mask_bottom + .ok_or_else(|| anyhow::anyhow!("mask_bottom is required"))?; + mask_bottom.pack(&mut mem, big, encoding, info)?; + let mask_right = self + .mask_right + .ok_or_else(|| anyhow::anyhow!("mask_right is required"))?; + mask_right.pack(&mut mem, big, encoding, info)?; + } else { + if mem.data.len() == 18 { + mem.write_u16(0)?; // padding to 20 bytes + } + } + let data = mem.into_inner(); + let length = data.len() as u32; + length.pack(writer, big, encoding, info)?; + writer.write_all(&data)?; + Ok(()) + } +} + +impl StructUnpack for LayerMask { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let length = u32::unpack(reader, big, encoding, info)?; + let mut stream_region = StreamRegion::with_size(reader, length as u64)?; + let top = i32::unpack(&mut stream_region, big, encoding, info)?; + let left = i32::unpack(&mut stream_region, big, encoding, info)?; + let bottom = i32::unpack(&mut stream_region, big, encoding, info)?; + let right = i32::unpack(&mut stream_region, big, encoding, info)?; + let default_color = u8::unpack(&mut stream_region, big, encoding, info)?; + let flags = u8::unpack(&mut stream_region, big, encoding, info)?; + let mask_parameters = if flags == 4 { + Some(u8::unpack(&mut stream_region, big, encoding, info)?) + } else { + None + }; + let mask_data = if flags == 0 || flags == 2 { + Some(stream_region.read_exact_vec(1)?) + } else if flags == 1 || flags == 3 { + Some(stream_region.read_exact_vec(8)?) + } else { + None + }; + if length == 20 { + let _ = stream_region.read_u16()?; // padding + } + if stream_region.cur_pos() < length as u64 { + let real_flags = u8::unpack(&mut stream_region, big, encoding, info)?; + let real_user_mask_background = u8::unpack(&mut stream_region, big, encoding, info)?; + let mask_top = i32::unpack(&mut stream_region, big, encoding, info)?; + let mask_left = i32::unpack(&mut stream_region, big, encoding, info)?; + let mask_bottom = i32::unpack(&mut stream_region, big, encoding, info)?; + let mask_right = i32::unpack(&mut stream_region, big, encoding, info)?; + Ok(LayerMask { + top, + left, + bottom, + right, + default_color, + flags, + mask_parameters, + mask_data, + real_flags: Some(real_flags), + real_user_mask_background: Some(real_user_mask_background), + mask_top: Some(mask_top), + mask_left: Some(mask_left), + mask_bottom: Some(mask_bottom), + mask_right: Some(mask_right), + }) + } else { + Ok(LayerMask { + top, + left, + bottom, + right, + default_color, + flags, + mask_parameters, + mask_data, + real_flags: None, + real_user_mask_background: None, + mask_top: None, + mask_left: None, + mask_bottom: None, + mask_right: None, + }) + } + } +} + +#[derive(Debug, Clone, StructPack, StructUnpack)] +pub struct ChannelInfo { + pub channel_id: i16, + pub length: u32, +} + +#[derive(Debug, Clone)] +pub struct LayerBlendingRanges { + pub gray_blend_source: u32, + pub gray_blend_dest: u32, + pub channel_ranges: Vec, +} + +impl StructUnpack for LayerBlendingRanges { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let total_length = u32::unpack(reader, big, encoding, info)?; + let mut stream_region = StreamRegion::with_size(reader, total_length as u64)?; + let gray_blend_source = u32::unpack(&mut stream_region, big, encoding, info)?; + let gray_blend_dest = u32::unpack(&mut stream_region, big, encoding, info)?; + let mut channel_ranges = Vec::new(); + while stream_region.cur_pos() < total_length as u64 { + let channel_range = ChannelRange::unpack(&mut stream_region, big, encoding, info)?; + channel_ranges.push(channel_range); + } + Ok(LayerBlendingRanges { + gray_blend_source, + gray_blend_dest, + channel_ranges, + }) + } +} + +impl StructPack for LayerBlendingRanges { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut mem = MemWriter::new(); + self.gray_blend_source.pack(&mut mem, big, encoding, info)?; + self.gray_blend_dest.pack(&mut mem, big, encoding, info)?; + for channel_range in &self.channel_ranges { + channel_range.pack(&mut mem, big, encoding, info)?; + } + let data = mem.into_inner(); + let total_length = data.len() as u32; + total_length.pack(writer, big, encoding, info)?; + writer.write_all(&data)?; + Ok(()) + } +} + +#[derive(Debug, Clone, StructPack, StructUnpack)] +pub struct ChannelRange { + pub source_range: u32, + pub dest_range: u32, +} + +#[derive(Debug, Clone)] +pub struct AdditionalLayerInfo { + pub signature: [u8; 4], + pub key: [u8; 4], + pub data: Vec, +} + +impl StructUnpack for AdditionalLayerInfo { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let signature = <[u8; 4]>::unpack(reader, big, encoding, info)?; + let key = <[u8; 4]>::unpack(reader, big, encoding, info)?; + let length = u32::unpack(reader, big, encoding, info)?; + let data = reader.read_exact_vec(length as usize)?; + Ok(AdditionalLayerInfo { + signature, + key, + data, + }) + } +} + +impl StructPack for AdditionalLayerInfo { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + self.signature.pack(writer, big, encoding, info)?; + self.key.pack(writer, big, encoding, info)?; + let mut length = self.data.len() as u32; + let need_pad = length % 2 != 0; + if need_pad { + length += 1; + } + length.pack(writer, big, encoding, info)?; + writer.write_all(&self.data)?; + if need_pad { + writer.write_u8(0)?; // padding byte + } + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct ChannelImageData { + pub compression: u16, + pub image_data: Vec, +} + +fn get_layer_info(info: &Option>) -> Result<&LayerRecord> { + if let Some(boxed) = info { + if let Some(layer_record) = boxed.downcast_ref::() { + return Ok(layer_record); + } + } + Err(anyhow::anyhow!( + "LayerRecord info is required for ChannelImageData unpacking" + )) +} + +impl StructUnpack for ChannelImageData { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let compression = u16::unpack(reader, big, encoding, info)?; + if compression != 0 { + return Err(anyhow::anyhow!( + "Only raw (compression == 0) channel image data is supported" + )); + } + let info = get_layer_info(info)?; + let len = (info.base.bottom - info.base.top) as usize + * (info.base.right - info.base.left) as usize; + let image_data = reader.read_exact_vec(len)?; + Ok(ChannelImageData { + compression, + image_data, + }) + } +} + +impl StructPack for ChannelImageData { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + self.compression.pack(writer, big, encoding, info)?; + if self.compression == 0 { + let layer_info = get_layer_info(info)?; + let expected_len = (layer_info.base.bottom - layer_info.base.top) as usize + * (layer_info.base.right - layer_info.base.left) as usize; + if self.image_data.len() != expected_len { + return Err(anyhow::anyhow!( + "Channel image data length does not match expected size" + )); + } + } + writer.write_all(&self.image_data)?; + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct GlobalLayerMaskInfo { + pub overlays_color_space: u16, + pub overlays_color_components: [u16; 4], + pub opacity: u16, + pub kind: u8, + pub filler: Vec, +} + +impl StructUnpack for GlobalLayerMaskInfo { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let length = u32::unpack(reader, big, encoding, info)?; + let mut stream_region = StreamRegion::with_size(reader, length as u64)?; + let overlays_color_space = u16::unpack(&mut stream_region, big, encoding, info)?; + let mut overlays_color_components = [0u16; 4]; + for i in 0..4 { + overlays_color_components[i] = u16::unpack(&mut stream_region, big, encoding, info)?; + } + let opacity = u16::unpack(&mut stream_region, big, encoding, info)?; + let kind = u8::unpack(&mut stream_region, big, encoding, info)?; + let filler_length = length as usize - 2 - (4 * 2) - 2 - 1; + let mut filler = vec![0u8; filler_length]; + stream_region.read_exact(&mut filler)?; + Ok(GlobalLayerMaskInfo { + overlays_color_space, + overlays_color_components, + opacity, + kind, + filler, + }) + } +} + +impl StructPack for GlobalLayerMaskInfo { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + let mut mem = MemWriter::new(); + self.overlays_color_space + .pack(&mut mem, big, encoding, info)?; + for component in &self.overlays_color_components { + component.pack(&mut mem, big, encoding, info)?; + } + self.opacity.pack(&mut mem, big, encoding, info)?; + self.kind.pack(&mut mem, big, encoding, info)?; + mem.write_all(&self.filler)?; + let data = mem.into_inner(); + let length = data.len() as u32; + length.pack(writer, big, encoding, info)?; + writer.write_all(&data)?; + Ok(()) + } +} + +pub struct ImageDataSection { + pub compression: u16, + pub image_data: Vec, +} + +fn get_psd_header(info: &Option>) -> Result<&PsdHeader> { + if let Some(boxed) = info { + if let Some(psd_header) = boxed.downcast_ref::() { + return Ok(psd_header); + } + } + Err(anyhow::anyhow!("PsdHeader info is required")) +} + +impl StructUnpack for ImageDataSection { + fn unpack( + reader: &mut R, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result { + let compression = u16::unpack(reader, big, encoding, info)?; + if compression != 0 { + return Err(anyhow::anyhow!( + "Only raw (compression == 0) image data is supported" + )); + } + let psd_header = get_psd_header(info)?; + let len = psd_header.channels as usize + * psd_header.height as usize + * psd_header.width as usize + * psd_header.depth as usize + / 8; + let image_data = reader.read_exact_vec(len)?; + Ok(ImageDataSection { + compression, + image_data, + }) + } +} + +impl StructPack for ImageDataSection { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + info: &Option>, + ) -> Result<()> { + self.compression.pack(writer, big, encoding, info)?; + if self.compression == 0 { + let psd_header = get_psd_header(info)?; + let expected_len = psd_header.channels as usize + * psd_header.height as usize + * psd_header.width as usize + * psd_header.depth as usize + / 8; + if self.image_data.len() != expected_len { + return Err(anyhow::anyhow!( + "Image data length does not match expected size" + )); + } + } + writer.write_all(&self.image_data)?; + Ok(()) + } +} + +pub struct PsdFile { + pub header: PsdHeader, + pub color_mode_data: ColorModeData, + pub image_resource: ImageResourceSection, + pub layer_and_mask_info: LayerAndMaskInfo, + pub image_data: ImageDataSection, +} + +impl StructPack for PsdFile { + fn pack( + &self, + writer: &mut W, + big: bool, + encoding: Encoding, + _info: &Option>, + ) -> Result<()> { + let psd_info = Some(Box::new(self.header.clone()) as Box); + self.header.pack(writer, big, encoding, &psd_info)?; + self.color_mode_data + .pack(writer, big, encoding, &psd_info)?; + self.image_resource.pack(writer, big, encoding, &psd_info)?; + self.layer_and_mask_info + .pack(writer, big, encoding, &psd_info)?; + self.image_data.pack(writer, big, encoding, &psd_info)?; + Ok(()) + } +}