oxihuman_export/
psd_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum PsdBlendMode {
10 Normal,
11 Multiply,
12 Screen,
13 Overlay,
14}
15
16impl PsdBlendMode {
17 pub fn fourcc(&self) -> &'static str {
19 match self {
20 Self::Normal => "norm",
21 Self::Multiply => "mul ",
22 Self::Screen => "scrn",
23 Self::Overlay => "over",
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct PsdLayer {
31 pub name: String,
32 pub width: u32,
33 pub height: u32,
34 pub opacity: u8,
35 pub blend_mode: PsdBlendMode,
36 pub visible: bool,
37 pub pixels: Vec<[u8; 4]>,
38}
39
40impl PsdLayer {
41 pub fn new_solid(name: &str, width: u32, height: u32, color: [u8; 4]) -> Self {
43 let pixels = vec![color; (width * height) as usize];
44 Self {
45 name: name.to_string(),
46 width,
47 height,
48 opacity: 255,
49 blend_mode: PsdBlendMode::Normal,
50 visible: true,
51 pixels,
52 }
53 }
54
55 pub fn pixel_count(&self) -> usize {
57 self.pixels.len()
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct PsdExport {
64 pub width: u32,
65 pub height: u32,
66 pub layers: Vec<PsdLayer>,
67}
68
69impl PsdExport {
70 pub fn new(width: u32, height: u32) -> Self {
72 Self {
73 width,
74 height,
75 layers: Vec::new(),
76 }
77 }
78
79 pub fn add_layer(&mut self, layer: PsdLayer) {
81 self.layers.push(layer);
82 }
83
84 pub fn layer_count(&self) -> usize {
86 self.layers.len()
87 }
88
89 pub fn visible_layer_count(&self) -> usize {
91 self.layers.iter().filter(|l| l.visible).count()
92 }
93}
94
95pub fn validate_psd(doc: &PsdExport) -> bool {
97 doc.width > 0 && doc.height > 0
98}
99
100pub fn estimate_psd_bytes(doc: &PsdExport) -> usize {
102 let layer_data: usize = doc.layers.iter().map(|l| l.pixel_count() * 4 + 256).sum();
103 26 + layer_data + (doc.width * doc.height) as usize * 4
104}
105
106pub fn psd_metadata_json(doc: &PsdExport) -> String {
108 format!(
109 "{{\"width\":{},\"height\":{},\"layers\":{}}}",
110 doc.width,
111 doc.height,
112 doc.layer_count()
113 )
114}
115
116pub fn find_psd_layer<'a>(doc: &'a PsdExport, name: &str) -> Option<&'a PsdLayer> {
118 doc.layers.iter().find(|l| l.name == name)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn sample_doc() -> PsdExport {
126 let mut doc = PsdExport::new(128, 128);
127 doc.add_layer(PsdLayer::new_solid(
128 "Background",
129 128,
130 128,
131 [200, 200, 200, 255],
132 ));
133 let mut fg = PsdLayer::new_solid("Foreground", 128, 128, [100, 0, 0, 200]);
134 fg.blend_mode = PsdBlendMode::Multiply;
135 doc.add_layer(fg);
136 doc
137 }
138
139 #[test]
140 fn test_layer_count() {
141 assert_eq!(sample_doc().layer_count(), 2);
143 }
144
145 #[test]
146 fn test_visible_layer_count() {
147 assert_eq!(sample_doc().visible_layer_count(), 2);
149 }
150
151 #[test]
152 fn test_validate_valid() {
153 assert!(validate_psd(&sample_doc()));
155 }
156
157 #[test]
158 fn test_blend_mode_fourcc() {
159 assert_ne!(
161 PsdBlendMode::Normal.fourcc(),
162 PsdBlendMode::Multiply.fourcc()
163 );
164 }
165
166 #[test]
167 fn test_estimate_bytes_positive() {
168 assert!(estimate_psd_bytes(&sample_doc()) > 0);
170 }
171
172 #[test]
173 fn test_metadata_json() {
174 let json = psd_metadata_json(&sample_doc());
176 assert!(json.contains("layers"));
177 }
178
179 #[test]
180 fn test_find_psd_layer() {
181 let doc = sample_doc();
183 assert!(find_psd_layer(&doc, "Background").is_some());
184 assert!(find_psd_layer(&doc, "None").is_none());
185 }
186
187 #[test]
188 fn test_pixel_count() {
189 let l = PsdLayer::new_solid("L", 16, 16, [0; 4]);
191 assert_eq!(l.pixel_count(), 256);
192 }
193}