image-extras 0.1.0

Additional image format decoders for the image crate
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
/*! Decoding of SGI Image File Format (.rgb)
 *
 * The SGI Image File format (often referred to as .rgb) is an obsolete
 * file format which has uncompressed and run-length encoded modes,
 * supports a variable number of color channels, and both 8-bit and
 * 16-bit precisions.
 *
 * This decoder does not support:
 * - Images with ≥ 5 color channels (zsize). (While theoretically supported
 *   by the format, we haven't found any existing images that do this.)
 *
 * - Colormaps: Not supported, only the NORMAL=0 mode. The other operations
 *   (DITHERED=1, SCREEN=2, COLORMAP=3) were obsolete at the time the spec
 *   was written.
 *
 * The format specification does not explain how the alpha channel is to
 * be interpreted. Existing decoders appear to assume straight alpha, like
 * PNG.
 *
 * Specification:
 * - <https://web.archive.org/web/20010413154909/https://reality.sgi.com/grafica/sgiimage.html>
 * - <https://www.fileformat.info/format/sgiimage/egff.htm>
 *
 */

use core::ffi::CStr;
use std::fmt;
use std::io::BufRead;

use image::error::{
    DecodingError, ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind,
};
use image::{ColorType, ExtendedColorType, ImageDecoder, LimitSupport, Limits};

/// The length of the SGI .rgb file header, including padding
const HEADER_FULL_LENGTH: usize = 512;

/// Important information from an SGI Image header.
/// This _excludes_:
/// - The name field (returned separately)
/// - the pixmin/pixmax fields, which different encoders generate
///   inconsistently and are easy to compute if needed
/// - colormap field: only colormap NORMAL=0 is accepted, not obsolete modes
///
#[derive(Clone, Copy)]
struct SgiRgbHeaderInfo {
    xsize: u16,
    ysize: u16,
    color_type: ColorType,
    is_rle: bool,
}

/// Errors which can occur while parsing an SGI .rgb file
#[derive(Debug)]
enum SgiRgbDecodeError {
    BadMagic,
    HeaderError,
    RLERowInvalid(u32, u32), // Invalid RLE row specification
    UnexpectedColormap(u32),
    UnexpectedZSize(u16),
    ZeroSize(u16, u16, u16),
    ScanlineOverflow,
    ScanlineUnderflow,
    ScanlineTooShort,
    ScanlineTooLong,
    InvalidName,
    EarlyEOF,
}

impl fmt::Display for SgiRgbDecodeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::BadMagic => f.write_str("Incorrect magic bytes, not an SGI image file"),
            Self::HeaderError => f.write_str("Error parsing header"),
            Self::UnexpectedColormap(code) => {
                f.write_fmt(format_args!("Unexpected color map value ({})", code))
            }
            Self::UnexpectedZSize(dim) => {
                f.write_fmt(format_args!("Unexpected Z dimension is >= 5 ({})", dim))
            }
            Self::ZeroSize(x, y, z) => f.write_fmt(format_args!(
                "Image has zero size: x*y*z={}*{}*{}=0",
                x, y, z
            )),
            Self::RLERowInvalid(offset, length) => {
                f.write_fmt(format_args!("Bad RLE row info (offset={}, length={}); empty or not in image data region", offset, length))
            }
            Self::InvalidName => {
                f.write_str("Invalid name field (not null terminated or not ASCII)")
            }
            Self::ScanlineOverflow => {
                f.write_str("An RLE-encoded scanline contained more data than the image width")
            }
            Self::ScanlineUnderflow => {
                f.write_str("An RLE-encoded scanline contained less data than the image width")
            }
            Self::ScanlineTooShort => {
                f.write_str("An RLE-encoded scanline stopped (reached a zero counter) before its stated length")
            }
            Self::ScanlineTooLong => {
                f.write_str("An RLE-encoded scanline did not stop at its stated length (missing trailing zero counter).")
            }
            Self::EarlyEOF => {
                f.write_str("File ended before all scanlines were read.")
            }
        }
    }
}

impl std::error::Error for SgiRgbDecodeError {}

