#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PsdBlendMode {
Normal,
Multiply,
Screen,
Overlay,
}
impl PsdBlendMode {
pub fn fourcc(&self) -> &'static str {
match self {
Self::Normal => "norm",
Self::Multiply => "mul ",
Self::Screen => "scrn",
Self::Overlay => "over",
}
}
}
#[derive(Debug, Clone)]
pub struct PsdLayer {
pub name: String,
pub width: u32,
pub height: u32,
pub opacity: u8,
pub blend_mode: PsdBlendMode,
pub visible: bool,
pub pixels: Vec<[u8; 4]>,
}
impl PsdLayer {
pub fn new_solid(name: &str, width: u32, height: u32, color: [u8; 4]) -> Self {
let pixels = vec![color; (width * height) as usize];
Self {
name: name.to_string(),
width,
height,
opacity: 255,
blend_mode: PsdBlendMode::Normal,
visible: true,
pixels,
}
}
pub fn pixel_count(&self) -> usize {
self.pixels.len()
}
}
#[derive(Debug, Clone)]
pub struct PsdExport {
pub width: u32,
pub height: u32,
pub layers: Vec<PsdLayer>,
}
impl PsdExport {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
layers: Vec::new(),
}
}
pub fn add_layer(&mut self, layer: PsdLayer) {
self.layers.push(layer);
}
pub fn layer_count(&self) -> usize {
self.layers.len()
}
pub fn visible_layer_count(&self) -> usize {
self.layers.iter().filter(|l| l.visible).count()
}
}
pub fn validate_psd(doc: &PsdExport) -> bool {
doc.width > 0 && doc.height > 0
}
pub fn estimate_psd_bytes(doc: &PsdExport) -> usize {
let layer_data: usize = doc.layers.iter().map(|l| l.pixel_count() * 4 + 256).sum();
26 + layer_data + (doc.width * doc.height) as usize * 4
}
pub fn psd_metadata_json(doc: &PsdExport) -> String {
format!(
"{{\"width\":{},\"height\":{},\"layers\":{}}}",
doc.width,
doc.height,
doc.layer_count()
)
}
pub fn find_psd_layer<'a>(doc: &'a PsdExport, name: &str) -> Option<&'a PsdLayer> {
doc.layers.iter().find(|l| l.name == name)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_doc() -> PsdExport {
let mut doc = PsdExport::new(128, 128);
doc.add_layer(PsdLayer::new_solid(
"Background",
128,
128,
[200, 200, 200, 255],
));
let mut fg = PsdLayer::new_solid("Foreground", 128, 128, [100, 0, 0, 200]);
fg.blend_mode = PsdBlendMode::Multiply;
doc.add_layer(fg);
doc
}
#[test]
fn test_layer_count() {
assert_eq!(sample_doc().layer_count(), 2);
}
#[test]
fn test_visible_layer_count() {
assert_eq!(sample_doc().visible_layer_count(), 2);
}
#[test]
fn test_validate_valid() {
assert!(validate_psd(&sample_doc()));
}
#[test]
fn test_blend_mode_fourcc() {
assert_ne!(
PsdBlendMode::Normal.fourcc(),
PsdBlendMode::Multiply.fourcc()
);
}
#[test]
fn test_estimate_bytes_positive() {
assert!(estimate_psd_bytes(&sample_doc()) > 0);
}
#[test]
fn test_metadata_json() {
let json = psd_metadata_json(&sample_doc());
assert!(json.contains("layers"));
}
#[test]
fn test_find_psd_layer() {
let doc = sample_doc();
assert!(find_psd_layer(&doc, "Background").is_some());
assert!(find_psd_layer(&doc, "None").is_none());
}
#[test]
fn test_pixel_count() {
let l = PsdLayer::new_solid("L", 16, 16, [0; 4]);
assert_eq!(l.pixel_count(), 256);
}
}