mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-07-07 17:10:36 +08:00
Add support for layer groups in PSD files
This commit is contained in:
@@ -97,6 +97,215 @@ impl ScriptBuilder for PImgBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PImgLayer<'a> {
|
||||||
|
data: &'a PsbValueFixed,
|
||||||
|
name: &'a str,
|
||||||
|
layer_id: i64,
|
||||||
|
/// seems is layer type in PSD files
|
||||||
|
layer_type: i64,
|
||||||
|
left: u32,
|
||||||
|
top: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
opacity: u8,
|
||||||
|
visible: bool,
|
||||||
|
type_: i64,
|
||||||
|
children: Vec<PImgLayer<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for PImgLayer<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("PImgLayer")
|
||||||
|
.field("layer_id", &self.layer_id)
|
||||||
|
.field("layer_type", &self.layer_type)
|
||||||
|
.field("name", &self.name)
|
||||||
|
.field("left", &self.left)
|
||||||
|
.field("top", &self.top)
|
||||||
|
.field("width", &self.width)
|
||||||
|
.field("height", &self.height)
|
||||||
|
.field("opacity", &self.opacity)
|
||||||
|
.field("visible", &self.visible)
|
||||||
|
.field("type", &self.type_)
|
||||||
|
.field("children", &self.children)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PImgLayer<'a> {
|
||||||
|
pub fn new(data: &'a PsbValueFixed, layers: &'a PsbValueFixed) -> Result<Self> {
|
||||||
|
let layer_id = data["layer_id"]
|
||||||
|
.as_i64()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid layer_id"))?;
|
||||||
|
let layer_type = data["layer_type"]
|
||||||
|
.as_i64()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid layer_type"))?;
|
||||||
|
let name = data["name"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid name"))?;
|
||||||
|
let left = data["left"]
|
||||||
|
.as_u32()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid left"))?;
|
||||||
|
let top = data["top"]
|
||||||
|
.as_u32()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid top"))?;
|
||||||
|
let width = data["width"]
|
||||||
|
.as_u32()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid width"))?;
|
||||||
|
let height = data["height"]
|
||||||
|
.as_u32()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid height"))?;
|
||||||
|
let opacity = data["opacity"]
|
||||||
|
.as_u8()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid opacity"))?;
|
||||||
|
let visible = data["visible"].as_i64().unwrap_or(1) != 0;
|
||||||
|
let type_ = data["type"]
|
||||||
|
.as_i64()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid type"))?;
|
||||||
|
let mut children = Vec::new();
|
||||||
|
for layer in layers.members() {
|
||||||
|
if layer_type == 2 || layer_type == 1 {
|
||||||
|
if let Some(parent_id) = layer["group_layer_id"].as_i64() {
|
||||||
|
if parent_id == layer_id {
|
||||||
|
children.push(PImgLayer::new(layer, layers)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if layer_type == 0 {
|
||||||
|
if let Some(base_id) = layer["diff_id"].as_i64() {
|
||||||
|
if base_id == layer_id {
|
||||||
|
children.push(PImgLayer::new(layer, layers)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
data,
|
||||||
|
layer_id,
|
||||||
|
layer_type,
|
||||||
|
name,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
opacity,
|
||||||
|
visible,
|
||||||
|
type_,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
1 + self.children.iter().map(|c| c.len()).sum::<usize>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_img(&self, img: &PImg) -> Result<ImageData> {
|
||||||
|
if self.layer_type == 2 || self.layer_type == 1 {
|
||||||
|
anyhow::bail!("Group layers do not have image data");
|
||||||
|
}
|
||||||
|
if self.layer_id == -1 {
|
||||||
|
// Generate a empty image
|
||||||
|
Ok(ImageData {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
color_type: ImageColorType::Rgba,
|
||||||
|
depth: 8,
|
||||||
|
data: vec![0u8; (self.width * self.height * 4) as usize],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let tlg = img.load_img(self.layer_id).map_err(|e| {
|
||||||
|
anyhow::anyhow!("Failed to load image for layer_id {}: {}", self.layer_id, e)
|
||||||
|
})?;
|
||||||
|
let mut img = ImageData {
|
||||||
|
width: tlg.width,
|
||||||
|
height: tlg.height,
|
||||||
|
color_type: match tlg.color {
|
||||||
|
TlgColorType::Bgr24 => ImageColorType::Bgr,
|
||||||
|
TlgColorType::Bgra32 => ImageColorType::Bgra,
|
||||||
|
TlgColorType::Grayscale8 => ImageColorType::Grayscale,
|
||||||
|
},
|
||||||
|
depth: 8,
|
||||||
|
data: tlg.data.clone(),
|
||||||
|
};
|
||||||
|
convert_to_rgba(&mut img)?;
|
||||||
|
Ok(img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_to_psd(&self, img: &PImg, psd: &mut PsdWriter, base: &mut ImageData) -> Result<()> {
|
||||||
|
if self.children.is_empty() {
|
||||||
|
let img = self.load_img(img)?;
|
||||||
|
let mut visible = self.visible;
|
||||||
|
if !self.data["diff_id"].is_none() {
|
||||||
|
visible = false; // Diff layers are always hide by default
|
||||||
|
}
|
||||||
|
if visible {
|
||||||
|
draw_on_img_with_opacity(base, &img, self.left, self.top, self.opacity)?;
|
||||||
|
}
|
||||||
|
let option = PsdLayerOption {
|
||||||
|
visible,
|
||||||
|
opacity: self.opacity,
|
||||||
|
};
|
||||||
|
psd.add_layer(self.name, self.left, self.top, img, Some(option))?;
|
||||||
|
} else {
|
||||||
|
psd.add_layer_group_end()?;
|
||||||
|
if self.layer_type == 0 {
|
||||||
|
let img = self.load_img(img)?;
|
||||||
|
let visible = self.visible;
|
||||||
|
if visible {
|
||||||
|
draw_on_img_with_opacity(base, &img, self.left, self.top, self.opacity)?;
|
||||||
|
}
|
||||||
|
let option = PsdLayerOption {
|
||||||
|
visible,
|
||||||
|
opacity: self.opacity,
|
||||||
|
};
|
||||||
|
psd.add_layer(self.name, self.left, self.top, img, Some(option))?;
|
||||||
|
}
|
||||||
|
for child in &self.children {
|
||||||
|
child.save_to_psd(img, psd, base)?;
|
||||||
|
}
|
||||||
|
let option = if self.layer_type == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(PsdLayerOption {
|
||||||
|
visible: self.visible,
|
||||||
|
opacity: self.opacity,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
psd.add_layer_group(self.name, self.layer_type == 2, option)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PImgLayerRoot<'a> {
|
||||||
|
layers: Vec<PImgLayer<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PImgLayerRoot<'a> {
|
||||||
|
pub fn new(layers: &'a PsbValueFixed) -> Result<Self> {
|
||||||
|
let mut root_layers = Vec::new();
|
||||||
|
for layer in layers.members() {
|
||||||
|
if layer["group_layer_id"].is_none() && layer["diff_id"].is_none() {
|
||||||
|
root_layers.push(PImgLayer::new(layer, layers)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
layers: root_layers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.layers.iter().map(|l| l.len()).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_to_psd(&self, img: &PImg, psd: &mut PsdWriter, base: &mut ImageData) -> Result<()> {
|
||||||
|
for layer in &self.layers {
|
||||||
|
layer.save_to_psd(img, psd, base)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// Emote PImg Script
|
/// Emote PImg Script
|
||||||
pub struct PImg {
|
pub struct PImg {
|
||||||
@@ -226,7 +435,7 @@ impl Script for PImg {
|
|||||||
let height = psb["height"]
|
let height = psb["height"]
|
||||||
.as_u32()
|
.as_u32()
|
||||||
.ok_or(anyhow::anyhow!("missing height"))?;
|
.ok_or(anyhow::anyhow!("missing height"))?;
|
||||||
let mut psd = PsdWriter::new(width, height, ImageColorType::Rgba, 8)?
|
let mut psd = PsdWriter::new(width, height, ImageColorType::Rgba, 8, encoding)?
|
||||||
.compress(self.psd_compress)
|
.compress(self.psd_compress)
|
||||||
.zlib_compression_level(self.zlib_compression_level);
|
.zlib_compression_level(self.zlib_compression_level);
|
||||||
let mut base = ImageData {
|
let mut base = ImageData {
|
||||||
@@ -236,75 +445,14 @@ impl Script for PImg {
|
|||||||
depth: 8,
|
depth: 8,
|
||||||
data: vec![0u8; (width * height * 4) as usize],
|
data: vec![0u8; (width * height * 4) as usize],
|
||||||
};
|
};
|
||||||
let mut new_layers = PsbListFixed::new();
|
let layers = PImgLayerRoot::new(&psb["layers"])?;
|
||||||
for layer in psb["layers"].members() {
|
if layers.len() != psb["layers"].len() {
|
||||||
if layer["diff_id"].is_none() {
|
return Err(anyhow::anyhow!("Layer hierarchy is invalid"));
|
||||||
new_layers.values.push(layer.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for layer in psb["layers"].members() {
|
|
||||||
if !layer["diff_id"].is_none() {
|
|
||||||
new_layers.values.push(layer.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for layer in new_layers.iter() {
|
|
||||||
let layer_id = layer["layer_id"]
|
|
||||||
.as_i64()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid layer_id"))?;
|
|
||||||
let layer_name = layer["name"]
|
|
||||||
.as_str()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid name"))?;
|
|
||||||
let width = layer["width"]
|
|
||||||
.as_u32()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid width"))?;
|
|
||||||
let height = layer["height"]
|
|
||||||
.as_u32()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid height"))?;
|
|
||||||
let top = layer["top"]
|
|
||||||
.as_u32()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid top"))?;
|
|
||||||
let left = layer["left"]
|
|
||||||
.as_u32()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid left"))?;
|
|
||||||
let opacity = layer["opacity"]
|
|
||||||
.as_u8()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Layer does not have a valid opacity"))?;
|
|
||||||
let mut visible = layer["visible"].as_u8().unwrap_or(1) != 0;
|
|
||||||
if !layer["diff_id"].is_none() {
|
|
||||||
visible = false; // Always hide diff layers
|
|
||||||
}
|
|
||||||
let img = self.load_img(layer_id)?;
|
|
||||||
let mut layer = ImageData {
|
|
||||||
width: img.width,
|
|
||||||
height: img.height,
|
|
||||||
color_type: match img.color {
|
|
||||||
TlgColorType::Bgr24 => ImageColorType::Bgr,
|
|
||||||
TlgColorType::Bgra32 => ImageColorType::Bgra,
|
|
||||||
TlgColorType::Grayscale8 => ImageColorType::Grayscale,
|
|
||||||
},
|
|
||||||
depth: 8,
|
|
||||||
data: img.data.clone(),
|
|
||||||
};
|
|
||||||
if img.width != width || img.height != height {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"Layer ID {} size mismatch: expected {}x{}, got {}x{}",
|
|
||||||
layer_id,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
img.width,
|
|
||||||
img.height
|
|
||||||
));
|
|
||||||
}
|
|
||||||
convert_to_rgba(&mut layer)?;
|
|
||||||
let option = PsdLayerOption { opacity, visible };
|
|
||||||
if visible {
|
|
||||||
draw_on_img_with_opacity(&mut base, &layer, left, top, opacity)?;
|
|
||||||
}
|
|
||||||
psd.add_layer(layer_name, left, top, layer, Some(option))?;
|
|
||||||
}
|
}
|
||||||
|
layers.save_to_psd(self, &mut psd, &mut base)?;
|
||||||
let file = std::fs::File::create(filename)?;
|
let file = std::fs::File::create(filename)?;
|
||||||
let mut writer = std::io::BufWriter::new(file);
|
let mut writer = std::io::BufWriter::new(file);
|
||||||
psd.save(base, &mut writer, encoding)?;
|
psd.save(base, &mut writer)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ impl Script for DpngImage {
|
|||||||
self.img.header.image_height,
|
self.img.header.image_height,
|
||||||
ImageColorType::Rgba,
|
ImageColorType::Rgba,
|
||||||
8,
|
8,
|
||||||
|
encoding,
|
||||||
)?
|
)?
|
||||||
.compress(self.config.psd_compress)
|
.compress(self.config.psd_compress)
|
||||||
.zlib_compression_level(self.config.zlib_compression_level);
|
.zlib_compression_level(self.config.zlib_compression_level);
|
||||||
@@ -266,7 +267,7 @@ impl Script for DpngImage {
|
|||||||
}
|
}
|
||||||
let file = std::fs::File::create(filename)?;
|
let file = std::fs::File::create(filename)?;
|
||||||
let mut writer = std::io::BufWriter::new(file);
|
let mut writer = std::io::BufWriter::new(file);
|
||||||
psd.save(base, &mut writer, encoding)?;
|
psd.save(base, &mut writer)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -793,9 +793,9 @@ pub fn draw_on_img_with_opacity(
|
|||||||
let src_comp = diff_pixel[i] as u16;
|
let src_comp = diff_pixel[i] as u16;
|
||||||
let dst_comp = base_pixel_orig[i] as u16;
|
let dst_comp = base_pixel_orig[i] as u16;
|
||||||
|
|
||||||
let numerator = src_comp * src_alpha_u16
|
let numerator = src_comp as u32 * src_alpha_u16 as u32
|
||||||
+ (dst_comp * dst_alpha_u16 * (255 - src_alpha_u16)) / 255;
|
+ (dst_comp as u32 * dst_alpha_u16 as u32 * (255 - src_alpha_u16) as u32) / 255;
|
||||||
base.data[base_idx + i] = (numerator / out_alpha_u16) as u8;
|
base.data[base_idx + i] = (numerator / out_alpha_u16 as u32) as u8;
|
||||||
}
|
}
|
||||||
base.data[base_idx + 3] = out_alpha_u16 as u8;
|
base.data[base_idx + 3] = out_alpha_u16 as u8;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod types;
|
|||||||
|
|
||||||
use crate::ext::io::*;
|
use crate::ext::io::*;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
use crate::utils::encoding::*;
|
||||||
use crate::utils::img::*;
|
use crate::utils::img::*;
|
||||||
use crate::utils::struct_pack::*;
|
use crate::utils::struct_pack::*;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -35,11 +36,31 @@ pub struct PsdWriter {
|
|||||||
color_type: ImageColorType,
|
color_type: ImageColorType,
|
||||||
compress: bool,
|
compress: bool,
|
||||||
zlib_compression_level: u32,
|
zlib_compression_level: u32,
|
||||||
|
encoding: Encoding,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_unicode_layer(name: &str) -> Result<AdditionalLayerInfo> {
|
||||||
|
let layer = UnicodeLayer {
|
||||||
|
name: UnicodeString(name.to_string()),
|
||||||
|
};
|
||||||
|
let mut data = MemWriter::new();
|
||||||
|
layer.pack(&mut data, true, Encoding::Utf16BE, &None)?;
|
||||||
|
Ok(AdditionalLayerInfo {
|
||||||
|
signature: *IMAGE_RESOURCE_SIGNATURE,
|
||||||
|
key: *b"luni",
|
||||||
|
data: data.into_inner(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PsdWriter {
|
impl PsdWriter {
|
||||||
/// Creates a new PSD writer with the specified dimensions, color type, and bit depth.
|
/// 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<Self> {
|
pub fn new(
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
color_type: ImageColorType,
|
||||||
|
depth: u8,
|
||||||
|
encoding: Encoding,
|
||||||
|
) -> Result<Self> {
|
||||||
let color_type = match color_type {
|
let color_type = match color_type {
|
||||||
ImageColorType::Bgr => ImageColorType::Rgb,
|
ImageColorType::Bgr => ImageColorType::Rgb,
|
||||||
ImageColorType::Bgra => ImageColorType::Rgba,
|
ImageColorType::Bgra => ImageColorType::Rgba,
|
||||||
@@ -91,6 +112,7 @@ impl PsdWriter {
|
|||||||
color_type,
|
color_type,
|
||||||
compress: true,
|
compress: true,
|
||||||
zlib_compression_level: 6,
|
zlib_compression_level: 6,
|
||||||
|
encoding,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,12 +237,13 @@ impl PsdWriter {
|
|||||||
image_data: d,
|
image_data: d,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let encoded = encode_string(self.encoding, &name, false)?;
|
||||||
let layer = LayerRecord {
|
let layer = LayerRecord {
|
||||||
base: layer_base,
|
base: layer_base,
|
||||||
layer_mask: None,
|
layer_mask: None,
|
||||||
layer_blending_ranges,
|
layer_blending_ranges,
|
||||||
layer_name: PascalString4(name.to_string()),
|
layer_name: PascalString4(encoded),
|
||||||
infos: vec![],
|
infos: vec![encode_unicode_layer(name)?],
|
||||||
};
|
};
|
||||||
self.psd
|
self.psd
|
||||||
.layer_and_mask_info
|
.layer_and_mask_info
|
||||||
@@ -240,15 +263,115 @@ impl PsdWriter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds the start of a layer group to the PSD file.
|
||||||
|
pub fn add_layer_group(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
is_closed: bool,
|
||||||
|
option: Option<PsdLayerOption>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let type_info = SectionDividerSetting {
|
||||||
|
typ: if is_closed { 2 } else { 1 },
|
||||||
|
};
|
||||||
|
let mut data = MemWriter::new();
|
||||||
|
type_info.pack(&mut data, true, self.encoding, &None)?;
|
||||||
|
let encoded = encode_string(self.encoding, &name, false)?;
|
||||||
|
let flags = if let Some(opt) = &option {
|
||||||
|
opt.to_flags()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let opacity = if let Some(opt) = &option {
|
||||||
|
opt.opacity
|
||||||
|
} else {
|
||||||
|
255
|
||||||
|
};
|
||||||
|
let layer = LayerRecord {
|
||||||
|
base: LayerRecordBase {
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
channels: 0,
|
||||||
|
channel_infos: vec![],
|
||||||
|
blend_mode_signature: *IMAGE_RESOURCE_SIGNATURE,
|
||||||
|
blend_mode_key: *b"pass",
|
||||||
|
opacity,
|
||||||
|
clipping: 0,
|
||||||
|
flags,
|
||||||
|
filler: 0,
|
||||||
|
},
|
||||||
|
layer_mask: None,
|
||||||
|
layer_blending_ranges: LayerBlendingRanges {
|
||||||
|
gray_blend_dest: 0xFFFF,
|
||||||
|
gray_blend_source: 0xFFFF,
|
||||||
|
channel_ranges: vec![],
|
||||||
|
},
|
||||||
|
layer_name: PascalString4(encoded),
|
||||||
|
infos: vec![
|
||||||
|
AdditionalLayerInfo {
|
||||||
|
signature: *IMAGE_RESOURCE_SIGNATURE,
|
||||||
|
key: *b"lsct",
|
||||||
|
data: data.into_inner(),
|
||||||
|
},
|
||||||
|
encode_unicode_layer(name)?,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
self.psd
|
||||||
|
.layer_and_mask_info
|
||||||
|
.layer_info
|
||||||
|
.layer_records
|
||||||
|
.push(layer);
|
||||||
|
self.psd.layer_and_mask_info.layer_info.layer_count += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the end of a layer group to the PSD file.
|
||||||
|
pub fn add_layer_group_end(&mut self) -> Result<()> {
|
||||||
|
let type_info = SectionDividerSetting { typ: 3 };
|
||||||
|
let mut data = MemWriter::new();
|
||||||
|
type_info.pack(&mut data, true, self.encoding, &None)?;
|
||||||
|
let layer = LayerRecord {
|
||||||
|
base: LayerRecordBase {
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
channels: 0,
|
||||||
|
channel_infos: vec![],
|
||||||
|
blend_mode_signature: *IMAGE_RESOURCE_SIGNATURE,
|
||||||
|
blend_mode_key: *b"norm",
|
||||||
|
opacity: 255,
|
||||||
|
clipping: 0,
|
||||||
|
flags: 0,
|
||||||
|
filler: 0,
|
||||||
|
},
|
||||||
|
layer_mask: None,
|
||||||
|
layer_blending_ranges: LayerBlendingRanges {
|
||||||
|
gray_blend_dest: 0xFFFF,
|
||||||
|
gray_blend_source: 0xFFFF,
|
||||||
|
channel_ranges: vec![],
|
||||||
|
},
|
||||||
|
layer_name: PascalString4(b"</Layer group>".to_vec()),
|
||||||
|
infos: vec![AdditionalLayerInfo {
|
||||||
|
signature: *IMAGE_RESOURCE_SIGNATURE,
|
||||||
|
key: *b"lsct",
|
||||||
|
data: data.into_inner(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
self.psd
|
||||||
|
.layer_and_mask_info
|
||||||
|
.layer_info
|
||||||
|
.layer_records
|
||||||
|
.push(layer);
|
||||||
|
self.psd.layer_and_mask_info.layer_info.layer_count += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Saves the PSD file to the specified writer with the given encoding.
|
/// 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.
|
/// * `data` - The final composite image data to be saved in the PSD file.
|
||||||
pub fn save<T: Write>(
|
pub fn save<T: Write>(&mut self, data: ImageData, mut writer: T) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
data: ImageData,
|
|
||||||
mut writer: T,
|
|
||||||
encoding: Encoding,
|
|
||||||
) -> Result<()> {
|
|
||||||
if data.color_type == ImageColorType::Bgr {
|
if data.color_type == ImageColorType::Bgr {
|
||||||
convert_bgr_to_rgb(&mut data.clone())?;
|
convert_bgr_to_rgb(&mut data.clone())?;
|
||||||
}
|
}
|
||||||
@@ -382,7 +505,7 @@ impl PsdWriter {
|
|||||||
let compression = if self.compress { 1 } else { 0 };
|
let compression = if self.compress { 1 } else { 0 };
|
||||||
self.psd.image_data.image_data = planar_data;
|
self.psd.image_data.image_data = planar_data;
|
||||||
self.psd.image_data.compression = compression;
|
self.psd.image_data.compression = compression;
|
||||||
self.psd.pack(&mut writer, true, encoding, &None)?;
|
self.psd.pack(&mut writer, true, self.encoding, &None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::ext::io::*;
|
use crate::ext::io::*;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use crate::utils::encoding::*;
|
|
||||||
use crate::utils::struct_pack::*;
|
use crate::utils::struct_pack::*;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use msg_tool_macro::*;
|
use msg_tool_macro::*;
|
||||||
@@ -10,57 +9,51 @@ use std::io::{Read, Seek, Write};
|
|||||||
pub const PSD_SIGNATURE: &[u8; 4] = b"8BPS";
|
pub const PSD_SIGNATURE: &[u8; 4] = b"8BPS";
|
||||||
pub const IMAGE_RESOURCE_SIGNATURE: &[u8; 4] = b"8BIM";
|
pub const IMAGE_RESOURCE_SIGNATURE: &[u8; 4] = b"8BIM";
|
||||||
|
|
||||||
// #[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
// pub struct UnicodeString(pub String);
|
pub struct UnicodeString(pub String);
|
||||||
|
|
||||||
// impl StructPack for UnicodeString {
|
impl StructPack for UnicodeString {
|
||||||
// fn pack<W: Write>(
|
fn pack<W: Write>(
|
||||||
// &self,
|
&self,
|
||||||
// writer: &mut W,
|
writer: &mut W,
|
||||||
// big: bool,
|
big: bool,
|
||||||
// encoding: Encoding,
|
encoding: Encoding,
|
||||||
// info: &Option<Box<dyn Any>>,
|
info: &Option<Box<dyn Any>>,
|
||||||
// ) -> Result<()> {
|
) -> Result<()> {
|
||||||
// let mut encoded: Vec<_> = self.0.encode_utf16().collect();
|
let encoded: Vec<_> = self.0.encode_utf16().collect();
|
||||||
// encoded.push(0); // null-terminator
|
let len = encoded.len() as u32;
|
||||||
// let len = encoded.len() as u32;
|
len.pack(writer, big, encoding, info)?;
|
||||||
// len.pack(writer, big, encoding, info)?;
|
for c in encoded {
|
||||||
// for c in encoded {
|
c.pack(writer, big, encoding, info)?;
|
||||||
// c.pack(writer, big, encoding, info)?;
|
}
|
||||||
// }
|
Ok(())
|
||||||
// Ok(())
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// impl StructUnpack for UnicodeString {
|
impl StructUnpack for UnicodeString {
|
||||||
// fn unpack<R: Read + Seek>(
|
fn unpack<R: Read + Seek>(
|
||||||
// reader: &mut R,
|
reader: &mut R,
|
||||||
// big: bool,
|
big: bool,
|
||||||
// encoding: Encoding,
|
encoding: Encoding,
|
||||||
// info: &Option<Box<dyn Any>>,
|
info: &Option<Box<dyn Any>>,
|
||||||
// ) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// let len = u32::unpack(reader, big, encoding, info)?;
|
let len = u32::unpack(reader, big, encoding, info)?;
|
||||||
// if len == 0 {
|
if len == 0 {
|
||||||
// return Ok(UnicodeString(String::new()));
|
return Ok(UnicodeString(String::new()));
|
||||||
// }
|
}
|
||||||
// let mut encoded: Vec<u16> = Vec::with_capacity((len as usize) - 1);
|
let mut encoded: Vec<u16> = Vec::with_capacity(len as usize);
|
||||||
// for _ in 0..len - 1 {
|
for _ in 0..len {
|
||||||
// let c = u16::unpack(reader, big, encoding, info)?;
|
let c = u16::unpack(reader, big, encoding, info)?;
|
||||||
// encoded.push(c);
|
encoded.push(c);
|
||||||
// }
|
}
|
||||||
// // null-terminator
|
let string = String::from_utf16(&encoded)
|
||||||
// let null = u16::unpack(reader, big, encoding, info)?;
|
.map_err(|e| anyhow::anyhow!("Failed to decode UTF-16 string: {}", e))?;
|
||||||
// if null != 0 {
|
Ok(UnicodeString(string))
|
||||||
// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PascalString(pub String);
|
pub struct PascalString(pub Vec<u8>);
|
||||||
|
|
||||||
impl StructPack for PascalString {
|
impl StructPack for PascalString {
|
||||||
fn pack<W: Write>(
|
fn pack<W: Write>(
|
||||||
@@ -70,13 +63,12 @@ impl StructPack for PascalString {
|
|||||||
encoding: Encoding,
|
encoding: Encoding,
|
||||||
info: &Option<Box<dyn Any>>,
|
info: &Option<Box<dyn Any>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut encoded = encode_string(encoding, &self.0, true)?;
|
let len = self.0.len() as u8;
|
||||||
let len = encoded.len() as u8;
|
|
||||||
len.pack(writer, big, encoding, info)?;
|
len.pack(writer, big, encoding, info)?;
|
||||||
|
writer.write_all(&self.0)?;
|
||||||
if len % 2 == 0 {
|
if len % 2 == 0 {
|
||||||
encoded.push(0); // padding byte
|
writer.write_u8(0)?; // padding byte
|
||||||
}
|
}
|
||||||
writer.write_all(&encoded)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,13 +85,12 @@ impl StructUnpack for PascalString {
|
|||||||
if len % 2 == 0 {
|
if len % 2 == 0 {
|
||||||
reader.read_u8()?; // padding byte
|
reader.read_u8()?; // padding byte
|
||||||
}
|
}
|
||||||
let string = decode_to_string(encoding, &encoded, true)?;
|
Ok(PascalString(encoded))
|
||||||
Ok(PascalString(string))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PascalString4(pub String);
|
pub struct PascalString4(pub Vec<u8>);
|
||||||
|
|
||||||
impl StructPack for PascalString4 {
|
impl StructPack for PascalString4 {
|
||||||
fn pack<W: Write>(
|
fn pack<W: Write>(
|
||||||
@@ -109,16 +100,15 @@ impl StructPack for PascalString4 {
|
|||||||
encoding: Encoding,
|
encoding: Encoding,
|
||||||
info: &Option<Box<dyn Any>>,
|
info: &Option<Box<dyn Any>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut encoded = encode_string(encoding, &self.0, true)?;
|
let len = self.0.len() as u8;
|
||||||
let len = encoded.len() as u8;
|
|
||||||
len.pack(writer, big, encoding, info)?;
|
len.pack(writer, big, encoding, info)?;
|
||||||
let padding = 4 - (len as usize + 1) % 4;
|
let padding = 4 - (len as usize + 1) % 4;
|
||||||
|
writer.write_all(&self.0)?;
|
||||||
if padding != 4 {
|
if padding != 4 {
|
||||||
for _ in 0..padding {
|
for _ in 0..padding {
|
||||||
encoded.push(0); // padding bytes
|
writer.write_u8(0)?; // padding byte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.write_all(&encoded)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,8 +134,7 @@ impl StructUnpack for PascalString4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let string = decode_to_string(encoding, &encoded, true)?;
|
Ok(PascalString4(encoded))
|
||||||
Ok(PascalString4(string))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,12 +310,13 @@ impl StructPack for LayerInfo {
|
|||||||
for layer_record in &self.layer_records {
|
for layer_record in &self.layer_records {
|
||||||
layer_record.pack(&mut mem, big, encoding, info)?;
|
layer_record.pack(&mut mem, big, encoding, info)?;
|
||||||
}
|
}
|
||||||
|
let mut index = 0usize;
|
||||||
for i in 0..self.layer_count {
|
for i in 0..self.layer_count {
|
||||||
let layer = &self.layer_records[i as usize];
|
let layer = &self.layer_records[i as usize];
|
||||||
let info = Some(Box::new(layer.clone()) as Box<dyn Any>);
|
let info = Some(Box::new(layer.clone()) as Box<dyn Any>);
|
||||||
for j in 0..layer.base.channels {
|
for _ 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];
|
let data = &self.channel_image_data[index];
|
||||||
|
index += 1;
|
||||||
data.pack(&mut mem, big, encoding, &info)?;
|
data.pack(&mut mem, big, encoding, &info)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -931,3 +921,14 @@ impl StructPack for PsdFile {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, StructPack, StructUnpack)]
|
||||||
|
pub struct SectionDividerSetting {
|
||||||
|
pub typ: u32,
|
||||||
|
// TODO: implement the rest fields
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, StructPack, StructUnpack)]
|
||||||
|
pub struct UnicodeLayer {
|
||||||
|
pub name: UnicodeString,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user