impl From<SgiRgbDecodeError> for ImageError {
    fn from(e: SgiRgbDecodeError) -> ImageError {
        ImageError::Decoding(DecodingError::new(ImageFormatHint::Name("SGI".into()), e))
    }
}

/// Parse the image header, returning key metadata and the name field if successful
fn parse_header(
    header: &[u8; HEADER_FULL_LENGTH],
) -> Result<(SgiRgbHeaderInfo, &CStr), SgiRgbDecodeError> {
    if header[..2] != *b"\x01\xda" {
        return Err(SgiRgbDecodeError::BadMagic);
    }
    let storage = header[2];
    let bpc = header[3];
    let dimension = u16::from_be_bytes(header[4..6].try_into().unwrap());
    let xsize = u16::from_be_bytes(header[6..8].try_into().unwrap());
    let ysize = u16::from_be_bytes(header[8..10].try_into().unwrap());
    let zsize = u16::from_be_bytes(header[10..12].try_into().unwrap());
    let _pixmin = u32::from_be_bytes(header[12..16].try_into().unwrap());
    let _pixmax = u32::from_be_bytes(header[16..20].try_into().unwrap());
    /* Dummy bytes -- formerly, this was a 'wastebytes' field, so old
     * images may not set this to zero. */
    let _dummy1 = u32::from_be_bytes(header[20..24].try_into().unwrap());
    let imagename: &[u8] = &header[24..104];
    let colormap = u32::from_be_bytes(header[104..108].try_into().unwrap());
    /* More dummy bytes -- old libraries appear to have used the same struct
     * for header data and, following it, internal image parsing state; and
     * when writing the header just wrote whatever was in the struct. As a
     * result, the following bytes may contain random counters, pointer data,
     * etc. in old images. Recently (post 2000) written encoders tend to
     * follow the file format specification and write zeros here. */
    let _dummy2 = &header[108..];
    if storage >= 2 {
        return Err(SgiRgbDecodeError::HeaderError);
    }
    if bpc == 0 {
        return Err(SgiRgbDecodeError::HeaderError);
    }
    if colormap != 0 {
        return Err(SgiRgbDecodeError::UnexpectedColormap(colormap));
    }
    // Header fields `ysize`/`zsize` are only validated if used
    let (xsize, ysize, zsize) = match dimension {
        0 | 4.. => {
            return Err(SgiRgbDecodeError::HeaderError);
        }
        1 => (xsize, 1, 1),
        2 => (xsize, ysize, 1),
        3 => (xsize, ysize, zsize),
    };
    if xsize == 0 || ysize == 0 || zsize == 0 {
        return Err(SgiRgbDecodeError::ZeroSize(xsize, ysize, zsize));
    }
    let name = CStr::from_bytes_until_nul(imagename).map_err(|_| SgiRgbDecodeError::InvalidName)?;
    if name.to_bytes().iter().any(|x| !x.is_ascii()) {
        return Err(SgiRgbDecodeError::InvalidName);
    }

    let color_type = match (bpc, zsize) {
        (1, 1) => ColorType::L8,
        (1, 2) => ColorType::La8,
        (1, 3) => ColorType::Rgb8,
        (1, 4) => ColorType::Rgba8,
        (2, 1) => ColorType::L16,
        (2, 2) => ColorType::La16,
        (2, 3) => ColorType::Rgb16,
        (2, 4) => ColorType::Rgba16,
        _ => {
            if zsize >= 5 {
                return Err(SgiRgbDecodeError::UnexpectedZSize(zsize));
            } else {
                return Err(SgiRgbDecodeError::HeaderError);
            }
        }
    };
    Ok((
        SgiRgbHeaderInfo {
            is_rle: storage == 1,
            xsize,
            ysize,
            color_type,
        },
        name,
    ))
}

/// Decoder for SGI (.rgb) images.
pub struct SgiDecoder<R> {
    info: SgiRgbHeaderInfo,
    reader: R,
}

impl<R> SgiDecoder<R>
where
    R: BufRead,
{
    /// Create a new `SgiDecoder`. Assumes `r` starts at seek position 0.
    pub fn new(mut r: R) -> Result<SgiDecoder<R>, ImageError> {
        let mut header = [0u8; HEADER_FULL_LENGTH];
        r.read_exact(&mut header)?;
        let (header_info, _name) = parse_header(&header)?;

        Ok(SgiDecoder {
            info: header_info,
            reader: r,
        })
    }
}

