From 7046510d19bc628c258ed390d7c94db4cf899d40 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Tue, 3 Feb 2026 00:20:47 +0800 Subject: [PATCH] Add support to compress psd files --- Cargo.toml | 2 +- src/args.rs | 4 ++ src/main.rs | 2 + src/scripts/qlie/image/dpng.rs | 4 +- src/types.rs | 4 ++ src/utils/psd/mod.rs | 82 +++++++++++++++++++++++----------- 6 files changed, 69 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0bf36dc..dbdf3ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,7 +121,7 @@ utils-crc32 = [] utils-escape = ["fancy-regex"] utils-mmx = [] utils-pcm = [] -utils-psd = ["image"] +utils-psd = ["image", "flate2"] utils-rc4 = [] utils-serde-base64bytes = ["base64"] utils-str = [] diff --git a/src/args.rs b/src/args.rs index 80cc464..225b91c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -656,6 +656,10 @@ pub struct Arg { #[arg(long, global = true, action = ArgAction::SetTrue)] /// Export Qlie DPNG images as PSD files. pub qlie_dpng_psd: bool, + #[cfg(feature = "utils-psd")] + #[arg(long, global = true, action = ArgAction::SetTrue)] + /// Whether to disable compression for image data in PSD files. + pub psd_no_compress: bool, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/main.rs b/src/main.rs index fe17977..7c7c2e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3365,6 +3365,8 @@ fn main() { qlie_dpng_use_raw_png: arg.qlie_dpng_use_raw_png, #[cfg(feature = "qlie-img")] qlie_dpng_psd: arg.qlie_dpng_psd, + #[cfg(feature = "utils-psd")] + psd_compress: !arg.psd_no_compress, }); 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 6614d69..e567d24 100644 --- a/src/scripts/qlie/image/dpng.rs +++ b/src/scripts/qlie/image/dpng.rs @@ -227,7 +227,9 @@ impl Script for DpngImage { self.img.header.image_height, ImageColorType::Rgba, 8, - )?; + )? + .compress(self.config.psd_compress) + .zlib_compression_level(self.config.zlib_compression_level); let (idx, tile) = self .img .tiles diff --git a/src/types.rs b/src/types.rs index 1533900..0774b95 100644 --- a/src/types.rs +++ b/src/types.rs @@ -609,6 +609,10 @@ pub struct ExtraConfig { #[cfg(feature = "qlie-img")] /// Export Qlie DPNG images as PSD files. pub qlie_dpng_psd: bool, + #[cfg(feature = "utils-psd")] + #[default(true)] + /// Whether to use compression for image data in PSD files. + pub psd_compress: bool, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/utils/psd/mod.rs b/src/utils/psd/mod.rs index aa5c22e..493ffff 100644 --- a/src/utils/psd/mod.rs +++ b/src/utils/psd/mod.rs @@ -12,6 +12,8 @@ use types::*; pub struct PsdWriter { psd: PsdFile, color_type: ImageColorType, + compress: bool, + zlib_compression_level: u32, } impl PsdWriter { @@ -63,7 +65,24 @@ impl PsdWriter { image_data: vec![], }, }; - Ok(Self { psd, color_type }) + Ok(Self { + psd, + color_type, + compress: true, + zlib_compression_level: 6, + }) + } + + /// Sets whether to compress image data in the PSD file. + pub fn compress(mut self, compress: bool) -> Self { + self.compress = compress; + self + } + + /// Sets the zlib compression level for the PSD file. + pub fn zlib_compression_level(mut self, level: u32) -> Self { + self.zlib_compression_level = level; + self } /// Add a visible layer to the PSD file. @@ -75,39 +94,24 @@ impl PsdWriter { convert_bgra_to_rgba(&mut data)?; } let length = data.width as u32 * data.height as u32; - let mut channel_infos = Vec::new(); + let mut channel_ids = Vec::new(); if data.color_type == ImageColorType::Grayscale { - channel_infos.push(ChannelInfo { - channel_id: 0, - length: length + 2, - }); + channel_ids.push(0); } 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, - }); + channel_ids.push(0); // R + channel_ids.push(1); // G + channel_ids.push(2); // B if data.color_type == ImageColorType::Rgba { - channel_infos.push(ChannelInfo { - channel_id: -1, - length: length + 2, - }); + channel_ids.push(-1); // Alpha } } - let layer_base = LayerRecordBase { + let mut 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, + channel_infos: Vec::new(), blend_mode_signature: *IMAGE_RESOURCE_SIGNATURE, blend_mode_key: *b"norm", opacity: 255, @@ -137,8 +141,33 @@ impl PsdWriter { d.push(data.data[index]); } } + if self.compress { + for y in 0..data.height { + let ind = y as usize * data.width as usize; + let mut pre = d[ind]; + for x in 1..data.width as usize { + let cur = d[ind + x]; + d[ind + x] = cur.wrapping_sub(pre); + pre = cur; + } + } + let mut data = Vec::new(); + let mut enc = flate2::write::ZlibEncoder::new( + &mut data, + flate2::Compression::new(self.zlib_compression_level), + ); + enc.write_all(&d)?; + enc.finish()?; + d = data; + } + let cinfo = ChannelInfo { + channel_id: channel_ids[i as usize], + length: d.len() as u32 + 2, // +2 for compression method + }; + layer_base.channel_infos.push(cinfo); + let compression = if self.compress { 3 } else { 0 }; image_data.push(ChannelImageData { - compression: 0, + compression, image_data: d, }); } @@ -218,9 +247,8 @@ impl PsdWriter { planar_data.push(val); } } - + // TODO: RLE compression for planar data if needed self.psd.image_data.image_data = planar_data; - self.psd.image_data.compression = 0; // Raw self.psd.pack(&mut writer, true, encoding, &None)?; Ok(()) }