1use std::fs::File;
30use std::io::{BufReader, ErrorKind, Read, Seek, SeekFrom};
31use std::path::Path;
32use std::sync::Arc;
33
34use image::metadata::Orientation;
35use image::{DynamicImage, RgbaImage};
36use moxcms::{ColorProfile, Layout, TransformExecutor, TransformOptions};
37
38pub const CHUNK_BYTES: u64 = 8 << 20;
40
41const MAX_SIDE_PSD: u32 = 30_000;
42const MAX_SIDE_PSB: u32 = 300_000;
43const MAX_ICC_BYTES: u32 = 64 << 20;
45const MAX_EXIF_BYTES: u32 = 1 << 20;
46
47pub fn handles(ext: &str) -> bool {
49 matches!(ext, "psd" | "psdt" | "psb")
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum PsdError {
55 NoComposite,
57 Compression(u16),
59 Mode { mode: u16, depth: u16 },
61 Damaged(String),
63}
64
65impl std::fmt::Display for PsdError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 PsdError::NoComposite => write!(f, "saved without a merged image"),
69 PsdError::Compression(n) => write!(f, "merged image compression {n} is not handled"),
70 PsdError::Mode { mode, depth } => write!(f, "colour mode {mode} at {depth} bits is not handled"),
71 PsdError::Damaged(why) => write!(f, "damaged file: {why}"),
72 }
73 }
74}
75
76impl std::error::Error for PsdError {}
77
78fn damaged(why: impl Into<String>) -> PsdError {
79 PsdError::Damaged(why.into())
80}
81
82fn io(e: std::io::Error) -> PsdError {
83 if e.kind() == ErrorKind::UnexpectedEof { damaged("the file ends early") } else { damaged(e.to_string()) }
84}
85
86pub fn dimensions(path: &Path) -> Option<(u32, u32)> {
89 if let Ok(c) = Composite::open(path) {
90 return Some(c.size());
91 }
92 let mut src = Source::open(path).ok()?;
93 let h = Header::read(&mut src).ok()?;
94 Some((h.width, h.height))
95}
96
97#[derive(Clone, Copy, PartialEq, Eq, Debug)]
98enum Mode {
99 Bitmap,
100 Gray,
101 Indexed,
102 Rgb,
103 Cmyk,
104 Duotone,
105 Lab,
106}
107
108impl Mode {
109 fn channels(self) -> usize {
111 match self {
112 Mode::Bitmap | Mode::Gray | Mode::Indexed | Mode::Duotone => 1,
113 Mode::Rgb | Mode::Lab => 3,
114 Mode::Cmyk => 4,
115 }
116 }
117}
118
119struct Source {
120 r: BufReader<File>,
121 len: u64,
122}
123
124impl Source {
125 fn open(path: &Path) -> Result<Self, PsdError> {
126 let file = File::open(path).map_err(io)?;
127 let len = file.metadata().map_err(io)?.len();
128 Ok(Source { r: BufReader::new(file), len })
129 }
130
131 fn pos(&mut self) -> Result<u64, PsdError> {
132 self.r.stream_position().map_err(io)
133 }
134
135 fn seek(&mut self, to: u64) -> Result<(), PsdError> {
136 self.r.seek(SeekFrom::Start(to)).map_err(io).map(|_| ())
137 }
138
139 fn skip(&mut self, n: u64) -> Result<(), PsdError> {
140 let to = self.pos()?.saturating_add(n);
141 self.seek(to)
142 }
143
144 fn array<const N: usize>(&mut self) -> Result<[u8; N], PsdError> {
145 let mut b = [0; N];
146 self.r.read_exact(&mut b).map_err(io)?;
147 Ok(b)
148 }
149
150 fn u16(&mut self) -> Result<u16, PsdError> {
151 self.array().map(u16::from_be_bytes)
152 }
153
154 fn u32(&mut self) -> Result<u32, PsdError> {
155 self.array().map(u32::from_be_bytes)
156 }
157
158 fn length(&mut self, psb: bool) -> Result<u64, PsdError> {
160 if psb { self.array().map(u64::from_be_bytes) } else { self.u32().map(u64::from) }
161 }
162
163 fn bytes(&mut self, n: u64) -> Result<Vec<u8>, PsdError> {
165 let pos = self.pos()?;
166 if pos.saturating_add(n) > self.len {
167 return Err(damaged("a section runs past the end of the file"));
168 }
169 let mut b = vec![0; n as usize];
170 self.r.read_exact(&mut b).map_err(io)?;
171 Ok(b)
172 }
173}
174
175struct Header {
176 psb: bool,
177 channels: u16,
178 height: u32,
179 width: u32,
180 depth: u16,
181 mode: u16,
182}
183
184impl Header {
185 fn read(src: &mut Source) -> Result<Self, PsdError> {
186 if &src.array::<4>()? != b"8BPS" {
187 return Err(damaged("not a Photoshop document"));
188 }
189 let psb = match src.u16()? {
190 1 => false,
191 2 => true,
192 v => return Err(damaged(format!("unknown version {v}"))),
193 };
194 src.array::<6>()?;
195 let (channels, height, width, depth, mode) = (src.u16()?, src.u32()?, src.u32()?, src.u16()?, src.u16()?);
196 let max = if psb { MAX_SIDE_PSB } else { MAX_SIDE_PSD };
197 if width == 0 || height == 0 || width > max || height > max {
198 return Err(damaged(format!("impossible size {width} x {height}")));
199 }
200 if !(1..=56).contains(&channels) {
201 return Err(damaged(format!("impossible channel count {channels}")));
202 }
203 Ok(Header { psb, channels, height, width, depth, mode })
204 }
205
206 fn colour(&self) -> Result<Mode, PsdError> {
207 let refused = PsdError::Mode { mode: self.mode, depth: self.depth };
208 let mode = match (self.mode, self.depth) {
209 (0, 1) => Mode::Bitmap,
210 (1, 8 | 16 | 32) => Mode::Gray,
211 (2, 8) => Mode::Indexed,
212 (3, 8 | 16 | 32) => Mode::Rgb,
213 (4, 8 | 16) => Mode::Cmyk,
214 (8, 8 | 16) => Mode::Duotone,
215 (9, 8 | 16) => Mode::Lab,
216 _ => return Err(refused),
217 };
218 Ok(mode)
219 }
220}
221
222#[derive(Default)]
224struct Resources {
225 icc: Option<Vec<u8>>,
226 transparent_index: Option<u8>,
227 orientation: Option<Orientation>,
228 no_merged_data: bool,
229}
230
231impl Resources {
232 fn read(src: &mut Source, end: u64) -> Result<Self, PsdError> {
233 let mut out = Resources::default();
234 while src.pos()? + 12 <= end {
235 src.array::<4>()?;
237 let id = src.u16()?;
238 let name = src.array::<1>()?[0] as u64;
239 let name_rest = name + 1 - (name % 2);
241 src.skip(name_rest)?;
242 let size = src.u32()?;
243 let start = src.pos()?;
244 let next = start.saturating_add(u64::from(size) + u64::from(size % 2));
245 if next > end + 1 {
246 return Err(damaged("an image resource runs past its section"));
247 }
248 match id {
249 1039 if size <= MAX_ICC_BYTES => out.icc = Some(src.bytes(size.into())?),
250 1047 if size >= 2 => out.transparent_index = u8::try_from(src.u16()?).ok(),
251 1057 if size >= 5 => {
252 src.u32()?;
253 out.no_merged_data = src.array::<1>()?[0] == 0;
254 }
255 1058 if size <= MAX_EXIF_BYTES => {
256 let data = src.bytes(size.into())?;
257 let tiff = data.strip_prefix(b"Exif\0\0").unwrap_or(&data);
258 out.orientation = crate::exif::orientation(tiff).and_then(|v| u8::try_from(v).ok()).and_then(Orientation::from_exif);
259 }
260 _ => {}
261 }
262 src.seek(next)?;
263 }
264 Ok(out)
265 }
266}
267
268fn merged_transparency(src: &mut Source, psb: bool, end: u64) -> Result<bool, PsdError> {
271 if src.pos()? + if psb { 8 } else { 4 } > end {
272 return Ok(false);
273 }
274 let info = src.length(psb)?;
275 let start = src.pos()?;
276 if start.saturating_add(info) > end {
277 return Err(damaged("the layer info runs past its section"));
278 }
279 let mut alpha = info >= 2 && (src.u16()? as i16) < 0;
280 src.seek(start + info)?;
281 if src.pos()? + 4 > end {
282 return Ok(alpha);
283 }
284 let mask = u64::from(src.u32()?);
285 src.skip(mask)?;
286 while src.pos()? + 12 <= end {
289 let mut sig = src.array::<4>()?;
290 if &sig != b"8BIM" && &sig != b"8B64" {
291 let back = src.pos()? - 2;
293 src.seek(back)?;
294 sig = src.array::<4>()?;
295 if &sig != b"8BIM" && &sig != b"8B64" {
296 break;
297 }
298 }
299 let key = src.array::<4>()?;
300 let long = psb && matches!(&key, b"LMsk" | b"Lr16" | b"Lr32" | b"Layr" | b"Mt16" | b"Mt32" | b"Mtrn" | b"Alph" | b"FMsk" | b"lnk2" | b"FEid" | b"FXid" | b"PxSD");
301 let len = src.length(long)?;
302 let at = src.pos()?;
303 match &key {
304 b"Mtrn" | b"Mt16" | b"Mt32" => alpha = true,
305 b"Lr16" | b"Lr32" if len >= 2 => alpha |= (src.u16()? as i16) < 0,
306 _ => {}
307 }
308 src.seek(at.saturating_add(len).saturating_add(len % 2))?;
309 }
310 Ok(alpha)
311}
312
313enum Rows {
315 Raw { start: u64 },
316 Rle { offsets: Vec<u64> },
318}
319
320pub struct Composite {
322 file: File,
323 width: usize,
324 height: usize,
325 depth: u16,
326 mode: Mode,
327 alpha: bool,
328 row_bytes: usize,
330 rows: Rows,
331 icc: Option<Vec<u8>>,
332 palette: Vec<u8>,
334 transparent_index: Option<u8>,
335 orientation: Orientation,
336}
337
338impl Composite {
339 pub fn open(path: &Path) -> Result<Self, PsdError> {
343 let mut src = Source::open(path)?;
344 let h = Header::read(&mut src)?;
345 let mode = h.colour()?;
346 let colour_len = u64::from(src.u32()?);
347 let palette = if mode == Mode::Indexed {
348 if colour_len != 768 {
349 return Err(damaged("an indexed image without its colour table"));
350 }
351 src.bytes(768)?
352 } else {
353 src.skip(colour_len)?;
354 Vec::new()
355 };
356 let resources_len = u64::from(src.u32()?);
357 let resources_end = src.pos()?.saturating_add(resources_len);
358 if resources_end > src.len {
359 return Err(damaged("the image resources run past the end of the file"));
360 }
361 let resources = Resources::read(&mut src, resources_end)?;
362 src.seek(resources_end)?;
363 let layers_len = src.length(h.psb)?;
364 let layers_end = src.pos()?.saturating_add(layers_len);
365 if layers_end > src.len {
366 return Err(damaged("the layer section runs past the end of the file"));
367 }
368 let alpha = merged_transparency(&mut src, h.psb, layers_end)?;
369 src.seek(layers_end)?;
370 if resources.no_merged_data || layers_end == src.len {
373 return Err(PsdError::NoComposite);
374 }
375 let compression = src.u16()?;
376 if compression > 1 {
377 return Err(PsdError::Compression(compression));
378 }
379 let needed = mode.channels();
380 let all = usize::from(h.channels);
381 if all < needed {
382 return Err(damaged(format!("{all} channels where {needed} are needed")));
383 }
384 let alpha = alpha && all > needed;
385 let used = needed + usize::from(alpha);
386 let (width, height) = (h.width as usize, h.height as usize);
387 let row_bytes = if h.depth == 1 { width.div_ceil(8) } else { width * usize::from(h.depth) / 8 };
388 let rest = src.len - src.pos()?;
389 let rows = if compression == 0 {
390 let total = (row_bytes as u64).saturating_mul(height as u64).saturating_mul(all as u64);
391 if total > rest {
392 return Err(damaged("the file ends before the merged image does"));
393 }
394 Rows::Raw { start: src.pos()? }
395 } else {
396 let entry = if h.psb { 4u64 } else { 2 };
397 let table = (height as u64).saturating_mul(all as u64).saturating_mul(entry);
398 if table > rest {
399 return Err(damaged("the file ends inside the row table"));
400 }
401 let data = src.pos()? + table;
402 let longest = 2 * row_bytes as u64 + 16;
403 let mut offsets = Vec::with_capacity((height + 1) * used);
404 let mut at = data;
405 for channel in 0..all {
406 for _ in 0..height {
407 let n = if h.psb { u64::from(src.u32()?) } else { u64::from(src.u16()?) };
408 if n > longest {
409 return Err(damaged("a compressed row longer than any row can be"));
410 }
411 if channel < used {
412 offsets.push(at);
413 }
414 at += n;
415 }
416 if channel < used {
417 offsets.push(at);
418 }
419 }
420 if at > src.len {
421 return Err(damaged("the file ends before the merged image does"));
422 }
423 Rows::Rle { offsets }
424 };
425 let file = src.r.into_inner();
426 Ok(Composite {
427 file,
428 width,
429 height,
430 depth: h.depth,
431 mode,
432 alpha,
433 row_bytes,
434 rows,
435 icc: resources.icc,
436 palette,
437 transparent_index: resources.transparent_index,
438 orientation: resources.orientation.unwrap_or(Orientation::NoTransforms),
439 })
440 }
441
442 pub fn size(&self) -> (u32, u32) {
444 let (w, h) = (self.width as u32, self.height as u32);
445 if self.swaps_sides() { (h, w) } else { (w, h) }
446 }
447
448 fn swaps_sides(&self) -> bool {
449 matches!(self.orientation, Orientation::Rotate90 | Orientation::Rotate270 | Orientation::Rotate90FlipH | Orientation::Rotate270FlipH)
450 }
451
452 fn to_file_rect(&self, rect: [u32; 4]) -> [usize; 4] {
455 let (uw, uh) = self.size();
456 let [ux0, uy0, ux1, uy1] = [rect[0].min(uw), rect[1].min(uh), rect[2].min(uw), rect[3].min(uh)].map(|v| v as usize);
457 let (sw, sh) = (self.width, self.height);
458 let map = |u: usize, v: usize| match self.orientation {
459 Orientation::NoTransforms => (u, v),
460 Orientation::Rotate90 => (v, sh - u),
461 Orientation::Rotate180 => (sw - u, sh - v),
462 Orientation::Rotate270 => (sw - v, u),
463 Orientation::FlipHorizontal => (sw - u, v),
464 Orientation::FlipVertical => (u, sh - v),
465 Orientation::Rotate90FlipH => (v, u),
466 Orientation::Rotate270FlipH => (sw - v, sh - u),
467 };
468 let (a, b) = (map(ux0, uy0), map(ux1, uy1));
469 [a.0.min(b.0), a.1.min(b.1), a.0.max(b.0), a.1.max(b.1)]
470 }
471
472 fn upright(&self, img: RgbaImage) -> RgbaImage {
473 if self.orientation == Orientation::NoTransforms {
474 return img;
475 }
476 let mut d = DynamicImage::ImageRgba8(img);
477 d.apply_orientation(self.orientation);
478 d.into_rgba8()
479 }
480
481 pub fn read_rects(&mut self, rects: &[[u32; 4]]) -> Result<Vec<RgbaImage>, PsdError> {
486 let spans: Vec<[usize; 4]> = rects.iter().map(|r| self.to_file_rect(*r)).collect();
487 let mut out: Vec<RgbaImage> = spans.iter().map(|s| RgbaImage::new((s[2] - s[0]) as u32, (s[3] - s[1]) as u32)).collect();
488 let (mut x0, mut y0, mut x1, mut y1) = (usize::MAX, usize::MAX, 0, 0);
489 for s in spans.iter().filter(|s| s[2] > s[0] && s[3] > s[1]) {
490 (x0, y0, x1, y1) = (x0.min(s[0]), y0.min(s[1]), x1.max(s[2]), y1.max(s[3]));
491 }
492 if x1 > x0 {
493 self.for_each_row([x0, y0, x1, y1], 1, |y, row| {
494 for (s, img) in spans.iter().zip(&mut out) {
495 if (s[1]..s[3]).contains(&y) && s[2] > s[0] {
496 let width = (s[2] - s[0]) * 4;
497 let at = (y - s[1]) * width;
498 let from = (s[0] - x0) * 4;
499 let target: &mut [u8] = img;
500 target[at..at + width].copy_from_slice(&row[from..from + width]);
501 }
502 }
503 })?;
504 }
505 Ok(out.into_iter().map(|img| self.upright(img)).collect())
506 }
507
508 pub fn read_scaled(&mut self, rect: [u32; 4], long: u32) -> Result<RgbaImage, PsdError> {
515 let [x0, y0, x1, y1] = self.to_file_rect(rect);
516 let (sw, sh) = (x1 - x0, y1 - y0);
517 if sw == 0 || sh == 0 {
518 return Err(damaged("an empty part of the picture was asked for"));
519 }
520 let scale = (f64::from(long.max(1)) / sw.max(sh) as f64).min(1.0);
521 let ow = ((sw as f64 * scale).round() as usize).clamp(1, sw);
522 let oh = ((sh as f64 * scale).round() as usize).clamp(1, sh);
523 let stride = (sh / oh / 3).max(1);
524 let cols: Vec<u32> = (0..sw).map(|x| (x * ow / sw) as u32).collect();
526 let mut weight = vec![0u64; ow];
527 for &c in &cols {
528 weight[c as usize] += 1;
529 }
530 let mut sums = vec![[0u64; 4]; ow];
532 let mut out = RgbaImage::new(ow as u32, oh as u32);
533 let mut current = 0usize;
534 let mut rows_in = 0u64;
535 let flush = |out: &mut RgbaImage, sums: &mut [[u64; 4]], at: usize, rows: u64| {
536 if rows == 0 {
537 return;
538 }
539 let buf: &mut [u8] = out;
540 let row = &mut buf[at * ow * 4..(at + 1) * ow * 4];
541 for ((px, s), w) in row.as_chunks_mut::<4>().0.iter_mut().zip(sums.iter_mut()).zip(&weight) {
542 match s[3] {
543 0 => {}
544 a => {
545 for c in 0..3 {
546 px[c] = ((s[c] + a / 2) / a) as u8;
547 }
548 let n = w * rows;
549 px[3] = ((a + n / 2) / n) as u8;
550 }
551 }
552 *s = [0; 4];
553 }
554 };
555 self.for_each_row([x0, y0, x1, y1], stride, |y, row| {
556 let at = (y - y0) * oh / sh;
557 if at != current {
558 flush(&mut out, &mut sums, current, rows_in);
559 for gap in current + 1..at {
561 let (done, rest) = out.split_at_mut(gap * ow * 4);
562 rest[..ow * 4].copy_from_slice(&done[(gap - 1) * ow * 4..]);
563 }
564 current = at;
565 rows_in = 0;
566 }
567 for (px, &c) in row.as_chunks::<4>().0.iter().zip(&cols) {
568 let a = u64::from(px[3]);
569 let s = &mut sums[c as usize];
570 s[0] += u64::from(px[0]) * a;
571 s[1] += u64::from(px[1]) * a;
572 s[2] += u64::from(px[2]) * a;
573 s[3] += a;
574 }
575 rows_in += 1;
576 })?;
577 flush(&mut out, &mut sums, current, rows_in);
578 for gap in current + 1..oh {
579 let (done, rest) = out.split_at_mut(gap * ow * 4);
580 rest[..ow * 4].copy_from_slice(&done[(gap - 1) * ow * 4..]);
581 }
582 Ok(self.upright(out))
583 }
584
585 fn for_each_row(&mut self, rect: [usize; 4], stride: usize, f: impl FnMut(usize, &[u8])) -> Result<(), PsdError> {
588 match self.depth {
589 16 => self.rows_of::<u16>(rect, stride, f),
590 32 => self.rows_of::<f32>(rect, stride, f),
591 _ => self.rows_of::<u8>(rect, stride, f),
592 }
593 }
594
595 fn rows_of<T: Sample>(&mut self, rect: [usize; 4], stride: usize, mut f: impl FnMut(usize, &[u8])) -> Result<(), PsdError> {
596 let [x0, y0, x1, y1] = rect;
597 let n = x1 - x0;
598 let colour = self.mode.channels();
599 let used = colour + usize::from(self.alpha);
600 let mut chunks: Vec<Chunk> = (0..used).map(|_| Chunk::default()).collect();
601 let mut planes = vec![vec![0u8; self.width * T::BYTES]; used];
603 let mut unpacked = vec![0u8; self.row_bytes];
604 let converter = Converter::<T>::new(self.mode, self.icc.as_deref());
605 let mut rgba = vec![0u8; n * 4];
606 let mut work = Work::<T>::new(n, colour);
607 let mut y = y0;
608 while y < y1 {
609 for (channel, plane) in planes.iter_mut().enumerate() {
610 let raw = self.read_row(&mut chunks[channel], channel, y, y1, stride)?;
611 match &self.rows {
612 Rows::Raw { .. } => unpacked.copy_from_slice(raw),
613 Rows::Rle { .. } => unpack(raw, &mut unpacked)?,
614 }
615 if self.mode == Mode::Bitmap {
616 for (x, p) in plane.iter_mut().enumerate() {
618 *p = if unpacked[x / 8] & (0x80 >> (x % 8)) != 0 { 0 } else { 255 };
619 }
620 } else {
621 plane.copy_from_slice(&unpacked);
622 }
623 }
624 work.load(&planes, x0, n, self.alpha, colour);
625 self.convert(&converter, &mut work, &mut rgba)?;
626 f(y, &rgba);
627 y += stride;
628 }
629 Ok(())
630 }
631
632 fn read_row<'c>(&mut self, chunk: &'c mut Chunk, channel: usize, y: usize, end: usize, stride: usize) -> Result<&'c [u8], PsdError> {
635 let span = |r: usize| -> (u64, u64) {
636 match &self.rows {
637 Rows::Raw { start } => {
638 let rb = self.row_bytes as u64;
639 let at = start + channel as u64 * self.height as u64 * rb + r as u64 * rb;
640 (at, at + rb)
641 }
642 Rows::Rle { offsets } => {
643 let i = channel * (self.height + 1) + r;
644 (offsets[i], offsets[i + 1])
645 }
646 }
647 };
648 if !(chunk.first..chunk.end).contains(&y) {
649 let (from, mut to) = span(y);
650 let mut last = y + 1;
651 while stride == 1 && last < end && span(last).1 - from <= CHUNK_BYTES {
653 to = span(last).1;
654 last += 1;
655 }
656 chunk.first = y;
657 chunk.end = 0;
658 chunk.base = from;
659 chunk.data.resize((to - from) as usize, 0);
660 self.file.seek(SeekFrom::Start(from)).map_err(io)?;
661 self.file.read_exact(&mut chunk.data).map_err(io)?;
662 chunk.end = last;
663 }
664 let (from, to) = span(y);
665 Ok(&chunk.data[(from - chunk.base) as usize..(to - chunk.base) as usize])
666 }
667
668 fn convert<T: Sample>(&self, conv: &Converter<T>, work: &mut Work<T>, rgba: &mut [u8]) -> Result<(), PsdError> {
670 let n = work.alpha.len();
671 if self.alpha && self.mode != Mode::Lab {
674 unmatte(&mut work.colour, &work.alpha, self.mode.channels());
675 }
676 let encoded = conv.transform.is_some();
677 match self.mode {
678 Mode::Rgb => match &conv.transform {
679 Some(t) => t.transform(&work.colour, &mut work.rgb).map_err(|e| damaged(e.to_string()))?,
680 None => work.rgb.copy_from_slice(&work.colour),
681 },
682 Mode::Gray | Mode::Duotone | Mode::Bitmap => match &conv.transform {
683 Some(t) => t.transform(&work.colour, &mut work.rgb).map_err(|e| damaged(e.to_string()))?,
684 None => {
685 for (px, v) in work.rgb.as_chunks_mut::<3>().0.iter_mut().zip(&work.colour) {
686 px.fill(*v);
687 }
688 }
689 },
690 Mode::Indexed => {
691 for (px, v) in work.indexed.as_chunks_mut::<3>().0.iter_mut().zip(&work.colour) {
693 let i = usize::from(v.to_u8());
694 px.copy_from_slice(&[T::from_u8(self.palette[i]), T::from_u8(self.palette[256 + i]), T::from_u8(self.palette[512 + i])]);
695 }
696 match &conv.transform {
697 Some(t) => t.transform(&work.indexed, &mut work.rgb).map_err(|e| damaged(e.to_string()))?,
698 None => work.rgb.copy_from_slice(&work.indexed),
699 }
700 }
701 Mode::Cmyk => match &conv.transform {
702 Some(t) => {
703 for v in &mut work.colour {
705 *v = T::from_unit(1.0 - v.unit());
706 }
707 t.transform(&work.colour, &mut work.rgb).map_err(|e| damaged(e.to_string()))?;
708 }
709 None => {
710 for (px, s) in work.rgb.as_chunks_mut::<3>().0.iter_mut().zip(work.colour.as_chunks::<4>().0) {
711 let k = s[3].unit();
712 px.copy_from_slice(&[T::from_unit(s[0].unit() * k), T::from_unit(s[1].unit() * k), T::from_unit(s[2].unit() * k)]);
713 }
714 }
715 },
716 Mode::Lab => {
717 for (px, s) in work.rgb.as_chunks_mut::<3>().0.iter_mut().zip(work.colour.as_chunks::<3>().0) {
718 let [r, g, b] = lab_to_srgb(s[0].unit() * 100.0, s[1].lab_ab(), s[2].lab_ab());
719 px.copy_from_slice(&[T::from_unit(r), T::from_unit(g), T::from_unit(b)]);
720 }
721 }
722 }
723 let encoded = encoded || self.mode == Mode::Lab;
725 for i in 0..n {
726 let px = &mut rgba[i * 4..i * 4 + 4];
727 for (c, out) in px.iter_mut().take(3).enumerate() {
728 let v = work.rgb[i * 3 + c];
729 *out = if encoded { v.to_u8() } else { v.display() };
730 }
731 px[3] = if self.alpha {
732 work.alpha[i].to_u8()
733 } else if self.mode == Mode::Indexed && self.transparent_index == Some(work.colour[i].to_u8()) {
734 0
735 } else {
736 255
737 };
738 }
739 Ok(())
740 }
741}
742
743#[derive(Default)]
745struct Chunk {
746 first: usize,
747 end: usize,
748 base: u64,
749 data: Vec<u8>,
750}
751
752fn unpack(src: &[u8], dst: &mut [u8]) -> Result<(), PsdError> {
754 let (mut i, mut o) = (0, 0);
755 while o < dst.len() {
756 let &n = src.get(i).ok_or_else(|| damaged("a compressed row ends early"))?;
757 i += 1;
758 if n == 128 {
759 continue;
760 }
761 if n < 128 {
762 let len = usize::from(n) + 1;
763 let run = src.get(i..i + len).ok_or_else(|| damaged("a compressed row ends early"))?;
764 dst.get_mut(o..o + len).ok_or_else(|| damaged("a compressed row is too long"))?.copy_from_slice(run);
765 i += len;
766 o += len;
767 } else {
768 let len = 257 - usize::from(n);
769 let &v = src.get(i).ok_or_else(|| damaged("a compressed row ends early"))?;
770 i += 1;
771 dst.get_mut(o..o + len).ok_or_else(|| damaged("a compressed row is too long"))?.fill(v);
772 o += len;
773 }
774 }
775 Ok(())
776}
777
778fn unmatte<T: Sample>(colour: &mut [T], alpha: &[T], channels: usize) {
780 for (px, a) in colour.chunks_exact_mut(channels).zip(alpha) {
781 let a = a.unit();
782 if a > 0.0 && a < 1.0 {
783 for v in px {
784 *v = T::from_unit((v.unit() - (1.0 - a)) / a);
785 }
786 }
787 }
788}
789
790fn lab_to_srgb(l: f32, a: f32, b: f32) -> [f32; 3] {
792 let fy = (l + 16.0) / 116.0;
793 let (fx, fz) = (fy + a / 500.0, fy - b / 200.0);
794 let inv = |t: f32| if t > 6.0 / 29.0 { t * t * t } else { 3.0 * (6.0f32 / 29.0).powi(2) * (t - 4.0 / 29.0) };
795 let (x, y, z) = (0.9642 * inv(fx), inv(fy), 0.8249 * inv(fz));
796 let r = 3.133856 * x - 1.616867 * y - 0.490615 * z;
798 let g = -0.978768 * x + 1.916142 * y + 0.033454 * z;
799 let bl = 0.071945 * x - 0.228991 * y + 1.405243 * z;
800 [srgb_encode(r), srgb_encode(g), srgb_encode(bl)]
801}
802
803fn srgb_encode(v: f32) -> f32 {
804 let v = v.clamp(0.0, 1.0);
805 if v <= 0.003_130_8 { 12.92 * v } else { 1.055 * v.powf(1.0 / 2.4) - 0.055 }
806}
807
808type Executor<T> = Arc<dyn TransformExecutor<T> + Send + Sync>;
809
810trait Sample: Copy + Default + 'static {
812 const BYTES: usize;
813 fn be(b: &[u8]) -> Self;
814 fn unit(self) -> f32;
816 fn from_unit(v: f32) -> Self;
817 fn from_u8(v: u8) -> Self;
818 fn to_u8(self) -> u8;
820 fn display(self) -> u8 {
822 self.to_u8()
823 }
824 fn lab_ab(self) -> f32;
826 fn executor(profile: &ColorProfile, src: Layout, dst: &ColorProfile) -> Option<Executor<Self>>;
827}
828
829impl Sample for u8 {
830 const BYTES: usize = 1;
831 fn be(b: &[u8]) -> Self {
832 b[0]
833 }
834 fn unit(self) -> f32 {
835 f32::from(self) / 255.0
836 }
837 fn from_unit(v: f32) -> Self {
838 (v.clamp(0.0, 1.0) * 255.0).round() as u8
839 }
840 fn from_u8(v: u8) -> Self {
841 v
842 }
843 fn to_u8(self) -> u8 {
844 self
845 }
846 fn lab_ab(self) -> f32 {
847 f32::from(self) - 128.0
848 }
849 fn executor(profile: &ColorProfile, src: Layout, dst: &ColorProfile) -> Option<Executor<Self>> {
850 profile.create_transform_8bit(src, dst, Layout::Rgb, TransformOptions::default()).ok().map(|t| t as Executor<Self>)
851 }
852}
853
854impl Sample for u16 {
855 const BYTES: usize = 2;
856 fn be(b: &[u8]) -> Self {
857 u16::from_be_bytes([b[0], b[1]])
858 }
859 fn unit(self) -> f32 {
860 f32::from(self) / 65535.0
861 }
862 fn from_unit(v: f32) -> Self {
863 (v.clamp(0.0, 1.0) * 65535.0).round() as u16
864 }
865 fn from_u8(v: u8) -> Self {
866 u16::from(v) * 257
867 }
868 fn to_u8(self) -> u8 {
869 ((u32::from(self) * 255 + 32767) / 65535) as u8
870 }
871 fn lab_ab(self) -> f32 {
872 f32::from(self) / 256.0 - 128.0
873 }
874 fn executor(profile: &ColorProfile, src: Layout, dst: &ColorProfile) -> Option<Executor<Self>> {
875 profile.create_transform_16bit(src, dst, Layout::Rgb, TransformOptions::default()).ok()
876 }
877}
878
879impl Sample for f32 {
880 const BYTES: usize = 4;
881 fn be(b: &[u8]) -> Self {
882 f32::from_be_bytes([b[0], b[1], b[2], b[3]])
883 }
884 fn unit(self) -> f32 {
885 self
886 }
887 fn from_unit(v: f32) -> Self {
888 v
889 }
890 fn from_u8(v: u8) -> Self {
891 f32::from(v) / 255.0
892 }
893 fn to_u8(self) -> u8 {
894 (self.clamp(0.0, 1.0) * 255.0).round() as u8
896 }
897 fn display(self) -> u8 {
899 (srgb_encode(self) * 255.0).round() as u8
900 }
901 fn lab_ab(self) -> f32 {
902 self * 255.0 - 128.0
903 }
904 fn executor(profile: &ColorProfile, src: Layout, dst: &ColorProfile) -> Option<Executor<Self>> {
905 profile.create_transform_f32(src, dst, Layout::Rgb, TransformOptions::default()).ok()
906 }
907}
908
909struct Converter<T: Sample> {
912 transform: Option<Executor<T>>,
913}
914
915impl<T: Sample> Converter<T> {
916 fn new(mode: Mode, icc: Option<&[u8]>) -> Self {
917 let layout = match mode {
918 Mode::Rgb | Mode::Indexed => Some(Layout::Rgb),
919 Mode::Gray => Some(Layout::Gray),
920 Mode::Cmyk => Some(Layout::Rgba),
922 Mode::Lab | Mode::Duotone | Mode::Bitmap => None,
923 };
924 let space = match mode {
925 Mode::Rgb | Mode::Indexed => moxcms::DataColorSpace::Rgb,
926 Mode::Gray => moxcms::DataColorSpace::Gray,
927 _ => moxcms::DataColorSpace::Cmyk,
928 };
929 let transform = layout.zip(icc).and_then(|(layout, icc)| {
930 let profile = ColorProfile::new_from_slice(icc).ok()?;
931 (profile.color_space == space).then(|| T::executor(&profile, layout, &ColorProfile::new_srgb())).flatten()
932 });
933 Converter { transform }
934 }
935}
936
937struct Work<T> {
939 colour: Vec<T>,
940 alpha: Vec<T>,
941 indexed: Vec<T>,
942 rgb: Vec<T>,
943}
944
945impl<T: Sample> Work<T> {
946 fn new(n: usize, channels: usize) -> Self {
947 Work { colour: vec![T::default(); n * channels], alpha: vec![T::default(); n], indexed: vec![T::default(); n * 3], rgb: vec![T::default(); n * 3] }
948 }
949
950 fn load(&mut self, planes: &[Vec<u8>], x0: usize, n: usize, alpha: bool, channels: usize) {
951 for (c, plane) in planes.iter().take(channels).enumerate() {
952 let samples = plane[x0 * T::BYTES..(x0 + n) * T::BYTES].chunks_exact(T::BYTES);
953 for (i, s) in samples.enumerate() {
954 self.colour[i * channels + c] = T::be(s);
955 }
956 }
957 if alpha {
958 let samples = planes[channels][x0 * T::BYTES..(x0 + n) * T::BYTES].chunks_exact(T::BYTES);
959 for (v, s) in self.alpha.iter_mut().zip(samples) {
960 *v = T::be(s);
961 }
962 }
963 }
964}
965
966#[cfg(test)]
967mod tests;