/** State associated with the processing of a scan line */
#[derive(Clone, Copy)]
struct SgiRgbScanlineState {
    /// Offset of the encoded row in the input file
    offset: u32,
    /// Length of the encoded row in the file, including terminator
    length: u32,
    /// Current row number in 0..ysize
    row_id: u16,
    /// Current position to write in the current image row, range 0..=xsize
    position: u16,
    /// Index of the preceding line which contains the current file position
    /// (or u32::MAX if there is no such line.) Other than u32::MAX, values
    /// range from 0 to 2^18 - 1
    prec_active_line: u32,
    /// Color plane of row. Values in 0..4
    plane: u8,
    /// The most recently read counter byte; for copy operations, this may be
    /// modified as the copy progresses
    counter: u8,
    /// If 16-bit depth used, last high byte read
    data_char_hi: u8,
    /// If true, will next read a counter field
    at_counter: bool,
    /// If true, will next read a high byte
    high_byte: bool,
}

/** State associated with the processing of the image data for RLE type images */
struct SgiRgbDecodeState {
    /// Number of bytes in the stream read so far, including header. In practice,
    /// this can be as large as the max end of a valid encoded scanline (2^32 + 2^18 - 3)
    pos: u64,
    /// Maximum index of a row which contains pos, if one exists.
    max_active_row: Option<u32>,
    /// Index of the next row in the sequence which has offset >= pos, or rle_table.len() if no such row
    cursor: usize,
}

/** Apply a byte of the input to a scanline state machine.
 * Returns `Ok(true)` if this byte completes an end of line marker.
 * Returns Err(true) on line overflow, Err(false) on line underflow. */
fn apply_rle16_byte(
    row: &mut SgiRgbScanlineState,
    buf_row: &mut [u8],
    byte: u8,
    xsize: u16,
    channels: u8,
) -> Result<bool, bool> {
    let mut eor = false;
    if row.high_byte {
        row.data_char_hi = byte;
        row.high_byte = false;
    } else {
        if row.at_counter {
            row.counter = byte;
            row.at_counter = false;
            let count = row.counter & (!0x80);
            if count == 0 {
                // End of line
                if row.position != xsize {
                    return Err(false);
                } else {
                    eor = true;
                }
            }
        } else {
            let data = u16::from_be_bytes([row.data_char_hi, byte]);
            let mut count = row.counter & (!0x80);
            /* Have checked that count != 0 */
            if count == 0 {
                unreachable!();
            } else if row.counter & 0x80 == 0 {
                // Expand data to `count` repeated u16s
                let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
                if overflows_row {
                    return Err(true);
                }
                // The calculated write_start should be <= buf.len() and hence not overflow
                let write_start: usize = (row.position as usize) * (channels as usize);
                for i in 0..count {
                    let o = write_start + (row.plane as usize) + (channels as usize) * (i as usize);
                    buf_row[2 * o..2 * o + 2].copy_from_slice(&u16::to_ne_bytes(data));
                }
                row.position += count as u16;
                row.at_counter = true;
            } else {
                // Copy the next `count` u16s of data
                let overflows_row = row.position > xsize - 1;
                if overflows_row {
                    return Err(true);
                }

                let write_start: usize =
                    (row.position as usize) * (channels as usize) + (row.plane as usize);
                buf_row[2 * write_start..2 * write_start + 2]
                    .copy_from_slice(&u16::to_ne_bytes(data));

                count -= 1;
                row.position += 1;
                row.counter = 0x80 | count;
                row.at_counter = count == 0;
            }
        }

        row.high_byte = !row.high_byte;
    }
    Ok(eor)
}

/** Apply a byte of the input to a scanline state machine.
 * Returns Ok(true) if this byte completes an end of line marker.
 * Returns Err(true) on line overflow, Err(false) on line underflow. */
