1use crate::reader::{
17 read_float32, read_signature, read_uint16, read_uint32, read_unicode_string_with_length,
18 PsdReader, ReadError, ReadResult,
19};
20use crate::writer::{
21 create_writer, get_writer_buffer, write_float32, write_signature, write_uint16, write_uint32,
22 PsdWriter,
23};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum AseColorType {
28 Global,
30 Spot,
32 Normal,
34}
35
36impl AseColorType {
37 fn from_index(index: u16) -> Option<AseColorType> {
39 match index {
40 0 => Some(AseColorType::Global),
41 1 => Some(AseColorType::Spot),
42 2 => Some(AseColorType::Normal),
43 _ => None,
44 }
45 }
46
47 fn to_index(self) -> u16 {
48 match self {
49 AseColorType::Global => 0,
50 AseColorType::Spot => 1,
51 AseColorType::Normal => 2,
52 }
53 }
54}
55
56#[derive(Debug, Clone, PartialEq)]
58pub enum AseColorValue {
59 Rgb {
61 r: f32,
62 g: f32,
63 b: f32,
64 type_: AseColorType,
65 },
66 Cmyk {
68 c: f32,
69 m: f32,
70 y: f32,
71 k: f32,
72 type_: AseColorType,
73 },
74 Gray { k: f32, type_: AseColorType },
76 Lab {
78 l: f32,
79 a: f32,
80 b: f32,
81 type_: AseColorType,
82 },
83}
84
85#[derive(Debug, Clone, PartialEq)]
87pub struct AseColor {
88 pub name: String,
89 pub color: AseColorValue,
90}
91
92#[derive(Debug, Clone, PartialEq)]
94pub struct AseGroup {
95 pub name: String,
96 pub colors: Vec<AseColor>,
97}
98
99#[derive(Debug, Clone, PartialEq)]
101pub enum AseEntry {
102 Color(AseColor),
103 Group(AseGroup),
104}
105
106#[derive(Debug, Clone, Default, PartialEq)]
108pub struct Ase {
109 pub colors: Vec<AseEntry>,
110}
111
112pub fn read_ase(buffer: &[u8]) -> ReadResult<Ase> {
114 let reader = &mut PsdReader::new(buffer, None, None);
115
116 let signature = read_signature(reader)?; if signature != "ASEF" {
118 return Err(ReadError::StrictViolation("Invalid signature".to_string()));
119 }
120 let version_major = read_uint16(reader)?; let version_minor = read_uint16(reader)?; if version_major != 1 || version_minor != 0 {
123 return Err(ReadError::StrictViolation("Invalid version".to_string()));
124 }
125 let blocks_count = read_uint32(reader)?;
126
127 let mut ase = Ase { colors: Vec::new() };
128 let mut current_group: Option<usize> = None;
131
132 for _ in 0..blocks_count {
133 let type_ = read_uint16(reader)?;
134 let length = read_uint32(reader)? as usize;
135 let end = reader.offset + length;
136
137 match type_ {
138 0x0001 => {
139 let name_length = read_uint16(reader)? as usize;
141 let name = read_unicode_string_with_length(reader, name_length)?;
142 let color_mode = read_signature(reader)?;
143 let color = match color_mode.as_str() {
144 "RGB " => AseColorValue::Rgb {
145 r: read_float32(reader)?,
146 g: read_float32(reader)?,
147 b: read_float32(reader)?,
148 type_: read_color_type(reader)?,
149 },
150 "CMYK" => AseColorValue::Cmyk {
151 c: read_float32(reader)?,
152 m: read_float32(reader)?,
153 y: read_float32(reader)?,
154 k: read_float32(reader)?,
155 type_: read_color_type(reader)?,
156 },
157 "Gray" => AseColorValue::Gray {
158 k: read_float32(reader)?,
159 type_: read_color_type(reader)?,
160 },
161 "LAB " => AseColorValue::Lab {
162 l: read_float32(reader)?,
163 a: read_float32(reader)?,
164 b: read_float32(reader)?,
165 type_: read_color_type(reader)?,
166 },
167 _ => {
168 return Err(ReadError::StrictViolation("Invalid color mode".to_string()))
169 }
170 };
171 let entry = AseColor { name, color };
172 match current_group {
173 Some(gi) => {
174 if let AseEntry::Group(g) = &mut ase.colors[gi] {
175 g.colors.push(entry);
176 }
177 }
178 None => ase.colors.push(AseEntry::Color(entry)),
179 }
180 }
181 0xC001 => {
182 let name_length = read_uint16(reader)? as usize;
184 let name = read_unicode_string_with_length(reader, name_length)?;
185 ase.colors.push(AseEntry::Group(AseGroup {
186 name,
187 colors: Vec::new(),
188 }));
189 current_group = Some(ase.colors.len() - 1);
190 }
191 0xC002 => {
192 current_group = None;
194 }
195 _ => return Err(ReadError::StrictViolation("Invalid block type".to_string())),
196 }
197
198 reader.offset = end;
199 }
200
201 Ok(ase)
202}
203
204fn read_color_type(reader: &mut PsdReader) -> ReadResult<AseColorType> {
205 let index = read_uint16(reader)?;
206 AseColorType::from_index(index)
207 .ok_or_else(|| ReadError::StrictViolation(format!("Invalid color type: {}", index)))
208}
209
210pub fn write_ase(ase: &Ase) -> Vec<u8> {
216 let mut writer = create_writer(4096);
217
218 write_signature(&mut writer, "ASEF");
219 write_uint16(&mut writer, 1); write_uint16(&mut writer, 0); let mut blocks_count: u32 = 0;
224 for entry in &ase.colors {
225 match entry {
226 AseEntry::Color(_) => blocks_count += 1,
227 AseEntry::Group(g) => blocks_count += 2 + g.colors.len() as u32,
228 }
229 }
230 write_uint32(&mut writer, blocks_count);
231
232 for entry in &ase.colors {
233 match entry {
234 AseEntry::Color(c) => write_color_block(&mut writer, c),
235 AseEntry::Group(g) => {
236 write_block(&mut writer, 0xC001, |w| write_name(w, &g.name));
237 for c in &g.colors {
238 write_color_block(&mut writer, c);
239 }
240 write_block(&mut writer, 0xC002, |_| {});
241 }
242 }
243 }
244
245 get_writer_buffer(&writer)
246}
247
248fn write_name(writer: &mut PsdWriter, name: &str) {
249 let units: Vec<u16> = name.encode_utf16().collect();
252 write_uint16(writer, (units.len() + 1) as u16);
253 for u in &units {
254 write_uint16(writer, *u);
255 }
256 write_uint16(writer, 0); }
258
259fn write_color_block(writer: &mut PsdWriter, c: &AseColor) {
260 write_block(writer, 0x0001, |w| {
261 write_name(w, &c.name);
262 match &c.color {
263 AseColorValue::Rgb { r, g, b, type_ } => {
264 write_signature(w, "RGB ");
265 write_float32(w, *r);
266 write_float32(w, *g);
267 write_float32(w, *b);
268 write_uint16(w, type_.to_index());
269 }
270 AseColorValue::Cmyk { c, m, y, k, type_ } => {
271 write_signature(w, "CMYK");
272 write_float32(w, *c);
273 write_float32(w, *m);
274 write_float32(w, *y);
275 write_float32(w, *k);
276 write_uint16(w, type_.to_index());
277 }
278 AseColorValue::Gray { k, type_ } => {
279 write_signature(w, "Gray");
280 write_float32(w, *k);
281 write_uint16(w, type_.to_index());
282 }
283 AseColorValue::Lab { l, a, b, type_ } => {
284 write_signature(w, "LAB ");
285 write_float32(w, *l);
286 write_float32(w, *a);
287 write_float32(w, *b);
288 write_uint16(w, type_.to_index());
289 }
290 }
291 });
292}
293
294fn write_block<F: FnOnce(&mut PsdWriter)>(writer: &mut PsdWriter, type_: u16, func: F) {
296 write_uint16(writer, type_);
297 let length_offset = writer.offset;
298 write_uint32(writer, 0); let start = writer.offset;
300 func(writer);
301 let length = (writer.offset - start) as u32;
302 writer.buffer[length_offset..length_offset + 4].copy_from_slice(&length.to_be_bytes());
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 fn sample() -> Ase {
310 Ase {
311 colors: vec![
312 AseEntry::Color(AseColor {
313 name: "Red".to_string(),
314 color: AseColorValue::Rgb {
315 r: 1.0,
316 g: 0.0,
317 b: 0.0,
318 type_: AseColorType::Global,
319 },
320 }),
321 AseEntry::Group(AseGroup {
322 name: "Grays".to_string(),
323 colors: vec![
324 AseColor {
325 name: "Mid".to_string(),
326 color: AseColorValue::Gray {
327 k: 0.5,
328 type_: AseColorType::Normal,
329 },
330 },
331 AseColor {
332 name: "Cyanish".to_string(),
333 color: AseColorValue::Cmyk {
334 c: 1.0,
335 m: 0.0,
336 y: 0.0,
337 k: 0.0,
338 type_: AseColorType::Spot,
339 },
340 },
341 ],
342 }),
343 AseEntry::Color(AseColor {
344 name: "Lab".to_string(),
345 color: AseColorValue::Lab {
346 l: 50.0,
347 a: 10.0,
348 b: -20.0,
349 type_: AseColorType::Normal,
350 },
351 }),
352 ],
353 }
354 }
355
356 #[test]
357 fn ase_round_trip() {
358 let ase = sample();
359 let bytes = write_ase(&ase);
360 let decoded = read_ase(&bytes).expect("read_ase");
361 assert_eq!(ase, decoded);
362 }
363
364 #[test]
365 fn ase_rejects_bad_signature() {
366 let bytes = b"XXXX\x00\x01\x00\x00\x00\x00\x00\x00";
367 assert!(read_ase(bytes).is_err());
368 }
369
370 fn fixture(sub: &str) -> std::path::PathBuf {
371 let mut p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
372 p.pop();
373 p.pop();
374 p.push("test/ag-psd/test/ase-read");
375 p.push(sub);
376 p.push("src.ase");
377 p
378 }
379
380 #[test]
381 fn ase_decodes_photoshop_fixture() {
382 let path = fixture("from-photoshop");
383 if !path.exists() {
384 eprintln!("ase fixture missing, skipping");
385 return;
386 }
387 let data = std::fs::read(&path).unwrap();
388 let ase = read_ase(&data).expect("decode ase fixture");
389 assert!(!ase.colors.is_empty());
390 match &ase.colors[0] {
392 AseEntry::Color(c) => {
393 assert_eq!(c.name, "#FFCCCC");
394 match &c.color {
395 AseColorValue::Rgb { r, g, b, type_ } => {
396 assert_eq!(*r, 1.0);
397 assert!((*g - 0.79998779).abs() < 1e-4);
398 assert!((*b - 0.79998779).abs() < 1e-4);
399 assert_eq!(*type_, AseColorType::Global);
400 }
401 other => panic!("expected rgb, got {:?}", other),
402 }
403 }
404 other => panic!("expected color entry, got {:?}", other),
405 }
406 }
407
408 #[test]
409 fn ase_fixture_round_trip() {
410 let path = fixture("piratetrousle-dusk");
411 if !path.exists() {
412 eprintln!("ase fixture missing, skipping");
413 return;
414 }
415 let data = std::fs::read(&path).unwrap();
416 let ase = read_ase(&data).expect("decode");
417 let bytes = write_ase(&ase);
419 let again = read_ase(&bytes).expect("re-decode");
420 assert_eq!(ase, again);
421 }
422
423 #[test]
424 fn ase_smoke_header() {
425 let bytes = write_ase(&Ase { colors: vec![] });
426 assert_eq!(&bytes[0..4], b"ASEF");
428 let decoded = read_ase(&bytes).unwrap();
429 assert!(decoded.colors.is_empty());
430 }
431}