image_extras/
sgi.rs

1/*! Decoding of SGI Image File Format (.rgb)
2 *
3 * The SGI Image File format (often referred to as .rgb) is an obsolete
4 * file format which has uncompressed and run-length encoded modes,
5 * supports a variable number of color channels, and both 8-bit and
6 * 16-bit precisions.
7 *
8 * This decoder does not support:
9 * - Images with ≥ 5 color channels (zsize). (While theoretically supported
10 *   by the format, we haven't found any existing images that do this.)
11 *
12 * - Colormaps: Not supported, only the NORMAL=0 mode. The other operations
13 *   (DITHERED=1, SCREEN=2, COLORMAP=3) were obsolete at the time the spec
14 *   was written.
15 *
16 * The format specification does not explain how the alpha channel is to
17 * be interpreted. Existing decoders appear to assume straight alpha, like
18 * PNG.
19 *
20 * Specification:
21 * - <https://web.archive.org/web/20010413154909/https://reality.sgi.com/grafica/sgiimage.html>
22 * - <https://www.fileformat.info/format/sgiimage/egff.htm>
23 *
24 */
25
26use core::ffi::CStr;
27use std::fmt;
28use std::io::BufRead;
29
30use image::error::{
31    DecodingError, ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind,
32};
33use image::{ColorType, ExtendedColorType, ImageDecoder, LimitSupport, Limits};
34
35/// The length of the SGI .rgb file header, including padding
36const HEADER_FULL_LENGTH: usize = 512;
37
38/// Important information from an SGI Image header.
39/// This _excludes_:
40/// - The name field (returned separately)
41/// - the pixmin/pixmax fields, which different encoders generate
42///   inconsistently and are easy to compute if needed
43/// - colormap field: only colormap NORMAL=0 is accepted, not obsolete modes
44///
45#[derive(Clone, Copy)]
46struct SgiRgbHeaderInfo {
47    xsize: u16,
48    ysize: u16,
49    color_type: ColorType,
50    is_rle: bool,
51}
52
53/// Errors which can occur while parsing an SGI .rgb file
54#[derive(Debug)]
55enum SgiRgbDecodeError {
56    BadMagic,
57    HeaderError,
58    RLERowInvalid(u32, u32), // Invalid RLE row specification
59    UnexpectedColormap(u32),
60    UnexpectedZSize(u16),
61    ZeroSize(u16, u16, u16),
62    ScanlineOverflow,
63    ScanlineUnderflow,
64    ScanlineTooShort,
65    ScanlineTooLong,
66    InvalidName,
67    EarlyEOF,
68}
69
70impl fmt::Display for SgiRgbDecodeError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            Self::BadMagic => f.write_str("Incorrect magic bytes, not an SGI image file"),
74            Self::HeaderError => f.write_str("Error parsing header"),
75            Self::UnexpectedColormap(code) => {
76                f.write_fmt(format_args!("Unexpected color map value ({})", code))
77            }
78            Self::UnexpectedZSize(dim) => {
79                f.write_fmt(format_args!("Unexpected Z dimension is >= 5 ({})", dim))
80            }
81            Self::ZeroSize(x, y, z) => f.write_fmt(format_args!(
82                "Image has zero size: x*y*z={}*{}*{}=0",
83                x, y, z
84            )),
85            Self::RLERowInvalid(offset, length) => {
86                f.write_fmt(format_args!("Bad RLE row info (offset={}, length={}); empty or not in image data region", offset, length))
87            }
88            Self::InvalidName => {
89                f.write_str("Invalid name field (not null terminated or not ASCII)")
90            }
91            Self::ScanlineOverflow => {
92                f.write_str("An RLE-encoded scanline contained more data than the image width")
93            }
94            Self::ScanlineUnderflow => {
95                f.write_str("An RLE-encoded scanline contained less data than the image width")
96            }
97            Self::ScanlineTooShort => {
98                f.write_str("An RLE-encoded scanline stopped (reached a zero counter) before its stated length")
99            }
100            Self::ScanlineTooLong => {
101                f.write_str("An RLE-encoded scanline did not stop at its stated length (missing trailing zero counter).")
102            }
103            Self::EarlyEOF => {
104                f.write_str("File ended before all scanlines were read.")
105            }
106        }
107    }
108}
109
110impl std::error::Error for SgiRgbDecodeError {}
111
112impl From<SgiRgbDecodeError> for ImageError {
113    fn from(e: SgiRgbDecodeError) -> ImageError {
114        ImageError::Decoding(DecodingError::new(ImageFormatHint::Name("SGI".into()), e))
115    }
116}
117
118/// Parse the image header, returning key metadata and the name field if successful
119fn parse_header(
120    header: &[u8; HEADER_FULL_LENGTH],
121) -> Result<(SgiRgbHeaderInfo, &CStr), SgiRgbDecodeError> {
122    if header[..2] != *b"\x01\xda" {
123        return Err(SgiRgbDecodeError::BadMagic);
124    }
125    let storage = header[2];
126    let bpc = header[3];
127    let dimension = u16::from_be_bytes(header[4..6].try_into().unwrap());
128    let xsize = u16::from_be_bytes(header[6..8].try_into().unwrap());
129    let ysize = u16::from_be_bytes(header[8..10].try_into().unwrap());
130    let zsize = u16::from_be_bytes(header[10..12].try_into().unwrap());
131    let _pixmin = u32::from_be_bytes(header[12..16].try_into().unwrap());
132    let _pixmax = u32::from_be_bytes(header[16..20].try_into().unwrap());
133    /* Dummy bytes -- formerly, this was a 'wastebytes' field, so old
134     * images may not set this to zero. */
135    let _dummy1 = u32::from_be_bytes(header[20..24].try_into().unwrap());
136    let imagename: &[u8] = &header[24..104];
137    let colormap = u32::from_be_bytes(header[104..108].try_into().unwrap());
138    /* More dummy bytes -- old libraries appear to have used the same struct
139     * for header data and, following it, internal image parsing state; and
140     * when writing the header just wrote whatever was in the struct. As a
141     * result, the following bytes may contain random counters, pointer data,
142     * etc. in old images. Recently (post 2000) written encoders tend to
143     * follow the file format specification and write zeros here. */
144    let _dummy2 = &header[108..];
145    if storage >= 2 {
146        return Err(SgiRgbDecodeError::HeaderError);
147    }
148    if bpc == 0 {
149        return Err(SgiRgbDecodeError::HeaderError);
150    }
151    if colormap != 0 {
152        return Err(SgiRgbDecodeError::UnexpectedColormap(colormap));
153    }
154    // Header fields `ysize`/`zsize` are only validated if used
155    let (xsize, ysize, zsize) = match dimension {
156        0 | 4.. => {
157            return Err(SgiRgbDecodeError::HeaderError);
158        }
159        1 => (xsize, 1, 1),
160        2 => (xsize, ysize, 1),
161        3 => (xsize, ysize, zsize),
162    };
163    if xsize == 0 || ysize == 0 || zsize == 0 {
164        return Err(SgiRgbDecodeError::ZeroSize(xsize, ysize, zsize));
165    }
166    let name = CStr::from_bytes_until_nul(imagename).map_err(|_| SgiRgbDecodeError::InvalidName)?;
167    if name.to_bytes().iter().any(|x| !x.is_ascii()) {
168        return Err(SgiRgbDecodeError::InvalidName);
169    }
170
171    let color_type = match (bpc, zsize) {
172        (1, 1) => ColorType::L8,
173        (1, 2) => ColorType::La8,
174        (1, 3) => ColorType::Rgb8,
175        (1, 4) => ColorType::Rgba8,
176        (2, 1) => ColorType::L16,
177        (2, 2) => ColorType::La16,
178        (2, 3) => ColorType::Rgb16,
179        (2, 4) => ColorType::Rgba16,
180        _ => {
181            if zsize >= 5 {
182                return Err(SgiRgbDecodeError::UnexpectedZSize(zsize));
183            } else {
184                return Err(SgiRgbDecodeError::HeaderError);
185            }
186        }
187    };
188    Ok((
189        SgiRgbHeaderInfo {
190            is_rle: storage == 1,
191            xsize,
192            ysize,
193            color_type,
194        },
195        name,
196    ))
197}
198
199/// Decoder for SGI (.rgb) images.
200pub struct SgiDecoder<R> {
201    info: SgiRgbHeaderInfo,
202    reader: R,
203}
204
205impl<R> SgiDecoder<R>
206where
207    R: BufRead,
208{
209    /// Create a new `SgiDecoder`. Assumes `r` starts at seek position 0.
210    pub fn new(mut r: R) -> Result<SgiDecoder<R>, ImageError> {
211        let mut header = [0u8; HEADER_FULL_LENGTH];
212        r.read_exact(&mut header)?;
213        let (header_info, _name) = parse_header(&header)?;
214
215        Ok(SgiDecoder {
216            info: header_info,
217            reader: r,
218        })
219    }
220}
221
222/** State associated with the processing of a scan line */
223#[derive(Clone, Copy)]
224struct SgiRgbScanlineState {
225    /// Offset of the encoded row in the input file
226    offset: u32,
227    /// Length of the encoded row in the file, including terminator
228    length: u32,
229    /// Current row number in 0..ysize
230    row_id: u16,
231    /// Current position to write in the current image row, range 0..=xsize
232    position: u16,
233    /// Index of the preceding line which contains the current file position
234    /// (or u32::MAX if there is no such line.) Other than u32::MAX, values
235    /// range from 0 to 2^18 - 1
236    prec_active_line: u32,
237    /// Color plane of row. Values in 0..4
238    plane: u8,
239    /// The most recently read counter byte; for copy operations, this may be
240    /// modified as the copy progresses
241    counter: u8,
242    /// If 16-bit depth used, last high byte read
243    data_char_hi: u8,
244    /// If true, will next read a counter field
245    at_counter: bool,
246    /// If true, will next read a high byte
247    high_byte: bool,
248}
249
250/** State associated with the processing of the image data for RLE type images */
251struct SgiRgbDecodeState {
252    /// Number of bytes in the stream read so far, including header. In practice,
253    /// this can be as large as the max end of a valid encoded scanline (2^32 + 2^18 - 3)
254    pos: u64,
255    /// Maximum index of a row which contains pos, if one exists.
256    max_active_row: Option<u32>,
257    /// Index of the next row in the sequence which has offset >= pos, or rle_table.len() if no such row
258    cursor: usize,
259}
260
261/** Apply a byte of the input to a scanline state machine.
262 * Returns `Ok(true)` if this byte completes an end of line marker.
263 * Returns Err(true) on line overflow, Err(false) on line underflow. */
264fn apply_rle16_byte(
265    row: &mut SgiRgbScanlineState,
266    buf_row: &mut [u8],
267    byte: u8,
268    xsize: u16,
269    channels: u8,
270) -> Result<bool, bool> {
271    let mut eor = false;
272    if row.high_byte {
273        row.data_char_hi = byte;
274        row.high_byte = false;
275    } else {
276        if row.at_counter {
277            row.counter = byte;
278            row.at_counter = false;
279            let count = row.counter & (!0x80);
280            if count == 0 {
281                // End of line
282                if row.position != xsize {
283                    return Err(false);
284                } else {
285                    eor = true;
286                }
287            }
288        } else {
289            let data = u16::from_be_bytes([row.data_char_hi, byte]);
290            let mut count = row.counter & (!0x80);
291            /* Have checked that count != 0 */
292            if count == 0 {
293                unreachable!();
294            } else if row.counter & 0x80 == 0 {
295                // Expand data to `count` repeated u16s
296                let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
297                if overflows_row {
298                    return Err(true);
299                }
300                // The calculated write_start should be <= buf.len() and hence not overflow
301                let write_start: usize = (row.position as usize) * (channels as usize);
302                for i in 0..count {
303                    let o = write_start + (row.plane as usize) + (channels as usize) * (i as usize);
304                    buf_row[2 * o..2 * o + 2].copy_from_slice(&u16::to_ne_bytes(data));
305                }
306                row.position += count as u16;
307                row.at_counter = true;
308            } else {
309                // Copy the next `count` u16s of data
310                let overflows_row = row.position > xsize - 1;
311                if overflows_row {
312                    return Err(true);
313                }
314
315                let write_start: usize =
316                    (row.position as usize) * (channels as usize) + (row.plane as usize);
317                buf_row[2 * write_start..2 * write_start + 2]
318                    .copy_from_slice(&u16::to_ne_bytes(data));
319
320                count -= 1;
321                row.position += 1;
322                row.counter = 0x80 | count;
323                row.at_counter = count == 0;
324            }
325        }
326
327        row.high_byte = !row.high_byte;
328    }
329    Ok(eor)
330}
331
332/** Apply a byte of the input to a scanline state machine.
333 * Returns Ok(true) if this byte completes an end of line marker.
334 * Returns Err(true) on line overflow, Err(false) on line underflow. */
335fn apply_rle8_byte(
336    row: &mut SgiRgbScanlineState,
337    buf_row: &mut [u8],
338    byte: u8,
339    xsize: u16,
340    channels: u8,
341) -> Result<bool, bool> {
342    let mut eor = false;
343    if row.at_counter {
344        row.counter = byte;
345        row.at_counter = false;
346        let count = row.counter & (!0x80);
347        if count == 0 {
348            // End of line
349            if row.position != xsize {
350                return Err(false);
351            } else {
352                eor = true;
353            }
354        }
355    } else {
356        let mut count = row.counter & (!0x80);
357        /* Have checked that count != 0 */
358        if count == 0 {
359            unreachable!();
360        } else if row.counter & 0x80 == 0 {
361            // Expand data to `count` repeated u16s
362            let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
363            if overflows_row {
364                return Err(true);
365            }
366            // The calculated write_start should be <= buf.len() and hence not overflow
367            let write_start: usize = (row.position as usize) * (channels as usize);
368            for i in 0..count {
369                let o = write_start + (row.plane as usize) + (channels as usize) * (i as usize);
370                buf_row[o] = byte;
371            }
372            row.position += count as u16;
373            row.at_counter = true;
374        } else {
375            // Copy the next `count` u8s of data
376            let overflows_row = row.position > xsize - 1;
377            if overflows_row {
378                return Err(true);
379            }
380
381            let write_start: usize =
382                (row.position as usize) * (channels as usize) + (row.plane as usize);
383            buf_row[write_start] = byte;
384
385            count -= 1;
386            row.position += 1;
387            row.counter = 0x80 | count;
388            row.at_counter = count == 0;
389        }
390    }
391    Ok(eor)
392}
393
394/** For the given RLE scanline, process the bytes in `segment`.
395 * Parameter `ending` is true iff this segment ends the scanline. */
396fn process_rle_segment<const DEEP: bool>(
397    buf_row: &mut [u8],
398    row: &mut SgiRgbScanlineState,
399    xsize: u16,
400    channels: u8,
401    segment: &[u8],
402    ending: bool,
403) -> Result<(), SgiRgbDecodeError> {
404    for (i, byte) in segment.iter().enumerate() {
405        let res = if DEEP {
406            apply_rle16_byte(row, buf_row, *byte, xsize, channels)
407        } else {
408            apply_rle8_byte(row, buf_row, *byte, xsize, channels)
409        };
410        let eor = res.map_err(|overflow| {
411            if overflow {
412                SgiRgbDecodeError::ScanlineOverflow
413            } else {
414                SgiRgbDecodeError::ScanlineUnderflow
415            }
416        })?;
417
418        let expecting_end = i + 1 == segment.len() && ending;
419        if eor != expecting_end {
420            if eor {
421                // Row ended earlier than expected
422                return Err(SgiRgbDecodeError::ScanlineTooShort);
423            } else {
424                // At end of scanline data, did not process a row end marker
425                return Err(SgiRgbDecodeError::ScanlineTooLong);
426            }
427        }
428    }
429
430    Ok(())
431}
432
433/** Process the next region of the RLE-encoded image data section of the SGI image file */
434fn process_data_segment<const DEEP: bool>(
435    buf: &mut [u8],
436    info: SgiRgbHeaderInfo,
437    mut state: SgiRgbDecodeState,
438    rle_table: &mut [SgiRgbScanlineState],
439    segment: &[u8],
440) -> Result<(SgiRgbDecodeState, bool), SgiRgbDecodeError> {
441    let channels = info.color_type.channel_count();
442    let start_pos = state.pos;
443    let end_pos = state.pos + segment.len() as u64;
444
445    // Add new encoded lines that intersect `segment` to the active set
446    while state.cursor < rle_table.len() && rle_table[state.cursor].offset as u64 <= end_pos {
447        rle_table[state.cursor].prec_active_line = state.max_active_row.unwrap_or(u32::MAX);
448        state.max_active_row = Some(state.cursor as u32);
449        state.cursor += 1;
450    }
451
452    let mut prev_row: Option<u32> = None;
453    let Some(mut row_index) = state.max_active_row else {
454        // No rows are active, nothing to do
455        state.pos = end_pos;
456        return Ok((state, false));
457    };
458
459    loop {
460        let row = &mut rle_table[row_index as usize];
461        let row_end = row.offset as u64 + row.length as u64;
462        debug_assert!(row.offset as u64 <= end_pos && row_end > start_pos);
463
464        let prev_active_row = row.prec_active_line;
465
466        // Intersect the segment being processed with the row extents, and then
467        // run the RLE state machine over the resulting intersected segment
468        let rs_start: usize = if row.offset as u64 > start_pos {
469            ((row.offset as u64) - start_pos) as usize
470        } else {
471            0
472        };
473        let rs_end: usize = if row_end <= end_pos {
474            (row_end - start_pos) as usize
475        } else {
476            segment.len()
477        };
478        let has_ending = row_end <= end_pos;
479        let row_input = &segment[rs_start..rs_end];
480
481        let bpp = if DEEP { 2 } else { 1 };
482        let stride = (info.xsize as usize) * (channels as usize) * bpp;
483        let buf_row = &mut buf[((info.ysize - 1 - row.row_id) as usize) * stride
484            ..((info.ysize - 1 - row.row_id) as usize) * stride + stride];
485
486        process_rle_segment::<DEEP>(buf_row, row, info.xsize, channels, row_input, has_ending)?;
487
488        if has_ending {
489            // Mark this row as not having a preceding row, and remove
490            // it from the singly linked list
491            row.prec_active_line = u32::MAX;
492            if let Some(r) = prev_row {
493                rle_table[r as usize].prec_active_line = prev_active_row;
494            } else if prev_active_row == u32::MAX {
495                state.max_active_row = None;
496            } else {
497                state.max_active_row = Some(prev_active_row);
498            }
499        } else {
500            prev_row = Some(row_index);
501        }
502
503        if prev_active_row != u32::MAX {
504            row_index = prev_active_row;
505        } else {
506            break;
507        }
508    }
509
510    state.pos = end_pos;
511    let done = state.max_active_row.is_none() && state.cursor == rle_table.len();
512    Ok((state, done))
513}
514
515impl<R: BufRead> ImageDecoder for SgiDecoder<R> {
516    fn dimensions(&self) -> (u32, u32) {
517        (self.info.xsize as u32, self.info.ysize as u32)
518    }
519
520    fn color_type(&self) -> ColorType {
521        self.info.color_type
522    }
523
524    fn original_color_type(&self) -> ExtendedColorType {
525        self.info.color_type.into()
526    }
527
528    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
529        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
530
531        let channels = self.info.color_type.channel_count();
532        let deep = self.info.color_type.bytes_per_pixel() > channels;
533        if self.info.is_rle {
534            /* Tricky case: need to read the RLE offset tables to determine
535             * where to find the scanlines for each plane. Images can place
536             * the scanlines whereever, and processing them in logical order
537             * can require a lot of seeking.
538             *
539             * This decoder does not have fine grained control over the
540             * input stream buffering, so each seek operation could make
541             * the input stream read 8 kB and/or make a syscall if the
542             * input stream is a default BufReader. An image could trigger
543             * one seek operation per 8 marginal bytes of data. To avoid
544             * this unfortunate interaction, we load the O(height) bytes
545             * of RLE tables into memory and then process the rest of the
546             * image in a single pass.
547             */
548
549            /* `rle_offset_entries` has maximum value `4 * (2^16-1)` and will not overflow */
550            let rle_offset_entries = (channels as u32) * (self.info.ysize as u32);
551
552            let mut rle_table: Vec<SgiRgbScanlineState> = Vec::new();
553            /* Tiny invalid images can trigger medium-size 4 * (2^16-1) allocations here;
554             * this can be avoided by dynamically resizing the vector as it is read. */
555            rle_table
556                .try_reserve_exact(rle_offset_entries as usize)
557                .map_err(|_| ImageError::Limits(LimitErrorKind::InsufficientMemory.into()))?;
558            rle_table.resize(
559                rle_offset_entries as usize,
560                SgiRgbScanlineState {
561                    offset: 0,
562                    length: 0,
563                    row_id: 0,
564                    position: 0,
565                    plane: 0,
566                    counter: 0,
567                    data_char_hi: 0,
568                    prec_active_line: 0,
569                    high_byte: deep,
570                    at_counter: true,
571                },
572            );
573            // Read offset table
574            for plane in 0..channels {
575                for y in 0..self.info.ysize {
576                    let idx = (plane as usize) * (self.info.ysize as usize) + (y as usize);
577                    let mut tmp = [0u8; 4];
578                    self.reader.read_exact(&mut tmp)?;
579                    rle_table[idx].offset = u32::from_be_bytes(tmp);
580                    rle_table[idx].row_id = y;
581                    rle_table[idx].plane = plane;
582                }
583            }
584            // Read length table, and validate (offset, length) pairs
585            for plane in 0..channels {
586                for y in 0..self.info.ysize {
587                    let idx = (plane as usize) * (self.info.ysize as usize) + (y as usize);
588                    let mut tmp = [0u8; 4];
589                    self.reader.read_exact(&mut tmp)?;
590                    rle_table[idx].length = u32::from_be_bytes(tmp);
591
592                    // Per spec, image data follows the offset tables.
593                    // (although other decoders will probably read inside the offset tables
594                    // if asked.)
595                    let scanline_too_early = rle_table[idx].offset
596                        < (HEADER_FULL_LENGTH as u32) + rle_offset_entries * 8;
597                    let zero_length = rle_table[idx].length == 0;
598
599                    if scanline_too_early || zero_length {
600                        return Err(SgiRgbDecodeError::RLERowInvalid(
601                            rle_table[idx].offset,
602                            rle_table[idx].length,
603                        )
604                        .into());
605                    }
606                }
607            }
608
609            // Sort rows by their starting position in the stream, breaking
610            // ties by their position in the buffer.
611            rle_table.sort_unstable_by_key(|f| {
612                (f.offset as u64) << 32 | (f.row_id as u64) << 16 | f.plane as u64
613            });
614
615            // Use explicit state for linear processing
616            let mut rle_state = SgiRgbDecodeState {
617                cursor: 0,
618                max_active_row: None,
619                pos: (HEADER_FULL_LENGTH as u64) + (rle_table.len() as u64) * 8,
620            };
621
622            loop {
623                let buffer = self.reader.fill_buf()?;
624                if buffer.is_empty() {
625                    /* Unexpected EOF . */
626                    return Err(SgiRgbDecodeError::EarlyEOF.into());
627                }
628
629                let (new_state, done) = if deep {
630                    process_data_segment::<true>(buf, self.info, rle_state, &mut rle_table, buffer)?
631                } else {
632                    process_data_segment::<false>(
633                        buf,
634                        self.info,
635                        rle_state,
636                        &mut rle_table,
637                        buffer,
638                    )?
639                };
640                if done {
641                    return Ok(());
642                }
643                rle_state = new_state;
644
645                let buffer_length = buffer.len();
646                self.reader.consume(buffer_length);
647            }
648        } else {
649            /* Easy case: packed images by scanline, plane by plane  */
650            if deep {
651                let bpp = 2 * channels;
652                // `width` will be at most `(2^16-1) * 8`, so there is never overflow
653                let width = (bpp as u32) * (self.info.xsize as u32);
654                for plane in 0..channels as usize {
655                    for row in buf.chunks_exact_mut(width as usize).rev() {
656                        for px in row.chunks_exact_mut(bpp as usize) {
657                            let mut tmp = [0_u8; 2];
658                            self.reader.read_exact(&mut tmp)?;
659                            px[2 * plane..2 * plane + 2]
660                                .copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes(tmp)));
661                        }
662                    }
663                }
664            } else {
665                let width = (channels as u32) * (self.info.xsize as u32);
666                for plane in 0..channels as usize {
667                    for row in buf.chunks_exact_mut(width as usize).rev() {
668                        for px in row.chunks_exact_mut(channels as usize) {
669                            self.reader.read_exact(&mut px[plane..plane + 1])?;
670                        }
671                    }
672                }
673            }
674            Ok(())
675        }
676    }
677
678    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
679        (*self).read_image(buf)
680    }
681
682    fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
683        limits.check_support(&LimitSupport::default())?;
684        let (width, height) = self.dimensions();
685        limits.check_dimensions(width, height)?;
686
687        // This will not overflow, because 8 * (2^16-1) * (2^16-1) ≤ 2^35
688        let max_image_bytes = 8 * (self.info.xsize as u64) * (self.info.ysize as u64);
689        // This will not overflow, because even 1 KB * (2^16-1) ≤ 2^35 ⪡ 2^64-1
690        let max_table_bytes =
691            (self.info.ysize as u64) * (std::mem::size_of::<SgiRgbScanlineState>() as u64);
692        // This will not overflow, because it is ≤ 2^36 ⪡ 2^64-1
693        let max_bytes = max_image_bytes + max_table_bytes;
694
695        let max_alloc = limits.max_alloc.unwrap_or(u64::MAX);
696        if max_alloc < max_bytes {
697            return Err(ImageError::Limits(LimitError::from_kind(
698                LimitErrorKind::InsufficientMemory,
699            )));
700        }
701        Ok(())
702    }
703}