fn apply_rle8_byte(
    row: &mut SgiRgbScanlineState,
    buf_row: &mut [u8],
    byte: u8,
    xsize: u16,
    channels: u8,
) -> Result<bool, bool> {
    let mut eor = false;
    if row.at_counter {
        row.counter = byte;
        row.at_counter = false;
        let count = row.counter & (!0x80);
        if count == 0 {
            // End of line
            if row.position != xsize {
                return Err(false);
            } else {
                eor = true;
            }
        }
    } else {
        let mut count = row.counter & (!0x80);
        /* Have checked that count != 0 */
        if count == 0 {
            unreachable!();
        } else if row.counter & 0x80 == 0 {
            // Expand data to `count` repeated u16s
            let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
            if overflows_row {
                return Err(true);
            }
            // The calculated write_start should be <= buf.len() and hence not overflow
            let write_start: usize = (row.position as usize) * (channels as usize);
            for i in 0..count {
                let o = write_start + (row.plane as usize) + (channels as usize) * (i as usize);
                buf_row[o] = byte;
            }
            row.position += count as u16;
            row.at_counter = true;
        } else {
            // Copy the next `count` u8s of data
            let overflows_row = row.position > xsize - 1;
            if overflows_row {
                return Err(true);
            }

            let write_start: usize =
                (row.position as usize) * (channels as usize) + (row.plane as usize);
            buf_row[write_start] = byte;

            count -= 1;
            row.position += 1;
            row.counter = 0x80 | count;
            row.at_counter = count == 0;
        }
    }
    Ok(eor)
}

/** For the given RLE scanline, process the bytes in `segment`.
 * Parameter `ending` is true iff this segment ends the scanline. */
fn process_rle_segment<const DEEP: bool>(
    buf_row: &mut [u8],
    row: &mut SgiRgbScanlineState,
    xsize: u16,
    channels: u8,
    segment: &[u8],
    ending: bool,
) -> Result<(), SgiRgbDecodeError> {
    for (i, byte) in segment.iter().enumerate() {
        let res = if DEEP {
            apply_rle16_byte(row, buf_row, *byte, xsize, channels)
        } else {
            apply_rle8_byte(row, buf_row, *byte, xsize, channels)
        };
        let eor = res.map_err(|overflow| {
            if overflow {
                SgiRgbDecodeError::ScanlineOverflow
            } else {
                SgiRgbDecodeError::ScanlineUnderflow
            }
        })?;

        let expecting_end = i + 1 == segment.len() && ending;
        if eor != expecting_end {
            if eor {
                // Row ended earlier than expected
                return Err(SgiRgbDecodeError::ScanlineTooShort);
            } else {
                // At end of scanline data, did not process a row end marker
                return Err(SgiRgbDecodeError::ScanlineTooLong);
            }
        }
    }

    Ok(())
}

