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};
const HEADER_FULL_LENGTH: usize = 512;
#[derive(Clone, Copy)]
struct SgiRgbHeaderInfo {
xsize: u16,
ysize: u16,
color_type: ColorType,
is_rle: bool,
}
#[derive(Debug)]
enum SgiRgbDecodeError {
BadMagic,
HeaderError,
RLERowInvalid(u32, u32), 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))
}
}
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());
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());
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));
}
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,
))
}
pub struct SgiDecoder<R> {
info: SgiRgbHeaderInfo,
reader: R,
}
impl<R> SgiDecoder<R>
where
R: BufRead,
{
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,
})
}
}
#[derive(Clone, Copy)]
struct SgiRgbScanlineState {
offset: u32,
length: u32,
row_id: u16,
position: u16,
prec_active_line: u32,
plane: u8,
counter: u8,
data_char_hi: u8,
at_counter: bool,
high_byte: bool,
}
struct SgiRgbDecodeState {
pos: u64,
max_active_row: Option<u32>,
cursor: usize,
}
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 {
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);
if count == 0 {
unreachable!();
} else if row.counter & 0x80 == 0 {
let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
if overflows_row {
return Err(true);
}
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 {
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)
}
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 {
if row.position != xsize {
return Err(false);
} else {
eor = true;
}
}
} else {
let mut count = row.counter & (!0x80);
if count == 0 {
unreachable!();
} else if row.counter & 0x80 == 0 {
let overflows_row = (count as u16) > xsize || row.position > xsize - (count as u16);
if overflows_row {
return Err(true);
}
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 {
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)
}
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 {
return Err(SgiRgbDecodeError::ScanlineTooShort);
} else {
return Err(SgiRgbDecodeError::ScanlineTooLong);
}
}
}
Ok(())
}
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;
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 {
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;
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 {
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 {
let rle_offset_entries = (channels as u32) * (self.info.ysize as u32);
let mut rle_table: Vec<SgiRgbScanlineState> = Vec::new();
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,
},
);
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;
}
}
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);
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());
}
}
}
rle_table.sort_unstable_by_key(|f| {
(f.offset as u64) << 32 | (f.row_id as u64) << 16 | f.plane as u64
});
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() {
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 {
if deep {
let bpp = 2 * channels;
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)?;
let max_image_bytes = 8 * (self.info.xsize as u64) * (self.info.ysize as u64);
let max_table_bytes =
(self.info.ysize as u64) * (std::mem::size_of::<SgiRgbScanlineState>() as u64);
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(())
}
}