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 width: u32,
123 height: u32,
124 meta: HdrMetadata,
125}
126
127#[repr(C)]
129#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
130pub(crate) struct Rgbe8Pixel {
131 pub(crate) c: [u8; 3],
133 pub(crate) e: u8,
135}
136
137pub(crate) fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel {
139 Rgbe8Pixel { c: [r, g, b], e }
140}
141
142impl Rgbe8Pixel {
143 #[inline]
145 pub(crate) fn to_hdr(self) -> Rgb<f32> {
146 if self.e == 0 {
147 Rgb([0.0, 0.0, 0.0])
148 } else {
149 let exp = f32::exp2(<f32 as From<_>>::from(self.e) - (128.0 + 8.0));
151 Rgb([
152 exp * <f32 as From<_>>::from(self.c[0]),
153 exp * <f32 as From<_>>::from(self.c[1]),
154 exp * <f32 as From<_>>::from(self.c[2]),
155 ])
156 }
157 }
158}
159
160impl<R: Read> HdrDecoder<R> {
161 pub fn new(reader: R) -> ImageResult<Self> {
165 HdrDecoder::with_strictness(reader, true)
166 }
167
168 pub fn new_nonstrict(reader: R) -> ImageResult<Self> {
170 Self::with_strictness(reader, false)
171 }
172
173 pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult<HdrDecoder<R>> {
181 let mut attributes = HdrMetadata::new();
182
183 {
184 let r = &mut reader;
186 if strict {
187 let mut signature = [0; SIGNATURE_LENGTH];
188 r.read_exact(&mut signature)?;
189 if signature != SIGNATURE {
190 return Err(DecoderError::RadianceHdrSignatureInvalid.into());
191 } read_line_u8(r)?;
194 } else {
195 }
198 loop {
200 match read_line_u8(r)? {
201 None => {
202 return Err(DecoderError::TruncatedHeader.into());
204 }
205 Some(line) => {
206 if line.is_empty() {
207 break;
209 } else if line[0] == b'#' {
210 continue;
213 } let line = String::from_utf8_lossy(&line[..]);
216 attributes.update_header_info(&line, strict)?;
217 } } } } let (width, height) = match read_line_u8(&mut reader)? {
223 None => {
224 return Err(DecoderError::TruncatedDimensions.into());
226 }
227 Some(dimensions) => {
228 let dimensions = String::from_utf8_lossy(&dimensions[..]);
229 parse_dimensions_line(&dimensions, strict)?
230 }
231 };
232
233 if crate::utils::check_dimension_overflow(width, height, ColorType::Rgb8.bytes_per_pixel())
235 {
236 return Err(ImageError::Unsupported(
237 UnsupportedError::from_format_and_kind(
238 ImageFormat::Hdr.into(),
239 UnsupportedErrorKind::GenericFeature(format!(
240 "Image dimensions ({width}x{height}) are too large"
241 )),
242 ),
243 ));
244 }
245
246 Ok(HdrDecoder {
247 r: reader,
248
249 width,
250 height,
251 meta: HdrMetadata {
252 width,
253 height,
254 ..attributes
255 },
256 })
257 } pub fn metadata(&self) -> HdrMetadata {
261 self.meta.clone()
262 }
263
264 fn read_image_transform<T: Send, F: Send + Sync + Fn(Rgbe8Pixel) -> T>(
266 mut self,
267 f: F,
268 output_slice: &mut [T],
269 ) -> ImageResult<()> {
270 assert_eq!(
271 output_slice.len(),
272 self.width as usize * self.height as usize
273 );
274
275 if self.width == 0 || self.height == 0 {
277 return Ok(());
278 }
279
280 let chunks_iter = output_slice.chunks_mut(self.width as usize);
281
282 let mut buf = vec![Default::default(); self.width as usize];
283 for chunk in chunks_iter {
284 read_scanline(&mut self.r, &mut buf[..])?;
287 for (dst, &pix) in chunk.iter_mut().zip(buf.iter()) {
288 *dst = f(pix);
289 }
290 }
291 Ok(())
292 }
293}
294
295impl<R: Read> ImageDecoder for HdrDecoder<R> {
296 fn dimensions(&self) -> (u32, u32) {
297 (self.meta.width, self.meta.height)
298 }
299
300 fn color_type(&self) -> ColorType {
301 ColorType::Rgb32F
302 }
303
304 fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
305 assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
306
307 let mut img = vec![Rgb([0.0, 0.0, 0.0]); self.width as usize * self.height as usize];
308 self.read_image_transform(|pix| pix.to_hdr(), &mut img[..])?;
309
310 for (i, Rgb(data)) in img.into_iter().enumerate() {
311 buf[(i * 12)..][..12].copy_from_slice(bytemuck::cast_slice(&data));
312 }
313
314 Ok(())
315 }
316
317 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
318 (*self).read_image(buf)
319 }
320}
321
322fn read_scanline<R: Read>(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
324 assert!(!buf.is_empty());
325 let width = buf.len();
326 let fb = read_rgbe(r)?;
328 if fb.c[0] == 2 && fb.c[1] == 2 && fb.c[2] < 128 {
329 decode_component(r, width, |offset, value| buf[offset].c[0] = value)?;
333 decode_component(r, width, |offset, value| buf[offset].c[1] = value)?;
334 decode_component(r, width, |offset, value| buf[offset].c[2] = value)?;
335 decode_component(r, width, |offset, value| buf[offset].e = value)?;
336 } else {
337 decode_old_rle(r, fb, buf)?;
339 }
340 Ok(())
341}
342
343#[inline(always)]
344fn read_byte<R: Read>(r: &mut R) -> io::Result<u8> {
345 let mut buf = [0u8];
346 r.read_exact(&mut buf[..])?;
347 Ok(buf[0])
348}
349
350#[inline]
352fn decode_component<R: Read, S: FnMut(usize, u8)>(
353 r: &mut R,
354 width: usize,
355 mut set_component: S,
356) -> ImageResult<()> {
357 let mut buf = [0; 128];
358 let mut pos = 0;
359 while pos < width {
360 pos += {
362 let rl = read_byte(r)?;
363 if rl <= 128 {
364 if pos + rl as usize > width {
366 return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
367 }
368 r.read_exact(&mut buf[0..rl as usize])?;
370 for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
371 set_component(pos + offset, value);
372 }
373 rl as usize
374 } else {
375 let rl = rl - 128;
377 if pos + rl as usize > width {
379 return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
380 }
381 let value = read_byte(r)?;
383 for offset in 0..rl as usize {
384 set_component(pos + offset, value);
385 }
386 rl as usize
387 }
388 };
389 }
390 if pos != width {
391 return Err(DecoderError::WrongScanlineLength(pos, width).into());
392 }
393 Ok(())
394}
395
396fn decode_old_rle<R: Read>(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
400 assert!(!buf.is_empty());
401 let width = buf.len();
402 #[inline]
405 fn rl_marker(pix: Rgbe8Pixel) -> Option<usize> {
406 if pix.c == [1, 1, 1] {
407 Some(pix.e as usize)
408 } else {
409 None
410 }
411 }
412 if rl_marker(fb).is_some() {
415 return Err(DecoderError::FirstPixelRlMarker.into());
416 }
417 buf[0] = fb; let mut x_off = 1; let mut rl_mult = 1; let mut prev_pixel = fb;
422 while x_off < width {
423 let pix = read_rgbe(r)?;
424 x_off += {
426 if let Some(rl) = rl_marker(pix) {
427 let rl = rl * rl_mult;
429 rl_mult *= 256;
430 if x_off + rl <= width {
431 for b in &mut buf[x_off..x_off + rl] {
433 *b = prev_pixel;
434 }
435 } else {
436 return Err(DecoderError::WrongScanlineLength(x_off + rl, width).into());
437 };
438 rl } else {
440 rl_mult = 1; prev_pixel = pix;
442 buf[x_off] = pix;
443 1 }
445 };
446 }
447 if x_off != width {
448 return Err(DecoderError::WrongScanlineLength(x_off, width).into());
449 }
450 Ok(())
451}
452
453fn read_rgbe<R: Read>(r: &mut R) -> io::Result<Rgbe8Pixel> {
454 let mut buf = [0u8; 4];
455 r.read_exact(&mut buf[..])?;
456 Ok(Rgbe8Pixel {
457 c: [buf[0], buf[1], buf[2]],
458 e: buf[3],
459 })
460}
461
462#[derive(Debug, Clone)]
464pub struct HdrMetadata {
465 pub width: u32,
468 pub height: u32,
470 pub orientation: ((i8, i8), (i8, i8)),
474 pub exposure: Option<f32>,
479 pub color_correction: Option<(f32, f32, f32)>,
484 pub pixel_aspect_ratio: Option<f32>,
486 pub custom_attributes: Vec<(String, String)>,
490}
491
492impl HdrMetadata {
493 fn new() -> HdrMetadata {
494 HdrMetadata {
495 width: 0,
496 height: 0,
497 orientation: ((1, 0), (0, 1)),
498 exposure: None,
499 color_correction: None,
500 pixel_aspect_ratio: None,
501 custom_attributes: vec![],
502 }
503 }
504
505 fn update_header_info(&mut self, line: &str, strict: bool) -> ImageResult<()> {
508 let maybe_key_value = split_at_first(line, "=").map(|(key, value)| (key.trim(), value));
511 match maybe_key_value {
513 Some((key, val)) => self
514 .custom_attributes
515 .push((key.to_owned(), val.to_owned())),
516 None => self
517 .custom_attributes
518 .push((String::new(), line.to_owned())),
519 }
520 match maybe_key_value {
522 Some(("FORMAT", val)) => {
523 if val.trim() != "32-bit_rle_rgbe" {
524 return Err(ImageError::Unsupported(
526 UnsupportedError::from_format_and_kind(
527 ImageFormat::Hdr.into(),
528 UnsupportedErrorKind::Format(ImageFormatHint::Name(limit_string_len(
529 val, 20,
530 ))),
531 ),
532 ));
533 }
534 }
535 Some(("EXPOSURE", val)) => {
536 match val.trim().parse::<f32>() {
537 Ok(v) => {
538 self.exposure = Some(self.exposure.unwrap_or(1.0) * v); }
540 Err(parse_error) => {
541 if strict {
542 return Err(DecoderError::UnparsableF32(
543 LineType::Exposure,
544 parse_error,
545 )
546 .into());
547 } }
549 }
550 }
551 Some(("PIXASPECT", val)) => {
552 match val.trim().parse::<f32>() {
553 Ok(v) => {
554 self.pixel_aspect_ratio = Some(self.pixel_aspect_ratio.unwrap_or(1.0) * v);
555 }
557 Err(parse_error) => {
558 if strict {
559 return Err(DecoderError::UnparsableF32(
560 LineType::Pixaspect,
561 parse_error,
562 )
563 .into());
564 } }
566 }
567 }
568 Some(("COLORCORR", val)) => {
569 let mut rgbcorr = [1.0, 1.0, 1.0];
570 match parse_space_separated_f32(val, &mut rgbcorr, LineType::Colorcorr) {
571 Ok(extra_numbers) => {
572 if strict && extra_numbers {
573 return Err(DecoderError::ExtraneousColorcorrNumbers.into());
574 } let (rc, gc, bc) = self.color_correction.unwrap_or((1.0, 1.0, 1.0));
576 self.color_correction =
577 Some((rc * rgbcorr[0], gc * rgbcorr[1], bc * rgbcorr[2]));
578 }
579 Err(err) => {
580 if strict {
581 return Err(err);
582 } }
584 }
585 }
586 None => {
587 }
590 _ => {
591 }
593 } Ok(())
595 }
596}
597
598fn parse_space_separated_f32(line: &str, vals: &mut [f32], line_tp: LineType) -> ImageResult<bool> {
599 let mut nums = line.split_whitespace();
600 for val in vals.iter_mut() {
601 if let Some(num) = nums.next() {
602 match num.parse::<f32>() {
603 Ok(v) => *val = v,
604 Err(err) => return Err(DecoderError::UnparsableF32(line_tp, err).into()),
605 }
606 } else {
607 return Err(DecoderError::LineTooShort(line_tp).into());
609 }
610 }
611 Ok(nums.next().is_some())
612}
613
614fn parse_dimensions_line(line: &str, strict: bool) -> ImageResult<(u32, u32)> {
617 const DIMENSIONS_COUNT: usize = 4;
618
619 let mut dim_parts = line.split_whitespace();
620 let c1_tag = dim_parts
621 .next()
622 .ok_or(DecoderError::DimensionsLineTooShort(0, DIMENSIONS_COUNT))?;
623 let c1_str = dim_parts
624 .next()
625 .ok_or(DecoderError::DimensionsLineTooShort(1, DIMENSIONS_COUNT))?;
626 let c2_tag = dim_parts
627 .next()
628 .ok_or(DecoderError::DimensionsLineTooShort(2, DIMENSIONS_COUNT))?;
629 let c2_str = dim_parts
630 .next()
631 .ok_or(DecoderError::DimensionsLineTooShort(3, DIMENSIONS_COUNT))?;
632 if strict && dim_parts.next().is_some() {
633 return Err(DecoderError::DimensionsLineTooLong(DIMENSIONS_COUNT).into());
635 } match (c1_tag, c2_tag) {
639 ("-Y", "+X") => {
640 let height = c1_str
643 .parse::<u32>()
644 .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsHeight, pe))?;
645 let width = c2_str
646 .parse::<u32>()
647 .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsWidth, pe))?;
648 Ok((width, height))
649 }
650 _ => Err(ImageError::Unsupported(
651 UnsupportedError::from_format_and_kind(
652 ImageFormat::Hdr.into(),
653 UnsupportedErrorKind::GenericFeature(format!(
654 "Orientation {} {}",
655 limit_string_len(c1_tag, 4),
656 limit_string_len(c2_tag, 4)
657 )),
658 ),
659 )),
660 } }
662
663fn limit_string_len(s: &str, len: usize) -> String {
665 let s_char_len = s.chars().count();
666 if s_char_len > len {
667 s.chars().take(len).chain("...".chars()).collect()
668 } else {
669 s.into()
670 }
671}
672
673fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> {
676 match s.find(separator) {
677 None | Some(0) => None,
678 Some(p) if p >= s.len() - separator.len() => None,
679 Some(p) => Some((&s[..p], &s[(p + separator.len())..])),
680 }
681}
682
683fn read_line_u8<R: Read>(r: &mut R) -> io::Result<Option<Vec<u8>>> {
687 #[allow(clippy::disallowed_methods)]
689 let mut ret = Vec::with_capacity(16);
690 loop {
691 let mut byte = [0];
692 if r.read(&mut byte)? == 0 || byte[0] == b'\n' {
693 if ret.is_empty() && byte[0] != b'\n' {
694 return Ok(None);
695 }
696 return Ok(Some(ret));
697 }
698 ret.push(byte[0]);
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use std::{borrow::Cow, io::Cursor};
705
706 use super::*;
707
708 #[test]
709 fn split_at_first_test() {
710 assert_eq!(split_at_first(&Cow::Owned(String::new()), "="), None);
711 assert_eq!(split_at_first(&Cow::Owned("=".into()), "="), None);
712 assert_eq!(split_at_first(&Cow::Owned("= ".into()), "="), None);
713 assert_eq!(
714 split_at_first(&Cow::Owned(" = ".into()), "="),
715 Some((" ", " "))
716 );
717 assert_eq!(
718 split_at_first(&Cow::Owned("EXPOSURE= ".into()), "="),
719 Some(("EXPOSURE", " "))
720 );
721 assert_eq!(
722 split_at_first(&Cow::Owned("EXPOSURE= =".into()), "="),
723 Some(("EXPOSURE", " ="))
724 );
725 assert_eq!(
726 split_at_first(&Cow::Owned("EXPOSURE== =".into()), "=="),
727 Some(("EXPOSURE", " ="))
728 );
729 assert_eq!(split_at_first(&Cow::Owned("EXPOSURE".into()), ""), None);
730 }
731
732 #[test]
733 fn read_line_u8_test() {
734 let buf: Vec<_> = (&b"One\nTwo\nThree\nFour\n\n\n"[..]).into();
735 let input = &mut Cursor::new(buf);
736 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"One"[..]);
737 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Two"[..]);
738 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Three"[..]);
739 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Four"[..]);
740 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]);
741 assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]);
742 assert_eq!(read_line_u8(input).unwrap(), None);
743 }
744
745 #[test]
746 fn dimension_overflow() {
747 let data = b"#?RADIANCE\nFORMAT=32-bit_rle_rgbe\n\n -Y 4294967295 +X 4294967295";
748
749 assert!(HdrDecoder::new(Cursor::new(data)).is_err());
750 assert!(HdrDecoder::new_nonstrict(Cursor::new(data)).is_err());
751 }
752}