/** Process the next region of the RLE-encoded image data section of the SGI image file */
fn process_data_segment<const DEEP: bool>(
    buf: &mut [u8],
    info: SgiRgbHeaderInfo,
    mut state: SgiRgbDecodeState,
    rle_table: &mut [SgiRgbScanlineState],
    segment: &[u8],
) -> Result<(SgiRgbDecodeState, bool), SgiRgbDecodeError> {
    let channels = info.color_type.channel_count();
    let start_pos = state.pos;
    let end_pos = state.pos + segment.len() as u64;

    // Add new encoded lines that intersect `segment` to the active set
    while state.cursor < rle_table.len() && rle_table[state.cursor].offset as u64 <= end_pos {
        rle_table[state.cursor].prec_active_line = state.max_active_row.unwrap_or(u32::MAX);
        state.max_active_row = Some(state.cursor as u32);
        state.cursor += 1;
    }

    let mut prev_row: Option<u32> = None;
    let Some(mut row_index) = state.max_active_row else {
        // No rows are active, nothing to do
        state.pos = end_pos;
        return Ok((state, false));
    };

    loop {
        let row = &mut rle_table[row_index as usize];
        let row_end = row.offset as u64 + row.length as u64;
        debug_assert!(row.offset as u64 <= end_pos && row_end > start_pos);

        let prev_active_row = row.prec_active_line;

        // Intersect the segment being processed with the row extents, and then
        // run the RLE state machine over the resulting intersected segment
        let rs_start: usize = if row.offset as u64 > start_pos {
            ((row.offset as u64) - start_pos) as usize
        } else {
            0
        };
        let rs_end: usize = if row_end <= end_pos {
            (row_end - start_pos) as usize
        } else {
            segment.len()
        };
        let has_ending = row_end <= end_pos;
        let row_input = &segment[rs_start..rs_end];

        let bpp = if DEEP { 2 } else { 1 };
        let stride = (info.xsize as usize) * (channels as usize) * bpp;
        let buf_row = &mut buf[((info.ysize - 1 - row.row_id) as usize) * stride
            ..((info.ysize - 1 - row.row_id) as usize) * stride + stride];

        process_rle_segment::<DEEP>(buf_row, row, info.xsize, channels, row_input, has_ending)?;

        if has_ending {
            // Mark this row as not having a preceding row, and remove
            // it from the singly linked list
            row.prec_active_line = u32::MAX;
            if let Some(r) = prev_row {
                rle_table[r as usize].prec_active_line = prev_active_row;
            } else if prev_active_row == u32::MAX {
                state.max_active_row = None;
            } else {
                state.max_active_row = Some(prev_active_row);
            }
        } else {
            prev_row = Some(row_index);
        }

        if prev_active_row != u32::MAX {
            row_index = prev_active_row;
        } else {
            break;
        }
    }

    state.pos = end_pos;
    let done = state.max_active_row.is_none() && state.cursor == rle_table.len();
    Ok((state, done))
}

impl<R: BufRead> ImageDecoder for SgiDecoder<R> {
    fn dimensions(&self) -> (u32, u32) {
        (self.info.xsize as u32, self.info.ysize as u32)
    }

    fn color_type(&self) -> ColorType {
        self.info.color_type
    }

