1use 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
35const HEADER_FULL_LENGTH: usize = 512;
37
38#[derive(Clone, Copy)]
46struct SgiRgbHeaderInfo {
47 xsize: u16,
48 ysize: u16,
49 color_type: ColorType,
50 is_rle: bool,
51}
52
53#[derive(Debug)]
55enum SgiRgbDecodeError {
56 BadMagic,
57 HeaderError,
58 RLERowInvalid(u32, u32), 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
118fn 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 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 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 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
199pub struct SgiDecoder<R> {
201 info: SgiRgbHeaderInfo,
202 reader: R,
203}
204
205impl<R> SgiDecoder<R>
206where
207 R: BufRead,
208{
209 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#[derive(Clone, Copy)]
224struct SgiRgbScanlineState {
225 offset: u32,
227 length: u32,
229 row_id: u16,
231 position: u16,
233 prec_active_line: u32,
237 plane: u8,
239 counter: u8,
242 data_char_hi: u8,
244 at_counter: bool,
246 high_byte: bool,
248}
249
250struct SgiRgbDecodeState {
252 pos: u64,
255 max_active_row: Option<u32>,
257 cursor: usize,
259}
260
261fn 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 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 if count == 0 {
293 unreachable!();
294 } else if row.counter & 0x80 == 0 {
295 let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
297 if overflows_row {
298 return Err(true);
299 }
300 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 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
332fn 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 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 if count == 0 {
359 unreachable!();
360 } else if row.counter & 0x80 == 0 {
361 let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
363 if overflows_row {
364 return Err(true);
365 }
366 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 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
394fn 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 return Err(SgiRgbDecodeError::ScanlineTooShort);
423 } else {
424 return Err(SgiRgbDecodeError::ScanlineTooLong);
426 }
427 }
428 }
429
430 Ok(())
431}
432
433fn 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 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 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 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 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 let rle_offset_entries = (channels as u32) * (self.info.ysize as u32);
551
552 let mut rle_table: Vec<SgiRgbScanlineState> = Vec::new();
553 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 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 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 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 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 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 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 if deep {
651 let bpp = 2 * channels;
652 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 let max_image_bytes = 8 * (self.info.xsize as u64) * (self.info.ysize as u64);
689 let max_table_bytes =
691 (self.info.ysize as u64) * (std::mem::size_of::<SgiRgbScanlineState>() as u64);
692 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}