Skip to main content

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. */
264#[inline]
265fn apply_rle16_byte(
266    row: &mut SgiRgbScanlineState,
267    buf_row: &mut [u8],
268    byte: u8,
269    xsize: u16,
270    channels: u8,
271) -> Result<bool, bool> {
272    let mut eor = false;
273    if row.high_byte {
274        row.data_char_hi = byte;
275        row.high_byte = false;
276    } else {
277        if row.at_counter {
278            row.counter = byte;
279            row.at_counter = false;
280            let count = row.counter & (!0x80);
281            if count == 0 {
282                // End of line
283                if row.position != xsize {
284                    return Err(false);
285                } else {
286                    eor = true;
287                }
288            }
289        } else {
290            let data = u16::from_be_bytes([row.data_char_hi, byte]);
291            let mut count = row.counter & (!0x80);
292            /* Have checked that count != 0 */
293            if count == 0 {
294                unreachable!();
295            } else if row.counter & 0x80 == 0 {
296                // Expand data to `count` repeated u16s
297                let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
298                if overflows_row {
299                    return Err(true);
300                }
301                // The calculated write_start should be <= buf.len() and hence not overflow
302                let write_start: usize = (row.position as usize) * (channels as usize);
303                for i in 0..count {
304                    let o = write_start + (row.plane as usize) + (channels as usize) * (i as usize);
305                    buf_row[2 * o..2 * o + 2].copy_from_slice(&u16::to_ne_bytes(data));
306                }
307                row.position += count as u16;
308                row.at_counter = true;
309            } else {
310                // Copy the next `count` u16s of data
311                let overflows_row = row.position > xsize - 1;
312                if overflows_row {
313                    return Err(true);
314                }
315
316                let write_start: usize =
317                    (row.position as usize) * (channels as usize) + (row.plane as usize);
318                buf_row[2 * write_start..2 * write_start + 2]
319                    .copy_from_slice(&u16::to_ne_bytes(data));
320
321                count -= 1;
322                row.position += 1;
323                row.counter = 0x80 | count;
324                row.at_counter = count == 0;
325            }
326        }
327
328        row.high_byte = !row.high_byte;
329    }
330    Ok(eor)
331}
332
333/** Apply a byte of the input to a scanline state machine.
334 * Returns Ok(true) if this byte completes an end of line marker.
335 * Returns Err(true) on line overflow, Err(false) on line underflow. */
336#[inline]
337fn apply_rle8_byte(
338    row: &mut SgiRgbScanlineState,
339    buf_row: &mut [u8],
340    byte: u8,
341    xsize: u16,
342    channels: u8,
343) -> Result<bool, bool> {
344    let mut eor = false;
345    if row.at_counter {
346        row.counter = byte;
347        row.at_counter = false;
348        let count = row.counter & (!0x80);
349        if count == 0 {
350            // End of line
351            if row.position != xsize {
352                return Err(false);
353            } else {
354                eor = true;
355            }
356        }
357    } else {
358        let mut count = row.counter & (!0x80);
359        /* Have checked that count != 0 */
360        if count == 0 {
361            unreachable!();
362        } else if row.counter & 0x80 == 0 {
363            // Expand data to `count` repeated u16s
364            let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
365            if overflows_row {
366                return Err(true);
367            }
368            // The calculated write_start should be <= buf.len() and hence not overflow
369            let write_start: usize = (row.position as usize) * (channels as usize);
370            for i in 0..count {
371                let o = write_start + (row.plane as usize) + (channels as usize) * (i as usize);
372                buf_row[o] = byte;
373            }
374            row.position += count as u16;
375            row.at_counter = true;
376        } else {
377            // Copy the next `count` u8s of data
378            let overflows_row = row.position > xsize - 1;
379            if overflows_row {
380                return Err(true);
381            }
382
383            let write_start: usize =
384                (row.position as usize) * (channels as usize) + (row.plane as usize);
385            buf_row[write_start] = byte;
386
387            count -= 1;
388            row.position += 1;
389            row.counter = 0x80 | count;
390            row.at_counter = count == 0;
391        }
392    }
393    Ok(eor)
394}
395
396/** For the given RLE scanline, process the bytes in `segment`.
397 * Parameter `ending` is true iff this segment ends the scanline. */
398fn process_rle_segment<const DEEP: bool>(
399    buf_row: &mut [u8],
400    row: &mut SgiRgbScanlineState,
401    xsize: u16,
402    channels: u8,
403    segment: &[u8],
404    ending: bool,
405) -> Result<(), SgiRgbDecodeError> {
406    for (i, byte) in segment.iter().enumerate() {
407        let res = if DEEP {
408            apply_rle16_byte(row, buf_row, *byte, xsize, channels)
409        } else {
410            apply_rle8_byte(row, buf_row, *byte, xsize, channels)
411        };
412        let eor = res.map_err(|overflow| {
413            if overflow {
414                SgiRgbDecodeError::ScanlineOverflow
415            } else {
416                SgiRgbDecodeError::ScanlineUnderflow
417            }
418        })?;
419
420        let expecting_end = i + 1 == segment.len() && ending;
421        if eor != expecting_end {
422            if eor {
423                // Row ended earlier than expected
424                return Err(SgiRgbDecodeError::ScanlineTooShort);
425            } else {
426                // At end of scanline data, did not process a row end marker
427                return Err(SgiRgbDecodeError::ScanlineTooLong);
428            }
429        }
430    }
431
432    Ok(())
433}
434
435/** Process the next region of the RLE-encoded image data section of the SGI image file */
436fn process_data_segment<const DEEP: bool>(
437    buf: &mut [u8],
438    info: SgiRgbHeaderInfo,
439    mut state: SgiRgbDecodeState,
440    rle_table: &mut [SgiRgbScanlineState],
441    segment: &[u8],
442) -> Result<(SgiRgbDecodeState, bool), SgiRgbDecodeError> {
443    let channels = info.color_type.channel_count();
444    let start_pos = state.pos;
445    let end_pos = state.pos + segment.len() as u64;
446
447    // Add new encoded lines that intersect `segment` to the active set
448    while state.cursor < rle_table.len() && rle_table[state.cursor].offset as u64 <= end_pos {
449        rle_table[state.cursor].prec_active_line = state.max_active_row.unwrap_or(u32::MAX);
450        state.max_active_row = Some(state.cursor as u32);
451        state.cursor += 1;
452    }
453
454    let mut prev_row: Option<u32> = None;
455    let Some(mut row_index) = state.max_active_row else {
456        // No rows are active, nothing to do
457        state.pos = end_pos;
458        return Ok((state, false));
459    };
460
461    loop {
462        let row = &mut rle_table[row_index as usize];
463        let row_end = row.offset as u64 + row.length as u64;
464        debug_assert!(row.offset as u64 <= end_pos && row_end > start_pos);
465
466        let prev_active_row = row.prec_active_line;
467
468        // Intersect the segment being processed with the row extents, and then
469        // run the RLE state machine over the resulting intersected segment
470        let rs_start: usize = if row.offset as u64 > start_pos {
471            ((row.offset as u64) - start_pos) as usize
472        } else {
473            0
474        };
475        let rs_end: usize = if row_end <= end_pos {
476            (row_end - start_pos) as usize
477        } else {
478            segment.len()
479        };
480        let has_ending = row_end <= end_pos;
481        let row_input = &segment[rs_start..rs_end];
482
483        let bpp = if DEEP { 2 } else { 1 };
484        let stride = (info.xsize as usize) * (channels as usize) * bpp;
485        let buf_row = &mut buf[((info.ysize - 1 - row.row_id) as usize) * stride
486            ..((info.ysize - 1 - row.row_id) as usize) * stride + stride];
487
488        process_rle_segment::<DEEP>(buf_row, row, info.xsize, channels, row_input, has_ending)?;
489
490        if has_ending {
491            // Mark this row as not having a preceding row, and remove
492            // it from the singly linked list
493            row.prec_active_line = u32::MAX;
494            if let Some(r) = prev_row {
495                rle_table[r as usize].prec_active_line = prev_active_row;
496            } else if prev_active_row == u32::MAX {
497                state.max_active_row = None;
498            } else {
499                state.max_active_row = Some(prev_active_row);
500            }
501        } else {
502            prev_row = Some(row_index);
503        }
504
505        if prev_active_row != u32::MAX {
506            row_index = prev_active_row;
507        } else {
508            break;
509        }
510    }
511
512    state.pos = end_pos;
513    let done = state.max_active_row.is_none() && state.cursor == rle_table.len();
514    Ok((state, done))
515}
516
517impl<R: BufRead> ImageDecoder for SgiDecoder<R> {
518    fn dimensions(&self) -> (u32, u32) {
519        (self.info.xsize as u32, self.info.ysize as u32)
520    }
521
522    fn color_type(&self) -> ColorType {
523        self.info.color_type
524    }
525
526    fn original_color_type(&self) -> ExtendedColorType {
527        self.info.color_type.into()
528    }
529
530    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
531        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
532
533        let channels = self.info.color_type.channel_count();
534        let deep = self.info.color_type.bytes_per_pixel() > channels;
535        if self.info.is_rle {
536            /* Tricky case: need to read the RLE offset tables to determine
537             * where to find the scanlines for each plane. Images can place
538             * the scanlines whereever, and processing them in logical order
539             * can require a lot of seeking.
540             *
541             * This decoder does not have fine grained control over the
542             * input stream buffering, so each seek operation could make
543             * the input stream read 8 kB and/or make a syscall if the
544             * input stream is a default BufReader. An image could trigger
545             * one seek operation per 8 marginal bytes of data. To avoid
546             * this unfortunate interaction, we load the O(height) bytes
547             * of RLE tables into memory and then process the rest of the
548             * image in a single pass.
549             */
550
551            /* `rle_offset_entries` has maximum value `4 * (2^16-1)` and will not overflow */
552            let rle_offset_entries = (channels as u32) * (self.info.ysize as u32);
553
554            let mut rle_table: Vec<SgiRgbScanlineState> = Vec::new();
555            /* Tiny invalid images can trigger medium-size 4 * (2^16-1) allocations here;
556             * this can be avoided by dynamically resizing the vector as it is read. */
557            rle_table
558                .try_reserve_exact(rle_offset_entries as usize)
559                .map_err(|_| ImageError::Limits(LimitErrorKind::InsufficientMemory.into()))?;
560            rle_table.resize(
561                rle_offset_entries as usize,
562                SgiRgbScanlineState {
563                    offset: 0,
564                    length: 0,
565                    row_id: 0,
566                    position: 0,
567                    plane: 0,
568                    counter: 0,
569                    data_char_hi: 0,
570                    prec_active_line: 0,
571                    high_byte: deep,
572                    at_counter: true,
573                },
574            );
575            // Read offset table
576            for plane in 0..channels {
577                for y in 0..self.info.ysize {
578                    let idx = (plane as usize) * (self.info.ysize as usize) + (y as usize);
579                    let mut tmp = [0u8; 4];
580                    self.reader.read_exact(&mut tmp)?;
581                    rle_table[idx].offset = u32::from_be_bytes(tmp);
582                    rle_table[idx].row_id = y;
583                    rle_table[idx].plane = plane;
584                }
585            }
586            // Read length table, and validate (offset, length) pairs
587            for plane in 0..channels {
588                for y in 0..self.info.ysize {
589                    let idx = (plane as usize) * (self.info.ysize as usize) + (y as usize);
590                    let mut tmp = [0u8; 4];
591                    self.reader.read_exact(&mut tmp)?;
592                    rle_table[idx].length = u32::from_be_bytes(tmp);
593
594                    // Per spec, image data follows the offset tables.
595                    // (although other decoders will probably read inside the offset tables
596                    // if asked.)
597                    let scanline_too_early = rle_table[idx].offset
598                        < (HEADER_FULL_LENGTH as u32) + rle_offset_entries * 8;
599                    let zero_length = rle_table[idx].length == 0;
600
601                    if scanline_too_early || zero_length {
602                        return Err(SgiRgbDecodeError::RLERowInvalid(
603                            rle_table[idx].offset,
604                            rle_table[idx].length,
605                        )
606                        .into());
607                    }
608                }
609            }
610
611            // Sort rows by their starting position in the stream, breaking
612            // ties by their position in the buffer.
613            rle_table.sort_unstable_by_key(|f| {
614                (f.offset as u64) << 32 | (f.row_id as u64) << 16 | f.plane as u64
615            });
616
617            // Use explicit state for linear processing
618            let mut rle_state = SgiRgbDecodeState {
619                cursor: 0,
620                max_active_row: None,
621                pos: (HEADER_FULL_LENGTH as u64) + (rle_table.len() as u64) * 8,
622            };
623
624            loop {
625                let buffer = self.reader.fill_buf()?;
626                if buffer.is_empty() {
627                    /* Unexpected EOF . */
628                    return Err(SgiRgbDecodeError::EarlyEOF.into());
629                }
630
631                let (new_state, done) = if deep {
632                    process_data_segment::<true>(buf, self.info, rle_state, &mut rle_table, buffer)?
633                } else {
634                    process_data_segment::<false>(
635                        buf,
636                        self.info,
637                        rle_state,
638                        &mut rle_table,
639                        buffer,
640                    )?
641                };
642                if done {
643                    return Ok(());
644                }
645                rle_state = new_state;
646
647                let buffer_length = buffer.len();
648                self.reader.consume(buffer_length);
649            }
650        } else {
651            /* Easy case: packed images by scanline, plane by plane  */
652            if deep {
653                let bpp = 2 * channels;
654                // `width` will be at most `(2^16-1) * 8`, so there is never overflow
655                let width = (bpp as u32) * (self.info.xsize as u32);
656                for plane in 0..channels as usize {
657                    for row in buf.chunks_exact_mut(width as usize).rev() {
658                        for px in row.chunks_exact_mut(bpp as usize) {
659                            let mut tmp = [0_u8; 2];
660                            self.reader.read_exact(&mut tmp)?;
661                            px[2 * plane..2 * plane + 2]
662                                .copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes(tmp)));
663                        }
664                    }
665                }
666            } else {
667                let width = (channels as u32) * (self.info.xsize as u32);
668                for plane in 0..channels as usize {
669                    for row in buf.chunks_exact_mut(width as usize).rev() {
670                        for px in row.chunks_exact_mut(channels as usize) {
671                            self.reader.read_exact(&mut px[plane..plane + 1])?;
672                        }
673                    }
674                }
675            }
676            Ok(())
677        }
678    }
679
680    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
681        (*self).read_image(buf)
682    }
683
684    fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
685        limits.check_support(&LimitSupport::default())?;
686        let (width, height) = self.dimensions();
687        limits.check_dimensions(width, height)?;
688
689        // This will not overflow, because 8 * (2^16-1) * (2^16-1) ≤ 2^35
690        let max_image_bytes = 8 * (self.info.xsize as u64) * (self.info.ysize as u64);
691        // This will not overflow, because even 1 KB * (2^16-1) ≤ 2^35 ⪡ 2^64-1
692        let max_table_bytes =
693            (self.info.ysize as u64) * (std::mem::size_of::<SgiRgbScanlineState>() as u64);
694        // This will not overflow, because it is ≤ 2^36 ⪡ 2^64-1
695        let max_bytes = max_image_bytes + max_table_bytes;
696
697        let max_alloc = limits.max_alloc.unwrap_or(u64::MAX);
698        if max_alloc < max_bytes {
699            return Err(ImageError::Limits(LimitError::from_kind(
700                LimitErrorKind::InsufficientMemory,
701            )));
702        }
703        Ok(())
704    }
705}