Skip to main content

falcon_library/
psd.rs

1//! The merged (composite) image of Photoshop PSD, PSDT and PSB files, read
2//! from the public Adobe file format specification: header, colour mode
3//! data, image resources (ICC profile, EXIF orientation, version info,
4//! indexed transparency), the layer section (skipped, but for whether the
5//! merged image has transparency) and the image data, raw or RLE.
6//!
7//! Nothing is read into memory whole. The row table gives each row's place
8//! in the file, so any part of the picture is decoded on its own, row by
9//! row. Beyond the pixels the caller asks for, memory is at most
10//! [`CHUNK_BYTES`] of compressed data and a decoded row per channel in use
11//! (five at most), and the row table: 8 bytes per row per channel in use,
12//! 12 MB for a 300000-row PSB.
13//!
14//! Colour goes to 8-bit sRGB:
15//! - With an embedded ICC profile, RGB, grayscale, CMYK and indexed pixels
16//!   go through it (`moxcms`).
17//! - Without one, RGB and gray are taken as sRGB (32-bit samples as linear
18//!   sRGB), and CMYK goes through the naive `R = (1 - C)(1 - K)`.
19//! - Lab is converted from its D50 definition, and duotone shows the gray
20//!   image the file stores (its ink curves are undocumented). Bitmap is
21//!   black and white.
22//!
23//! Only the first extra channel is alpha, and only when the file says the
24//! merged image has transparency: a negative layer count or a `Mtrn`,
25//! `Mt16` or `Mt32` block in the layer section. Any other extra channel is
26//! a saved selection or a spot colour. Photoshop stores such a merged image
27//! blended over white; the white is taken back out.
28
29use 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
38/// Compressed bytes read from the file at a time, per channel.
39pub const CHUNK_BYTES: u64 = 8 << 20;
40
41const MAX_SIDE_PSD: u32 = 30_000;
42const MAX_SIDE_PSB: u32 = 300_000;
43/// Larger resources than these are ignored, not read.
44const MAX_ICC_BYTES: u32 = 64 << 20;
45const MAX_EXIF_BYTES: u32 = 1 << 20;
46
47/// Whether `ext` (lowercase, no dot) is a Photoshop document.
48pub fn handles(ext: &str) -> bool {
49    matches!(ext, "psd" | "psdt" | "psb")
50}
51
52/// Why a file's merged image can't be shown.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum PsdError {
55    /// Saved without one (Maximize Compatibility off).
56    NoComposite,
57    /// Zip compression, which Photoshop doesn't use for the merged image.
58    Compression(u16),
59    /// A colour mode and depth that aren't handled (multichannel, say).
60    Mode { mode: u16, depth: u16 },
61    /// Not a PSD, truncated, or a part of it contradicts the rest.
62    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
86/// The image's size after its orientation, from the merged image when it
87/// can be read, else from the header alone.
88pub 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    /// Channels holding the colour.
110    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    /// A section length: 32 bits in a PSD, 64 in a PSB.
159    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    /// `n` bytes from here; the length is checked against the file first.
164    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/// What the image resources section says about the merged image.
223#[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            // The signature is 8BIM in nearly every block, but others occur.
236            src.array::<4>()?;
237            let id = src.u16()?;
238            let name = src.array::<1>()?[0] as u64;
239            // The Pascal name, with its length byte, padded to even.
240            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
268/// Whether the layer section (starting after its own length, ending at
269/// `end`) says the merged image has transparency.
270fn 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    // Additional layer information: 16- and 32-bit files keep their layer
287    // info here, and the Mt blocks flag the merged transparency.
288    while src.pos()? + 12 <= end {
289        let mut sig = src.array::<4>()?;
290        if &sig != b"8BIM" && &sig != b"8B64" {
291            // Some writers pad blocks to four bytes rather than two.
292            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
313/// Where each row of each channel in use lies in the file.
314enum Rows {
315    Raw { start: u64 },
316    /// `(height + 1)` offsets per channel in use.
317    Rle { offsets: Vec<u64> },
318}
319
320/// An open merged image. Reading it doesn't hold the file in memory.
321pub struct Composite {
322    file: File,
323    width: usize,
324    height: usize,
325    depth: u16,
326    mode: Mode,
327    alpha: bool,
328    /// Bytes in a row of one channel in the file.
329    row_bytes: usize,
330    rows: Rows,
331    icc: Option<Vec<u8>>,
332    /// The colour table: 256 reds, 256 greens, 256 blues.
333    palette: Vec<u8>,
334    transparent_index: Option<u8>,
335    orientation: Orientation,
336}
337
338impl Composite {
339    /// Reads the structure of the file at `path`, checking every length
340    /// against the file's size; pixels are read when asked for, so a huge
341    /// header on a small file is refused here.
342    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        // Maximize Compatibility off: a blank stands in for the merged
371        // image, and the version info says so. Some writers leave it out.
372        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    /// The picture's size in pixels, upright.
443    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    /// The rectangle `[x0, y0, x1, y1]` of the upright picture in the file's
453    /// own pixels, clamped to the picture. Empty if outside it.
454    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    /// The rectangles `[x0, y0, x1, y1]` of the upright picture at full
482    /// resolution as straight-alpha RGBA. Every row of the file is decoded
483    /// once however many rectangles it crosses; memory is the rectangles'
484    /// pixels at 4 bytes each, plus what the module notes describe.
485    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    /// The rectangle `[x0, y0, x1, y1]` of the upright picture, box-filtered
509    /// down so its long side is at most `long` pixels; never enlarged. The
510    /// memory beyond the module notes' is 40 bytes per pixel of one row of
511    /// the result, and 4 per pixel of the result. Reduced six times or more,
512    /// rows are skipped so a huge file stays quick, leaving at least three
513    /// rows averaged into each row of the result.
514    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        // The output column of each source column, and how many land in it.
525        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        // Premultiplied sums, so transparent pixels lend no colour.
531        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                // A row a stride skipped over repeats the one before.
560                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    /// Calls `f` with each `stride`-th row of `rect` (in the file's own
586    /// pixels) as straight-alpha 8-bit sRGB.
587    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        // A decoded row per channel: bitmap rows are expanded to bytes.
602        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                    // 1 is black.
617                    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    /// The bytes of row `y` of a channel in use, from its chunk, which is
633    /// read when it doesn't hold them.
634    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            // Rows being skipped aren't read ahead.
652            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    /// Turns the loaded samples in `work` into RGBA.
669    fn convert<T: Sample>(&self, conv: &Converter<T>, work: &mut Work<T>, rgba: &mut [u8]) -> Result<(), PsdError> {
670        let n = work.alpha.len();
671        // The file's samples are blended over white; in every mode but Lab
672        // full-scale is white (CMYK is stored inverted: full scale, no ink).
673        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                // Only 8-bit: the sample is the index.
692                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                    // ICC CMYK counts ink; the file counts its absence.
704                    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        // Lab is already encoded for display.
724        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/// Compressed bytes of a run of rows of one channel.
744#[derive(Default)]
745struct Chunk {
746    first: usize,
747    end: usize,
748    base: u64,
749    data: Vec<u8>,
750}
751
752/// PackBits: `dst` must come out exactly full.
753fn 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
778/// Takes the white out of samples blended over it.
779fn 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
790/// A CIE Lab colour (D50) as sRGB samples in 0..1.
791fn 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    // XYZ (D50) to linear sRGB, Bradford adapted.
797    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
810/// A sample of the file's depth.
811trait Sample: Copy + Default + 'static {
812    const BYTES: usize;
813    fn be(b: &[u8]) -> Self;
814    /// In 0..1 (a 32-bit sample may lie outside).
815    fn unit(self) -> f32;
816    fn from_unit(v: f32) -> Self;
817    fn from_u8(v: u8) -> Self;
818    /// Rounded to 8 bits, as it stands.
819    fn to_u8(self) -> u8;
820    /// Rounded to 8 bits for display with no profile to go through.
821    fn display(self) -> u8 {
822        self.to_u8()
823    }
824    /// An `a*` or `b*` value in Lab.
825    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        // NaN becomes 0.
895        (self.clamp(0.0, 1.0) * 255.0).round() as u8
896    }
897    /// 32-bit samples are linear light.
898    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
909/// The embedded profile's transform to sRGB, when the mode uses one and it
910/// is a profile for that mode. A bad profile is treated as none.
911struct 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            // CMYK samples use moxcms's four-channel layout.
921            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
937/// Buffers for one row, interleaved for the colour transform.
938struct 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;