    fn original_color_type(&self) -> ExtendedColorType {
        self.info.color_type.into()
    }

    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));

        let channels = self.info.color_type.channel_count();
        let deep = self.info.color_type.bytes_per_pixel() > channels;
        if self.info.is_rle {
            /* Tricky case: need to read the RLE offset tables to determine
             * where to find the scanlines for each plane. Images can place
             * the scanlines whereever, and processing them in logical order
             * can require a lot of seeking.
             *
             * This decoder does not have fine grained control over the
             * input stream buffering, so each seek operation could make
             * the input stream read 8 kB and/or make a syscall if the
             * input stream is a default BufReader. An image could trigger
             * one seek operation per 8 marginal bytes of data. To avoid
             * this unfortunate interaction, we load the O(height) bytes
             * of RLE tables into memory and then process the rest of the
             * image in a single pass.
             */

            /* `rle_offset_entries` has maximum value `4 * (2^16-1)` and will not overflow */
            let rle_offset_entries = (channels as u32) * (self.info.ysize as u32);

            let mut rle_table: Vec<SgiRgbScanlineState> = Vec::new();
            /* Tiny invalid images can trigger medium-size 4 * (2^16-1) allocations here;
             * this can be avoided by dynamically resizing the vector as it is read. */
            rle_table
                .try_reserve_exact(rle_offset_entries as usize)
                .map_err(|_| ImageError::Limits(LimitErrorKind::InsufficientMemory.into()))?;
            rle_table.resize(
                rle_offset_entries as usize,
                SgiRgbScanlineState {
                    offset: 0,
                    length: 0,
                    row_id: 0,
                    position: 0,
                    plane: 0,
                    counter: 0,
                    data_char_hi: 0,
                    prec_active_line: 0,
                    high_byte: deep,
                    at_counter: true,
                },
            );
            // Read offset table
            for plane in 0..channels {
                for y in 0..self.info.ysize {
                    let idx = (plane as usize) * (self.info.ysize as usize) + (y as usize);
                    let mut tmp = [0u8; 4];
                    self.reader.read_exact(&mut tmp)?;
                    rle_table[idx].offset = u32::from_be_bytes(tmp);
                    rle_table[idx].row_id = y;
                    rle_table[idx].plane = plane;
                }
            }
            // Read length table, and validate (offset, length) pairs
            for plane in 0..channels {
                for y in 0..self.info.ysize {
                    let idx = (plane as usize) * (self.info.ysize as usize) + (y as usize);
                    let mut tmp = [0u8; 4];
                    self.reader.read_exact(&mut tmp)?;
                    rle_table[idx].length = u32::from_be_bytes(tmp);

                    // Per spec, image data follows the offset tables.
                    // (although other decoders will probably read inside the offset tables
                    // if asked.)
                    let scanline_too_early = rle_table[idx].offset
                        < (HEADER_FULL_LENGTH as u32) + rle_offset_entries * 8;
                    let zero_length = rle_table[idx].length == 0;

                    if scanline_too_early || zero_length {
                        return Err(SgiRgbDecodeError::RLERowInvalid(
                            rle_table[idx].offset,
                            rle_table[idx].length,
                        )
                        .into());
                    }
                }
            }

            // Sort rows by their starting position in the stream, breaking
            // ties by their position in the buffer.
            rle_table.sort_unstable_by_key(|f| {
                (f.offset as u64) << 32 | (f.row_id as u64) << 16 | f.plane as u64
            });

            // Use explicit state for linear processing
            let mut rle_state = SgiRgbDecodeState {
                cursor: 0,
                max_active_row: None,
                pos: (HEADER_FULL_LENGTH as u64) + (rle_table.len() as u64) * 8,
            };

            loop {
                let buffer = self.reader.fill_buf()?;
                if buffer.is_empty() {
                    /* Unexpected EOF . */
                    return Err(SgiRgbDecodeError::EarlyEOF.into());
                }

                let (new_state, done) = if deep {
                    process_data_segment::<true>(buf, self.info, rle_state, &mut rle_table, buffer)?
                } else {
                    process_data_segment::<false>(
                        buf,
                        self.info,
                        rle_state,
                        &mut rle_table,
                        buffer,
                    )?
                };
                if done {
                    return Ok(());
                }
                rle_state = new_state;

                let buffer_length = buffer.len();
                self.reader.consume(buffer_length);
            }
        } else {
            /* Easy case: packed images by scanline, plane by plane  */
            if deep {
                let bpp = 2 * channels;
                // `width` will be at most `(2^16-1) * 8`, so there is never overflow
                let width = (bpp as u32) * (self.info.xsize as u32);
                for plane in 0..channels as usize {
                    for row in buf.chunks_exact_mut(width as usize).rev() {
                        for px in row.chunks_exact_mut(bpp as usize) {
                            let mut tmp = [0_u8; 2];
                            self.reader.read_exact(&mut tmp)?;
                            px[2 * plane..2 * plane + 2]
                                .copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes(tmp)));
                        }
                    }
                }
            } else {
                let width = (channels as u32) * (self.info.xsize as u32);
                for plane in 0..channels as usize {
                    for row in buf.chunks_exact_mut(width as usize).rev() {
                        for px in row.chunks_exact_mut(channels as usize) {
                            self.reader.read_exact(&mut px[plane..plane + 1])?;
                        }
                    }
                }
            }
            Ok(())
        }
    }

    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
        (*self).read_image(buf)
    }

    fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
        limits.check_support(&LimitSupport::default())?;
        let (width, height) = self.dimensions();
        limits.check_dimensions(width, height)?;

        // This will not overflow, because 8 * (2^16-1) * (2^16-1) ≤ 2^35
        let max_image_bytes = 8 * (self.info.xsize as u64) * (self.info.ysize as u64);
        // This will not overflow, because even 1 KB * (2^16-1) ≤ 2^35 ⪡ 2^64-1
        let max_table_bytes =
            (self.info.ysize as u64) * (std::mem::size_of::<SgiRgbScanlineState>() as u64);
        // This will not overflow, because it is ≤ 2^36 ⪡ 2^64-1
        let max_bytes = max_image_bytes + max_table_bytes;

        let max_alloc = limits.max_alloc.unwrap_or(u64::MAX);
        if max_alloc < max_bytes {
            return Err(ImageError::Limits(LimitError::from_kind(
                LimitErrorKind::InsufficientMemory,
            )));
        }
        Ok(())
    }
}