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