#![deny(missing_docs)]
use std::collections::HashMap;
use std::ops::Deref;
use thiserror::Error;
use sections::file_header_section::FileHeaderSectionError;
use sections::image_data_section::ImageDataSectionError;
use sections::image_resources_section::ImageResourcesSectionError;
use sections::layer_and_mask_information_section::layer::PsdLayerError;
use crate::psd_channel::IntoRgba;
pub use crate::psd_channel::{PsdChannelCompression, PsdChannelKind};
pub use crate::sections::file_header_section::{ColorMode, PsdDepth};
use crate::sections::image_data_section::ChannelBytes;
use crate::sections::image_data_section::ImageDataSection;
pub use crate::sections::image_resources_section::ImageResource;
use crate::sections::image_resources_section::ImageResourcesSection;
pub use crate::sections::image_resources_section::{DescriptorField, UnitFloatStructure};
pub use crate::sections::layer_and_mask_information_section::layer::PsdGroup;
pub use crate::sections::layer_and_mask_information_section::layer::PsdLayer;
use crate::sections::layer_and_mask_information_section::LayerAndMaskInformationSection;
use crate::sections::MajorSections;
use self::sections::file_header_section::FileHeaderSection;
mod blend;
mod psd_channel;
mod render;
mod sections;
#[derive(PartialEq, Debug, Error)]
#[non_exhaustive]
pub enum PsdError {
#[error("Failed to parse PSD header: '{0}'.")]
HeaderError(FileHeaderSectionError),
#[error("Failed to parse PSD layer: '{0}'.")]
LayerError(PsdLayerError),
#[error("Failed to parse PSD data section: '{0}'.")]
ImageError(ImageDataSectionError),
#[error("Failed to parse PSD resource section: '{0}'.")]
ResourceError(ImageResourcesSectionError),
}
#[derive(Debug)]
pub struct Psd {
file_header_section: FileHeaderSection,
image_resources_section: ImageResourcesSection,
layer_and_mask_information_section: LayerAndMaskInformationSection,
image_data_section: ImageDataSection,
}
impl Psd {
pub fn from_bytes(bytes: &[u8]) -> Result<Psd, PsdError> {
let major_sections = MajorSections::from_bytes(bytes).map_err(PsdError::HeaderError)?;
let file_header_section = FileHeaderSection::from_bytes(major_sections.file_header)
.map_err(PsdError::HeaderError)?;
let psd_width = file_header_section.width.0;
let psd_height = file_header_section.height.0;
let channel_count = file_header_section.channel_count.count();
let layer_and_mask_information_section = LayerAndMaskInformationSection::from_bytes(
major_sections.layer_and_mask,
psd_width,
psd_height,
)
.map_err(PsdError::LayerError)?;
let image_data_section = ImageDataSection::from_bytes(
major_sections.image_data,
file_header_section.depth,
psd_height,
channel_count,
)
.map_err(PsdError::ImageError)?;
let image_resources_section =
ImageResourcesSection::from_bytes(major_sections.image_resources)
.map_err(PsdError::ResourceError)?;
Ok(Psd {
file_header_section,
image_resources_section,
layer_and_mask_information_section,
image_data_section,
})
}
}
impl Psd {
pub fn width(&self) -> u32 {
self.file_header_section.width.0
}
pub fn height(&self) -> u32 {
self.file_header_section.height.0
}
pub fn depth(&self) -> PsdDepth {
self.file_header_section.depth
}
pub fn color_mode(&self) -> ColorMode {
self.file_header_section.color_mode
}
}
impl Psd {
pub fn layers(&self) -> &Vec<PsdLayer> {
&self.layer_and_mask_information_section.layers
}
pub fn layer_by_name(&self, name: &str) -> Option<&PsdLayer> {
self.layer_and_mask_information_section
.layers
.item_by_name(name)
}
pub fn layer_by_idx(&self, idx: usize) -> &PsdLayer {
self.layer_and_mask_information_section
.layers
.get(idx)
.unwrap()
}
pub fn groups(&self) -> &HashMap<u32, PsdGroup> {
&self.layer_and_mask_information_section.groups
}
pub fn group_ids_in_order(&self) -> &Vec<u32> {
self.layer_and_mask_information_section
.groups
.group_ids_in_order()
}
pub fn get_group_sub_layers(&self, id: &u32) -> Option<&[PsdLayer]> {
match self.groups().get(id) {
Some(group) => Some(
&self.layer_and_mask_information_section.layers.deref()
[group.contained_layers.clone()],
),
None => None,
}
}
pub fn flatten_layers_rgba(
&self,
filter: &dyn Fn((usize, &PsdLayer)) -> bool,
) -> Result<Vec<u8>, PsdError> {
if self.layers().is_empty() {
return Ok(self.rgba());
}
let layers_to_flatten_top_down: Vec<&PsdLayer> = self
.layers()
.iter()
.enumerate()
.filter(|(_, layer)| (layer.opacity > 0 && layer.visible) || layer.clipping_mask)
.filter(|(idx, layer)| filter((*idx, layer)))
.map(|(_, layer)| layer)
.collect();
let pixel_count = self.width() * self.height();
if layers_to_flatten_top_down.is_empty() {
return Ok(vec![0; pixel_count as usize * 4]);
}
let renderer = render::Renderer::new(&layers_to_flatten_top_down, self.width() as usize);
let mut flattened_pixels = Vec::with_capacity((pixel_count * 4) as usize);
for pixel_idx in 0..pixel_count as usize {
let left = pixel_idx % self.width() as usize;
let top = pixel_idx / self.width() as usize;
let pixel_coord = (left, top);
let blended_pixel = renderer.flattened_pixel(pixel_coord);
flattened_pixels.push(blended_pixel[0]);
flattened_pixels.push(blended_pixel[1]);
flattened_pixels.push(blended_pixel[2]);
flattened_pixels.push(blended_pixel[3]);
}
Ok(flattened_pixels)
}
}
impl Psd {
pub fn rgba(&self) -> Vec<u8> {
self.generate_rgba()
}
pub fn compression(&self) -> &PsdChannelCompression {
&self.image_data_section.compression
}
}
impl Psd {
pub fn resources(&self) -> &Vec<ImageResource> {
&self.image_resources_section.resources
}
}
impl IntoRgba for Psd {
fn rgba_idx(&self, idx: usize) -> Option<usize> {
Some(idx)
}
fn red(&self) -> &ChannelBytes {
&self.image_data_section.red
}
fn green(&self) -> Option<&ChannelBytes> {
match self.color_mode() {
ColorMode::Grayscale => None,
_ => self.image_data_section.green.as_ref(),
}
}
fn blue(&self) -> Option<&ChannelBytes> {
self.image_data_section.blue.as_ref()
}
fn alpha(&self) -> Option<&ChannelBytes> {
self.image_data_section.alpha.as_ref()
}
fn psd_width(&self) -> u32 {
self.width()
}
fn psd_height(&self) -> u32 {
self.height()
}
}
#[cfg(test)]
mod tests {
use crate::sections::file_header_section::FileHeaderSectionError;
use super::*;
#[test]
fn psd_signature_fail() {
let psd = include_bytes!("../tests/fixtures/green-1x1.png");
let err = Psd::from_bytes(psd).expect_err("Psd::from_bytes() didn't catch the PNG file");
assert_eq!(
err,
PsdError::HeaderError(FileHeaderSectionError::InvalidSignature {})
);
}
}