use crate::Primitive; use num_traits::identities::Zero; #[cfg(test)] use std::borrow::Cow; use std::convert::TryFrom; use std::io::{self, BufRead, Cursor, Read, Seek}; use std::iter::Iterator; use std::marker::PhantomData; use std::num::{ParseFloatError, ParseIntError}; use std::path::Path; use std::{error, fmt, mem}; use crate::color::{ColorType, Rgb}; use crate::error::{ DecodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageFormat, Progress}; /// Errors that can occur during decoding and parsing of a HDR image #[derive(Debug, Clone, PartialEq, Eq)] enum DecoderError { /// HDR's "#?RADIANCE" signature wrong or missing RadianceHdrSignatureInvalid, /// EOF before end of header TruncatedHeader, /// EOF instead of image dimensions TruncatedDimensions, /// A value couldn't be parsed UnparsableF32(LineType, ParseFloatError), /// A value couldn't be parsed UnparsableU32(LineType, ParseIntError), /// Not enough numbers in line LineTooShort(LineType), /// COLORCORR contains too many numbers in strict mode ExtraneousColorcorrNumbers, /// Dimensions line had too few elements DimensionsLineTooShort(usize, usize), /// Dimensions line had too many elements DimensionsLineTooLong(usize), /// The length of a scanline (1) wasn't a match for the specified length (2) WrongScanlineLength(usize, usize), /// First pixel of a scanline is a run length marker FirstPixelRlMarker, } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::RadianceHdrSignatureInvalid => { f.write_str("Radiance HDR signature not found") } DecoderError::TruncatedHeader => f.write_str("EOF in header"), DecoderError::TruncatedDimensions => f.write_str("EOF in dimensions line"), DecoderError::UnparsableF32(line, pe) => { f.write_fmt(format_args!("Cannot parse {} value as f32: {}", line, pe)) } DecoderError::UnparsableU32(line, pe) => { f.write_fmt(format_args!("Cannot parse {} value as u32: {}", line, pe)) } DecoderError::LineTooShort(line) => { f.write_fmt(format_args!("Not enough numbers in {}", line)) } DecoderError::ExtraneousColorcorrNumbers => f.write_str("Extra numbers in COLORCORR"), DecoderError::DimensionsLineTooShort(elements, expected) => f.write_fmt(format_args!( "Dimensions line too short: have {} elements, expected {}", elements, expected )), DecoderError::DimensionsLineTooLong(expected) => f.write_fmt(format_args!( "Dimensions line too long, expected {} elements", expected )), DecoderError::WrongScanlineLength(len, expected) => f.write_fmt(format_args!( "Wrong length of decoded scanline: got {}, expected {}", len, expected )), DecoderError::FirstPixelRlMarker => { f.write_str("First pixel of a scanline shouldn't be run length marker") } } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Hdr.into(), e)) } } impl error::Error for DecoderError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { DecoderError::UnparsableF32(_, err) => Some(err), DecoderError::UnparsableU32(_, err) => Some(err), _ => None, } } } /// Lines which contain parsable data that can fail #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum LineType { Exposure, Pixaspect, Colorcorr, DimensionsHeight, DimensionsWidth, } impl fmt::Display for LineType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { LineType::Exposure => "EXPOSURE", LineType::Pixaspect => "PIXASPECT", LineType::Colorcorr => "COLORCORR", LineType::DimensionsHeight => "height dimension", LineType::DimensionsWidth => "width dimension", }) } } /// Adapter to conform to `ImageDecoder` trait #[derive(Debug)] pub struct HdrAdapter { inner: Option>, // data: Option>, meta: HdrMetadata, } impl HdrAdapter { /// Creates adapter pub fn new(r: R) -> ImageResult> { let decoder = HdrDecoder::new(r)?; let meta = decoder.metadata(); Ok(HdrAdapter { inner: Some(decoder), meta, }) } /// Allows reading old Radiance HDR images pub fn new_nonstrict(r: R) -> ImageResult> { let decoder = HdrDecoder::with_strictness(r, false)?; let meta = decoder.metadata(); Ok(HdrAdapter { inner: Some(decoder), meta, }) } /// Read the actual data of the image, and store it in Self::data. fn read_image_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self.inner.take() { Some(decoder) => { let img: Vec> = decoder.read_image_ldr()?; for (i, Rgb(data)) in img.into_iter().enumerate() { buf[(i * 3)..][..3].copy_from_slice(&data); } Ok(()) } None => Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::NoMoreData, ))), } } } /// Wrapper struct around a `Cursor>` pub struct HdrReader(Cursor>, PhantomData); impl Read for HdrReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + BufRead> ImageDecoder<'a> for HdrAdapter { type Reader = HdrReader; fn dimensions(&self) -> (u32, u32) { (self.meta.width, self.meta.height) } fn color_type(&self) -> ColorType { ColorType::Rgb8 } fn into_reader(self) -> ImageResult { Ok(HdrReader( Cursor::new(image::decoder_to_vec(self)?), PhantomData, )) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { self.read_image_data(buf) } } impl<'a, R: 'a + BufRead + Seek> ImageDecoderRect<'a> for HdrAdapter { fn read_rect_with_progress( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { image::load_rect( x, y, width, height, buf, progress_callback, self, |_, _| unreachable!(), |s, buf| s.read_image_data(buf), ) } } /// Radiance HDR file signature pub const SIGNATURE: &[u8] = b"#?RADIANCE"; const SIGNATURE_LENGTH: usize = 10; /// An Radiance HDR decoder #[derive(Debug)] pub struct HdrDecoder { r: R, width: u32, height: u32, meta: HdrMetadata, } /// Refer to [wikipedia](https://en.wikipedia.org/wiki/RGBE_image_format) #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Rgbe8Pixel { /// Color components pub c: [u8; 3], /// Exponent pub e: u8, } /// Creates `Rgbe8Pixel` from components pub fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel { Rgbe8Pixel { c: [r, g, b], e } } impl Rgbe8Pixel { /// Converts `Rgbe8Pixel` into `Rgb` linearly #[inline] pub fn to_hdr(self) -> Rgb { if self.e == 0 { Rgb([0.0, 0.0, 0.0]) } else { // let exp = f32::ldexp(1., self.e as isize - (128 + 8)); // unstable let exp = f32::exp2(>::from(self.e) - (128.0 + 8.0)); Rgb([ exp * >::from(self.c[0]), exp * >::from(self.c[1]), exp * >::from(self.c[2]), ]) } } /// Converts `Rgbe8Pixel` into `Rgb` with scale=1 and gamma=2.2 /// /// color_ldr = (color_hdr*scale)gamma /// /// # Panic /// /// Panics when `T::max_value()` cannot be represented as f32. #[inline] pub fn to_ldr(self) -> Rgb { self.to_ldr_scale_gamma(1.0, 2.2) } /// Converts `Rgbe8Pixel` into `Rgb` using provided scale and gamma /// /// color_ldr = (color_hdr*scale)gamma /// /// # Panic /// /// Panics when `T::max_value()` cannot be represented as f32. /// Panics when scale or gamma is NaN #[inline] pub fn to_ldr_scale_gamma(self, scale: f32, gamma: f32) -> Rgb { let Rgb(data) = self.to_hdr(); let (r, g, b) = (data[0], data[1], data[2]); #[inline] fn sg(v: f32, scale: f32, gamma: f32) -> T { let t_max = T::max_value(); // Disassembly shows that t_max_f32 is compiled into constant let t_max_f32: f32 = num_traits::NumCast::from(t_max) .expect("to_ldr_scale_gamma: maximum value of type is not representable as f32"); let fv = f32::powf(v * scale, gamma) * t_max_f32 + 0.5; if fv < 0.0 { T::zero() } else if fv > t_max_f32 { t_max } else { num_traits::NumCast::from(fv) .expect("to_ldr_scale_gamma: cannot convert f32 to target type. NaN?") } } Rgb([ sg(r, scale, gamma), sg(g, scale, gamma), sg(b, scale, gamma), ]) } } impl HdrDecoder { /// Reads Radiance HDR image header from stream `r` /// if the header is valid, creates HdrDecoder /// strict mode is enabled pub fn new(reader: R) -> ImageResult> { HdrDecoder::with_strictness(reader, true) } /// Reads Radiance HDR image header from stream `reader`, /// if the header is valid, creates `HdrDecoder`. /// /// strict enables strict mode /// /// Warning! Reading wrong file in non-strict mode /// could consume file size worth of memory in the process. pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult> { let mut attributes = HdrMetadata::new(); { // scope to make borrowck happy let r = &mut reader; if strict { let mut signature = [0; SIGNATURE_LENGTH]; r.read_exact(&mut signature)?; if signature != SIGNATURE { return Err(DecoderError::RadianceHdrSignatureInvalid.into()); } // no else // skip signature line ending read_line_u8(r)?; } else { // Old Radiance HDR files (*.pic) don't use signature // Let them be parsed in non-strict mode } // read header data until empty line loop { match read_line_u8(r)? { None => { // EOF before end of header return Err(DecoderError::TruncatedHeader.into()); } Some(line) => { if line.is_empty() { // end of header break; } else if line[0] == b'#' { // line[0] will not panic, line.len() == 0 is false here // skip comments continue; } // no else // process attribute line let line = String::from_utf8_lossy(&line[..]); attributes.update_header_info(&line, strict)?; } // <= Some(line) } // match read_line_u8() } // loop } // scope to end borrow of reader // parse dimensions let (width, height) = match read_line_u8(&mut reader)? { None => { // EOF instead of image dimensions return Err(DecoderError::TruncatedDimensions.into()); } Some(dimensions) => { let dimensions = String::from_utf8_lossy(&dimensions[..]); parse_dimensions_line(&dimensions, strict)? } }; // color type is always rgb8 if crate::utils::check_dimension_overflow(width, height, ColorType::Rgb8.bytes_per_pixel()) { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Hdr.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}x{}) are too large", width, height )), ), )); } Ok(HdrDecoder { r: reader, width, height, meta: HdrMetadata { width, height, ..attributes }, }) } // end with_strictness /// Returns file metadata. Refer to `HdrMetadata` for details. pub fn metadata(&self) -> HdrMetadata { self.meta.clone() } /// Consumes decoder and returns a vector of RGBE8 pixels pub fn read_image_native(mut self) -> ImageResult> { // Don't read anything if image is empty if self.width == 0 || self.height == 0 { return Ok(vec![]); } // expression self.width > 0 && self.height > 0 is true from now to the end of this method let pixel_count = self.width as usize * self.height as usize; let mut ret = vec![Default::default(); pixel_count]; for chunk in ret.chunks_mut(self.width as usize) { read_scanline(&mut self.r, chunk)?; } Ok(ret) } /// Consumes decoder and returns a vector of transformed pixels pub fn read_image_transform T>( mut self, f: F, output_slice: &mut [T], ) -> ImageResult<()> { assert_eq!( output_slice.len(), self.width as usize * self.height as usize ); // Don't read anything if image is empty if self.width == 0 || self.height == 0 { return Ok(()); } let chunks_iter = output_slice.chunks_mut(self.width as usize); let mut buf = vec![Default::default(); self.width as usize]; for chunk in chunks_iter { // read_scanline overwrites the entire buffer or returns an Err, // so not resetting the buffer here is ok. read_scanline(&mut self.r, &mut buf[..])?; for (dst, &pix) in chunk.iter_mut().zip(buf.iter()) { *dst = f(pix); } } Ok(()) } /// Consumes decoder and returns a vector of `Rgb` pixels. /// scale = 1, gamma = 2.2 pub fn read_image_ldr(self) -> ImageResult>> { let mut ret = vec![Rgb([0, 0, 0]); self.width as usize * self.height as usize]; self.read_image_transform(|pix| pix.to_ldr(), &mut ret[..])?; Ok(ret) } /// Consumes decoder and returns a vector of `Rgb` pixels. /// pub fn read_image_hdr(self) -> ImageResult>> { let mut ret = vec![Rgb([0.0, 0.0, 0.0]); self.width as usize * self.height as usize]; self.read_image_transform(|pix| pix.to_hdr(), &mut ret[..])?; Ok(ret) } } impl IntoIterator for HdrDecoder { type Item = ImageResult; type IntoIter = HdrImageDecoderIterator; fn into_iter(self) -> Self::IntoIter { HdrImageDecoderIterator { r: self.r, scanline_cnt: self.height as usize, buf: vec![Default::default(); self.width as usize], col: 0, scanline: 0, trouble: true, // make first call to `next()` read scanline error_encountered: false, } } } /// Scanline buffered pixel by pixel iterator pub struct HdrImageDecoderIterator { r: R, scanline_cnt: usize, buf: Vec, // scanline buffer col: usize, // current position in scanline scanline: usize, // current scanline trouble: bool, // optimization, true indicates that we need to check something error_encountered: bool, } impl HdrImageDecoderIterator { // Advances counter to the next pixel #[inline] fn advance(&mut self) { self.col += 1; if self.col == self.buf.len() { self.col = 0; self.scanline += 1; self.trouble = true; } } } impl Iterator for HdrImageDecoderIterator { type Item = ImageResult; fn next(&mut self) -> Option { if !self.trouble { let ret = self.buf[self.col]; self.advance(); Some(Ok(ret)) } else { // some condition is pending if self.buf.is_empty() || self.scanline == self.scanline_cnt { // No more pixels return None; } // no else if self.error_encountered { self.advance(); // Error was encountered. Keep producing errors. // ImageError can't implement Clone, so just dump some error return Some(Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::FailedAlready, )))); } // no else if self.col == 0 { // fill scanline buffer match read_scanline(&mut self.r, &mut self.buf[..]) { Ok(_) => { // no action required } Err(err) => { self.advance(); self.error_encountered = true; self.trouble = true; return Some(Err(err)); } } } // no else self.trouble = false; let ret = self.buf[0]; self.advance(); Some(Ok(ret)) } } fn size_hint(&self) -> (usize, Option) { let total_cnt = self.buf.len() * self.scanline_cnt; let cur_cnt = self.buf.len() * self.scanline + self.col; let remaining = total_cnt - cur_cnt; (remaining, Some(remaining)) } } impl ExactSizeIterator for HdrImageDecoderIterator {} // Precondition: buf.len() > 0 fn read_scanline(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> { assert!(!buf.is_empty()); let width = buf.len(); // first 4 bytes in scanline allow to determine compression method let fb = read_rgbe(r)?; if fb.c[0] == 2 && fb.c[1] == 2 && fb.c[2] < 128 { // denormalized pixel value (2,2,<128,_) indicates new per component RLE method // decode_component guarantees that offset is within 0 .. width // therefore we can skip bounds checking here, but we will not decode_component(r, width, |offset, value| buf[offset].c[0] = value)?; decode_component(r, width, |offset, value| buf[offset].c[1] = value)?; decode_component(r, width, |offset, value| buf[offset].c[2] = value)?; decode_component(r, width, |offset, value| buf[offset].e = value)?; } else { // old RLE method (it was considered old around 1991, should it be here?) decode_old_rle(r, fb, buf)?; } Ok(()) } #[inline(always)] fn read_byte(r: &mut R) -> io::Result { let mut buf = [0u8]; r.read_exact(&mut buf[..])?; Ok(buf[0]) } // Guarantees that first parameter of set_component will be within pos .. pos+width #[inline] fn decode_component( r: &mut R, width: usize, mut set_component: S, ) -> ImageResult<()> { let mut buf = [0; 128]; let mut pos = 0; while pos < width { // increment position by a number of decompressed values pos += { let rl = read_byte(r)?; if rl <= 128 { // sanity check if pos + rl as usize > width { return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into()); } // read values r.read_exact(&mut buf[0..rl as usize])?; for (offset, &value) in buf[0..rl as usize].iter().enumerate() { set_component(pos + offset, value); } rl as usize } else { // run let rl = rl - 128; // sanity check if pos + rl as usize > width { return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into()); } // fill with same value let value = read_byte(r)?; for offset in 0..rl as usize { set_component(pos + offset, value); } rl as usize } }; } if pos != width { return Err(DecoderError::WrongScanlineLength(pos, width).into()); } Ok(()) } // Decodes scanline, places it into buf // Precondition: buf.len() > 0 // fb - first 4 bytes of scanline fn decode_old_rle(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> { assert!(!buf.is_empty()); let width = buf.len(); // convenience function. // returns run length if pixel is a run length marker #[inline] fn rl_marker(pix: Rgbe8Pixel) -> Option { if pix.c == [1, 1, 1] { Some(pix.e as usize) } else { None } } // first pixel in scanline should not be run length marker // it is error if it is if rl_marker(fb).is_some() { return Err(DecoderError::FirstPixelRlMarker.into()); } buf[0] = fb; // set first pixel of scanline let mut x_off = 1; // current offset from beginning of a scanline let mut rl_mult = 1; // current run length multiplier let mut prev_pixel = fb; while x_off < width { let pix = read_rgbe(r)?; // it's harder to forget to increase x_off if I write this this way. x_off += { if let Some(rl) = rl_marker(pix) { // rl_mult takes care of consecutive RL markers let rl = rl * rl_mult; rl_mult *= 256; if x_off + rl <= width { // do run for b in &mut buf[x_off..x_off + rl] { *b = prev_pixel; } } else { return Err(DecoderError::WrongScanlineLength(x_off + rl, width).into()); }; rl // value to increase x_off by } else { rl_mult = 1; // chain of consecutive RL markers is broken prev_pixel = pix; buf[x_off] = pix; 1 // value to increase x_off by } }; } if x_off != width { return Err(DecoderError::WrongScanlineLength(x_off, width).into()); } Ok(()) } fn read_rgbe(r: &mut R) -> io::Result { let mut buf = [0u8; 4]; r.read_exact(&mut buf[..])?; Ok(Rgbe8Pixel { c: [buf[0], buf[1], buf[2]], e: buf[3], }) } /// Metadata for Radiance HDR image #[derive(Debug, Clone)] pub struct HdrMetadata { /// Width of decoded image. It could be either scanline length, /// or scanline count, depending on image orientation. pub width: u32, /// Height of decoded image. It depends on orientation too. pub height: u32, /// Orientation matrix. For standard orientation it is ((1,0),(0,1)) - left to right, top to bottom. /// First pair tells how resulting pixel coordinates change along a scanline. /// Second pair tells how they change from one scanline to the next. pub orientation: ((i8, i8), (i8, i8)), /// Divide color values by exposure to get to get physical radiance in /// watts/steradian/m2 /// /// Image may not contain physical data, even if this field is set. pub exposure: Option, /// Divide color values by corresponding tuple member (r, g, b) to get to get physical radiance /// in watts/steradian/m2 /// /// Image may not contain physical data, even if this field is set. pub color_correction: Option<(f32, f32, f32)>, /// Pixel height divided by pixel width pub pixel_aspect_ratio: Option, /// All lines contained in image header are put here. Ordering of lines is preserved. /// Lines in the form "key=value" are represented as ("key", "value"). /// All other lines are ("", "line") pub custom_attributes: Vec<(String, String)>, } impl HdrMetadata { fn new() -> HdrMetadata { HdrMetadata { width: 0, height: 0, orientation: ((1, 0), (0, 1)), exposure: None, color_correction: None, pixel_aspect_ratio: None, custom_attributes: vec![], } } // Updates header info, in strict mode returns error for malformed lines (no '=' separator) // unknown attributes are skipped fn update_header_info(&mut self, line: &str, strict: bool) -> ImageResult<()> { // split line at first '=' // old Radiance HDR files (*.pic) feature tabs in key, so vvv trim let maybe_key_value = split_at_first(line, "=").map(|(key, value)| (key.trim(), value)); // save all header lines in custom_attributes match maybe_key_value { Some((key, val)) => self .custom_attributes .push((key.to_owned(), val.to_owned())), None => self.custom_attributes.push(("".into(), line.to_owned())), } // parse known attributes match maybe_key_value { Some(("FORMAT", val)) => { if val.trim() != "32-bit_rle_rgbe" { // XYZE isn't supported yet return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Hdr.into(), UnsupportedErrorKind::Format(ImageFormatHint::Name(limit_string_len( val, 20, ))), ), )); } } Some(("EXPOSURE", val)) => { match val.trim().parse::() { Ok(v) => { self.exposure = Some(self.exposure.unwrap_or(1.0) * v); // all encountered exposure values should be multiplied } Err(parse_error) => { if strict { return Err(DecoderError::UnparsableF32( LineType::Exposure, parse_error, ) .into()); } // no else, skip this line in non-strict mode } }; } Some(("PIXASPECT", val)) => { match val.trim().parse::() { Ok(v) => { self.pixel_aspect_ratio = Some(self.pixel_aspect_ratio.unwrap_or(1.0) * v); // all encountered exposure values should be multiplied } Err(parse_error) => { if strict { return Err(DecoderError::UnparsableF32( LineType::Pixaspect, parse_error, ) .into()); } // no else, skip this line in non-strict mode } }; } Some(("COLORCORR", val)) => { let mut rgbcorr = [1.0, 1.0, 1.0]; match parse_space_separated_f32(val, &mut rgbcorr, LineType::Colorcorr) { Ok(extra_numbers) => { if strict && extra_numbers { return Err(DecoderError::ExtraneousColorcorrNumbers.into()); } // no else, just ignore extra numbers let (rc, gc, bc) = self.color_correction.unwrap_or((1.0, 1.0, 1.0)); self.color_correction = Some((rc * rgbcorr[0], gc * rgbcorr[1], bc * rgbcorr[2])); } Err(err) => { if strict { return Err(err); } // no else, skip malformed line in non-strict mode } } } None => { // old Radiance HDR files (*.pic) contain commands in a header // just skip them } _ => { // skip unknown attribute } } // match attributes Ok(()) } } fn parse_space_separated_f32(line: &str, vals: &mut [f32], line_tp: LineType) -> ImageResult { let mut nums = line.split_whitespace(); for val in vals.iter_mut() { if let Some(num) = nums.next() { match num.parse::() { Ok(v) => *val = v, Err(err) => return Err(DecoderError::UnparsableF32(line_tp, err).into()), } } else { // not enough numbers in line return Err(DecoderError::LineTooShort(line_tp).into()); } } Ok(nums.next().is_some()) } // Parses dimension line "-Y height +X width" // returns (width, height) or error fn parse_dimensions_line(line: &str, strict: bool) -> ImageResult<(u32, u32)> { const DIMENSIONS_COUNT: usize = 4; let mut dim_parts = line.split_whitespace(); let c1_tag = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(0, DIMENSIONS_COUNT))?; let c1_str = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(1, DIMENSIONS_COUNT))?; let c2_tag = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(2, DIMENSIONS_COUNT))?; let c2_str = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(3, DIMENSIONS_COUNT))?; if strict && dim_parts.next().is_some() { // extra data in dimensions line return Err(DecoderError::DimensionsLineTooLong(DIMENSIONS_COUNT).into()); } // no else // dimensions line is in the form "-Y 10 +X 20" // There are 8 possible orientations: +Y +X, +X -Y and so on match (c1_tag, c2_tag) { ("-Y", "+X") => { // Common orientation (left-right, top-down) // c1_str is height, c2_str is width let height = c1_str .parse::() .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsHeight, pe))?; let width = c2_str .parse::() .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsWidth, pe))?; Ok((width, height)) } _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Hdr.into(), UnsupportedErrorKind::GenericFeature(format!( "Orientation {} {}", limit_string_len(c1_tag, 4), limit_string_len(c2_tag, 4) )), ), )), } // final expression. Returns value } // Returns string with no more than len+3 characters fn limit_string_len(s: &str, len: usize) -> String { let s_char_len = s.chars().count(); if s_char_len > len { s.chars().take(len).chain("...".chars()).collect() } else { s.into() } } // Splits string into (before separator, after separator) tuple // or None if separator isn't found fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> { match s.find(separator) { None | Some(0) => None, Some(p) if p >= s.len() - separator.len() => None, Some(p) => Some((&s[..p], &s[(p + separator.len())..])), } } #[test] fn split_at_first_test() { assert_eq!(split_at_first(&Cow::Owned("".into()), "="), None); assert_eq!(split_at_first(&Cow::Owned("=".into()), "="), None); assert_eq!(split_at_first(&Cow::Owned("= ".into()), "="), None); assert_eq!( split_at_first(&Cow::Owned(" = ".into()), "="), Some((" ", " ")) ); assert_eq!( split_at_first(&Cow::Owned("EXPOSURE= ".into()), "="), Some(("EXPOSURE", " ")) ); assert_eq!( split_at_first(&Cow::Owned("EXPOSURE= =".into()), "="), Some(("EXPOSURE", " =")) ); assert_eq!( split_at_first(&Cow::Owned("EXPOSURE== =".into()), "=="), Some(("EXPOSURE", " =")) ); assert_eq!(split_at_first(&Cow::Owned("EXPOSURE".into()), ""), None); } // Reads input until b"\n" or EOF // Returns vector of read bytes NOT including end of line characters // or return None to indicate end of file fn read_line_u8(r: &mut R) -> ::std::io::Result>> { let mut ret = Vec::with_capacity(16); match r.read_until(b'\n', &mut ret) { Ok(0) => Ok(None), Ok(_) => { if let Some(&b'\n') = ret[..].last() { let _ = ret.pop(); } Ok(Some(ret)) } Err(err) => Err(err), } } #[test] fn read_line_u8_test() { let buf: Vec<_> = (&b"One\nTwo\nThree\nFour\n\n\n"[..]).into(); let input = &mut ::std::io::Cursor::new(buf); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"One"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Two"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Three"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Four"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]); assert_eq!(read_line_u8(input).unwrap(), None); } /// Helper function for reading raw 3-channel f32 images pub fn read_raw_file>(path: P) -> ::std::io::Result>> { use byteorder::{LittleEndian as LE, ReadBytesExt}; use std::fs::File; use std::io::BufReader; let mut r = BufReader::new(File::open(path)?); let w = r.read_u32::()? as usize; let h = r.read_u32::()? as usize; let c = r.read_u32::()? as usize; assert_eq!(c, 3); let cnt = w * h; let mut ret = Vec::with_capacity(cnt); for _ in 0..cnt { let cr = r.read_f32::()?; let cg = r.read_f32::()?; let cb = r.read_f32::()?; ret.push(Rgb([cr, cg, cb])); } Ok(ret) } #[cfg(test)] mod test { use super::*; use std::io::Cursor; #[test] fn dimension_overflow() { let data = b"#?RADIANCE\nFORMAT=32-bit_rle_rgbe\n\n -Y 4294967295 +X 4294967295"; assert!(HdrAdapter::new(Cursor::new(data)).is_err()); assert!(HdrAdapter::new_nonstrict(Cursor::new(data)).is_err()); } }