1use std::io::{self, Read};
2
3use std::num::{ParseFloatError, ParseIntError};
4use std::{error, fmt};
5
6use crate::error::{
7 DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind,
8};
9use crate::{ColorType, ImageDecoder, ImageFormat, Rgb};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13enum DecoderError {
14 RadianceHdrSignatureInvalid,
16 TruncatedHeader,
18 TruncatedDimensions,
20
21 UnparsableF32(LineType, ParseFloatError),
23 UnparsableU32(LineType, ParseIntError),
25 LineTooShort(LineType),
27
28 ExtraneousColorcorrNumbers,
30
31 DimensionsLineTooShort(usize, usize),
33 DimensionsLineTooLong(usize),
35
36 WrongScanlineLength(usize, usize),
38 FirstPixelRlMarker,
40}
41
42impl fmt::Display for DecoderError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 DecoderError::RadianceHdrSignatureInvalid => {
46 f.write_str("Radiance HDR signature not found")
47 }
48 DecoderError::TruncatedHeader => f.write_str("EOF in header"),
49 DecoderError::TruncatedDimensions => f.write_str("EOF in dimensions line"),
50 DecoderError::UnparsableF32(line, pe) => {
51 f.write_fmt(format_args!("Cannot parse {line} value as f32: {pe}"))
52 }
53 DecoderError::UnparsableU32(line, pe) => {
54 f.write_fmt(format_args!("Cannot parse {line} value as u32: {pe}"))
55 }
56 DecoderError::LineTooShort(line) => {
57 f.write_fmt(format_args!("Not enough numbers in {line}"))
58 }
59 DecoderError::ExtraneousColorcorrNumbers => f.write_str("Extra numbers in COLORCORR"),
60 DecoderError::DimensionsLineTooShort(elements, expected) => f.write_fmt(format_args!(
61 "Dimensions line too short: have {elements} elements, expected {expected}"
62 )),
63 DecoderError::DimensionsLineTooLong(expected) => f.write_fmt(format_args!(
64 "Dimensions line too long, expected {expected} elements"
65 )),
66 DecoderError::WrongScanlineLength(len, expected) => f.write_fmt(format_args!(
67 "Wrong length of decoded scanline: got {len}, expected {expected}"
68 )),
69 DecoderError::FirstPixelRlMarker => {
70 f.write_str("First pixel of a scanline shouldn't be run length marker")
71 }
72 }
73 }
74}
75
76impl From<DecoderError> for ImageError {
77 fn from(e: DecoderError) -> ImageError {
78 ImageError::Decoding(DecodingError::new(ImageFormat::Hdr.into(), e))
79 }
80}
81
82impl error::Error for DecoderError {
83 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
84 match self {
85 DecoderError::UnparsableF32(_, err) => Some(err),
86 DecoderError::UnparsableU32(_, err) => Some(err),
87 _ => None,
88 }
89 }
90}
91
92#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
94enum LineType {
95 Exposure,
96 Pixaspect,
97 Colorcorr,
98 DimensionsHeight,
99 DimensionsWidth,
100}
101
102impl fmt::Display for LineType {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str(match self {
105 LineType::Exposure => "EXPOSURE",
106 LineType::Pixaspect => "PIXASPECT",
107 LineType::Colorcorr => "COLORCORR",
108 LineType::DimensionsHeight => "height dimension",
109 LineType::DimensionsWidth => "width dimension",
110 })
111 }
112}
113
114pub const SIGNATURE: &[u8] = b"#?RADIANCE";
116const SIGNATURE_LENGTH: usize = 10;
117
118#[derive(Debug)]
120pub struct HdrDecoder<R> {
121 r: R,
122 meta: HdrMetadata,
123}
124
125#[repr(C)]
127#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
128pub(crate) struct Rgbe8Pixel {
129 pub(crate) c: [u8; 3],
131 pub(crate) e: u8,
133}
134
135pub(crate) fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel {
137 Rgbe8Pixel { c: [r, g, b], e }
138}
139
140impl Rgbe8Pixel {
141 #[inline]
143 pub(crate) fn to_hdr(self) -> Rgb<f32> {
144 let exp = f32::from_bits(if self.e > 1 {
150 ((self.e - 1) as u32) << 23
151 } else {
152 (self.e as u32) << 22
153 }) * 0.00390625;
154
155 Rgb([
156 exp * <f32 as From<_>>::from(self.c[0]),
157 exp * <f32 as From<_>>::from(self.c[1]),
158 exp * <f32 as From<_>>::from(self.c[2]),
159 ])
160 }
161}
162
163impl<R: Read> HdrDecoder<R> {
164 pub fn new(reader: R) -> ImageResult<Self> {
168 HdrDecoder::with_strictness(reader, true)
169 }
170
171 pub fn new_nonstrict(reader: R) -> ImageResult<Self> {
173 Self::with_strictness(reader, false)
174 }
175
176 pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult<HdrDecoder<R>> {
184 let mut attributes = HdrMetadata::new();
185
186 {
187 let r = &mut reader;
189 if strict {
190 let mut signature = [0; SIGNATURE_LENGTH];
191 r.read_exact(&mut signature)?;
192 if signature != SIGNATURE {
193 return Err(DecoderError::RadianceHdrSignatureInvalid.into());
194 } read_line_u8(r)?;
197 } else {
198 }
201 loop {
203 match read_line_u8(r)? {
204 None => {
205 return Err(DecoderError::TruncatedHeader.into());
207 }
208 Some(line) => {
209 if line.is_empty() {
210 break;
212 } else if line[0] == b'#' {
213 continue;
216 } let line = String::from_utf8_lossy(&line[..]);
219 attributes.update_header_info(&line, strict)?;
220 } } } } let (width, height) = match read_line_u8(&mut reader)? {
226 None => {
227 return Err(DecoderError::TruncatedDimensions.into());
229 }
230 Some(dimensions) => {
231 let dimensions = String::from_utf8_lossy(&dimensions[..]);
232 parse_dimensions_line(&dimensions, strict)?
233 }
234 };
235
236 if crate::utils::check_dimension_overflow(width, height, ColorType::Rgb8.bytes_per_pixel())
238 {
239 return Err(ImageError::Unsupported(
240 UnsupportedError::from_format_and_kind(
241 ImageFormat::Hdr.into(),
242 UnsupportedErrorKind::GenericFeature(format!(
243 "Image dimensions ({width}x{height}) are too large"
244 )),
245 ),
246 ));
247 }
248
249 Ok(HdrDecoder {
250 r: reader,
251
252 meta: HdrMetadata {
253 width,
254 height,
255 ..attributes
256 },
257 })
258 } pub fn metadata(&self) -> HdrMetadata {
262 self.meta.clone()
263 }
264}
265
266impl<R: Read> ImageDecoder for HdrDecoder<R> {
267 fn dimensions(&self) -> (u32, u32) {
268 (self.meta.width, self.meta.height)
269 }
270
271 fn color_type(&self) -> ColorType {
272 ColorType::Rgb32F
273 }
274
275 fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
276 assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
277
278 if self.meta.width == 0 || self.meta.height == 0 {
280 return Ok(());
281 }
282
283 let mut scanline = vec![Default::default(); self.meta.width as usize];
284
285 const PIXEL_SIZE: usize = size_of::<Rgb<f32>>();
286 let line_bytes = self.meta.width as usize * PIXEL_SIZE;
287
288 let chunks_iter = buf.chunks_exact_mut(line_bytes);
289 for chunk in chunks_iter {
290 read_scanline(&mut self.r, &mut scanline[..])?;
293 let dst_chunks = chunk.as_chunks_mut::<PIXEL_SIZE>().0.iter_mut();
294 for (dst, &pix) in dst_chunks.zip(scanline.iter()) {
295 dst.copy_from_slice(bytemuck::cast_slice(&pix.to_hdr().0));
296 }
297 }
298
299 Ok(())
300 }
301
302 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
303 (*self).read_image(buf)
304 }
305}
306
307fn read_scanline<R: Read>(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
309 assert!(!buf.is_empty());
310 let width = buf.len();
311 let fb = read_rgbe(r)?;
313 if fb.c[0] == 2 && fb.c[1] == 2 && fb.c[2] < 128 {
314 decode_component(r, width, |offset, value| buf[offset].c[0] = value)?;
318 decode_component(r, width, |offset, value| buf[offset].c[1] = value)?;
319 decode_component(r, width, |offset, value| buf[offset].c[2] = value)?;
320 decode_component(r, width, |offset, value| buf[offset].e = value)?;
321 } else {
322 decode_old_rle(r, fb, buf)?;
324 }
325 Ok(())
326}
327
328#[inline(always)]
329fn read_byte<R: Read>(r: &mut R) -> io::Result<u8> {
330 let mut buf = [0u8];
331 r.read_exact(&mut buf[..])?;
332 Ok(buf[0])
333}
334
335#[inline]
337fn decode_component<R: Read, S: FnMut(usize, u8)>(
338 r: &mut R,
339 width: usize,
340 mut set_component: S,
341) -> ImageResult<()> {
342 let mut buf = [0; 128];
343 let mut pos = 0;
344 while pos < width {
345 pos += {
347 let rl = read_byte(r)?;
348 if rl <= 128 {
349 if pos + rl as usize > width {
351 return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
352 }
353 r.read_exact(&mut buf[0..rl as usize])?;
355 for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
356 set_component(pos + offset, value);
357 }
358 rl as usize
359 } else {
360 let rl = rl - 128;
362 if pos + rl as usize > width {
364 return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
365 }
366 let value = read_byte(r)?;
368 for offset in 0..rl as usize {
369 set_component(pos + offset, value);
370 }
371 rl as usize
372 }
373 };
374 }
375 if pos != width {
376 return Err(DecoderError::WrongScanlineLength(pos, width).into());
377 }
378 Ok(())
379}
380
381fn decode_old_rle<R: Read>(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
385 assert!(!buf.is_empty());
386 let width = buf.len();
387 #[inline]
390 fn rl_marker(pix: Rgbe8Pixel) -> Option<usize> {
391 if pix.c == [1, 1, 1] {
392 Some(pix.e as usize)
393 } else {
394 None
395 }
396 }
397 if rl_marker(fb).is_some() {
400 return Err(DecoderError::FirstPixelRlMarker.into());
401 }
402 buf[0] = fb; let mut x_off = 1; let mut rl_mult = 1; let mut prev_pixel = fb;
407 while x_off < width {
408 let pix = read_rgbe(r)?;
409 x_off += {
411 if let Some(rl) = rl_marker(pix) {
412 let rl = rl * rl_mult;
414 rl_mult *= 256;
415 if x_off + rl <= width {
416 for b in &mut buf[x_off..x_off + rl] {
418 *b = prev_pixel;
419 }
420 } else {
421 return Err(DecoderError::WrongScanlineLength(x_off + rl, width).into());
422 };
423 rl } else {
425 rl_mult = 1; prev_pixel = pix;
427 buf[x_off] = pix;
428 1 }
430 };
431 }
432 if x_off != width {
433 return Err(DecoderError::WrongScanlineLength(x_off, width).into());
434 }
435 Ok(())
436}
437
438fn read_rgbe<R: Read>(r: &mut R) -> io::Result<Rgbe8Pixel> {
439 let mut buf = [0u8; 4];
440 r.read_exact(&mut buf[..])?;
441 Ok(Rgbe8Pixel {
442 c: [buf[0], buf[1], buf[2]],
443 e: buf[3],
444 })
445}
446
447#[derive(Debug, Clone)]
449pub struct HdrMetadata {
450 pub width: u32,
453 pub height: u32,
455 pub orientation: ((i8, i8), (i8, i8)),
459 pub exposure: Option<f32>,
464 pub color_correction: Option<(f32, f32, f32)>,
469 pub pixel_aspect_ratio: Option<f32>,
471 pub custom_attributes: Vec<(String, String)>,
475}
476
477impl HdrMetadata {
478 fn new() -> HdrMetadata {
479 HdrMetadata {
480 width: 0,
481 height: 0,
482 orientation: ((1, 0), (0, 1)),
483 exposure: None,
484 color_correction: None,
485 pixel_aspect_ratio: None,
486 custom_attributes: vec![],
487 }
488 }
489
490 fn update_header_info(&mut self, line: &str, strict: bool) -> ImageResult<()> {
493 let maybe_key_value = split_at_first(line, "=").map(|(key, value)| (key.trim(), value));
496 match maybe_key_value {
498 Some((key, val)) => self
499 .custom_attributes
500 .push((key.to_owned(), val.to_owned())),
501 None => self
502 .custom_attributes
503 .push((String::new(), line.to_owned())),
504 }
505 match maybe_key_value {
507 Some(("FORMAT", val)) => {
508 #[allow(clippy::collapsible_match)] if val.trim() != "32-bit_rle_rgbe" {
510 return Err(ImageError::Unsupported(
512 UnsupportedError::from_format_and_kind(
513 ImageFormat::Hdr.into(),
514 UnsupportedErrorKind::Format(ImageFormatHint::Name(limit_string_len(
515 val, 20,
516 ))),
517 ),
518 ));
519 }
520 }
521 Some(("EXPOSURE", val)) => {
522 match val.trim().parse::<f32>() {
523 Ok(v) => {
524 self.exposure = Some(self.exposure.unwrap_or(1.0) * v); }
526 Err(parse_error) => {
527 if strict {
528 return Err(DecoderError::UnparsableF32(
529 LineType::Exposure,
530 parse_error,
531 )
532 .into());
533 } }
535 }
536 }
537 Some(("PIXASPECT", val)) => {
538 match val.trim().parse::<f32>() {
539 Ok(v) => {
540 self.pixel_aspect_ratio = Some(self.pixel_aspect_ratio.unwrap_or(1.0) * v);
541 }
543 Err(parse_error) => {
544 if strict {
545 return Err(DecoderError::UnparsableF32(
546 LineType::Pixaspect,
547 parse_error,
548 )
549 .into());
550 } }
552 }
553 }
554 Some(("COLORCORR", val)) => {
555 let mut rgbcorr = [1.0, 1.0, 1.0];
556 match parse_space_separated_f32(val, &mut rgbcorr, LineType::Colorcorr) {
557 Ok(extra_numbers) => {
558 if strict && extra_numbers {
559 return Err(DecoderError::ExtraneousColorcorrNumbers.into());
560 } let (rc, gc, bc) = self.color_correction.unwrap_or((1.0, 1.0, 1.0));
562 self.color_correction =
563 Some((rc * rgbcorr[0], gc * rgbcorr[1], bc * rgbcorr[2]));
564 }
565 Err(err) => {
566 if strict {
567 return Err(err);
568 } }
570 }
571 }
572 None => {
573 }
576 _ => {
577 }
579 } Ok(())
581 }
582}
583
584fn parse_space_separated_f32(line: &str, vals: &mut [f32], line_tp: LineType) -> ImageResult<bool> {
585 let mut nums = line.split_whitespace();
586 for val in vals.iter_mut() {
587 if let Some(num) = nums.next() {
588 match num.parse::<f32>() {
589 Ok(v) => *val = v,
590 Err(err) => return Err(DecoderError::UnparsableF32(line_tp, err).into()),
591 }
592 } else {
593 return Err(DecoderError::LineTooShort(line_tp).into());
595 }
596 }
597 Ok(nums.next().is_some())
598}
599
600fn parse_dimensions_line(line: &str, strict: bool) -> ImageResult<(u32, u32)> {
603 const DIMENSIONS_COUNT: usize = 4;
604
605 let mut dim_parts = line.split_whitespace();
606 let c1_tag = dim_parts
607 .next()
608 .ok_or(DecoderError::DimensionsLineTooShort(0, DIMENSIONS_COUNT))?;
609 let c1_str = dim_parts
610 .next()
611 .ok_or(DecoderError::DimensionsLineTooShort(1, DIMENSIONS_COUNT))?;
612 let c2_tag = dim_parts
613 .next()
614 .ok_or(DecoderError::DimensionsLineTooShort(2, DIMENSIONS_COUNT))?;
615 let c2_str = dim_parts
616 .next()
617 .ok_or(DecoderError::DimensionsLineTooShort(3, DIMENSIONS_COUNT))?;
618 if strict && dim_parts.next().is_some() {
619 return Err(DecoderError::DimensionsLineTooLong(DIMENSIONS_COUNT).into());
621 } match (c1_tag, c2_tag) {
625 ("-Y", "+X") => {
626 let height = c1_str
629 .parse::<u32>()
630 .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsHeight, pe))?;
631 let width = c2_str
632 .parse::<u32>()
633 .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsWidth, pe))?;
634 Ok((width, height))
635 }
636 _ => Err(ImageError::Unsupported(
637 UnsupportedError::from_format_and_kind(
638 ImageFormat::Hdr.into(),
639 UnsupportedErrorKind::GenericFeature(format!(
640 "Orientation {} {}",
641 limit_string_len(c1_tag, 4),
642 limit_string_len(c2_tag, 4)
643 )),
644 ),
645 )),
646 } }
648
649fn limit_string_len(s: &str, len: usize) -> String {
651 let s_char_len = s.chars().count();
652 if s_char_len > len {
653 s.chars().take(len).chain("...".chars()).collect()
654 } else {
655 s.into()
656 }
657}
658
659fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> {
662 match s.find(separator) {
663 None | Some(0) => None,
664 Some(p) if p >= s.len() - separator.len() => None,
665 Some(p) => Some((&s[..p], &s[(p + separator.len())..])),
666 }
667}
668
669fn read_line_u8<R: Read>(r: &mut R) -> io::Result<Option<Vec<u8>>> {
673 #[allow(clippy::disallowed_methods)]
675 let mut ret = Vec::with_capacity(16);
676 loop {
677 let mut byte = [0];
678 if r.read(&mut byte)? == 0 || byte[0] == b'\n' {
679 if ret.is_empty() && byte[0] != b'\n' {
680 return Ok(None);
681 }
682 return Ok(Some(ret));
683 }
684 ret.push(byte[0]);
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use std::{borrow::Cow, io::Cursor};
691
692 use super::*;
693
694 #[test]
695 fn split_at_first_test() {
696 assert_eq!(split_at_first(&Cow::Owned(String::new()), "="), None);
697 assert_eq!(split_at_first(&Cow::Owned("=".into()), "="), None);
698 assert_eq!(split_at_first(&Cow::Owned("= ".into()), "="), None);
699 assert_eq!(
700 split_at_first(&Cow::Owned(" = ".into()), "="),
701 Some((" ", " "))
702 );
703 assert_eq!(
704 split_at_first(&Cow::Owned("EXPOSURE= ".into()), "="),
705 Some(("EXPOSURE", " "))
706 );
707 assert_eq!(
708 split_at_first(&Cow::Owned("EXPOSURE= =".into()), "="),
709 Some(("EXPOSURE", " ="))
710 );
711 assert_eq!(
712 split_at_first(&Cow::Owned("EXPOSURE== =".into()), "=="),
713 Some(("EXPOSURE", " ="))
714 );
715 assert_eq!(split_at_first(&Cow::Owned("EXPOSURE".into()), ""), None);
716 }
717
718 #[test]
719 fn read_line_u8_test() {
720 let buf: Vec<_> = (&b"One\nTwo\nThree\nFour\n\n\n"[..]).into();
721 let input = &mut Cursor::new(buf);
722 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"One"[..]);
723 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Two"[..]);
724 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Three"[..]);
725 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Four"[..]);
726 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]);
727 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]);
728 assert_eq!(read_line_u8(input).unwrap(), None);
729 }
730
731 #[test]
732 fn dimension_overflow() {
733 let data = b"#?RADIANCE\nFORMAT=32-bit_rle_rgbe\n\n -Y 4294967295 +X 4294967295";
734
735 assert!(HdrDecoder::new(Cursor::new(data)).is_err());
736 assert!(HdrDecoder::new_nonstrict(Cursor::new(data)).is_err());
737 }
738}