1use std::io::{self, Read};
2
3use std::num::{ParseFloatError, ParseIntError};
4use std::{error, fmt};
5
6use crate::color::{ColorType, Rgb};
7use crate::error::{
8 DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind,
9};
10use crate::image::{ImageDecoder, ImageFormat};
11
12/// Errors that can occur during decoding and parsing of a HDR image
13#[derive(Debug, Clone, PartialEq, Eq)]
14enum DecoderError {
15 /// HDR's "#?RADIANCE" signature wrong or missing
16 RadianceHdrSignatureInvalid,
17 /// EOF before end of header
18 TruncatedHeader,
19 /// EOF instead of image dimensions
20 TruncatedDimensions,
21
22 /// A value couldn't be parsed
23 UnparsableF32(LineType, ParseFloatError),
24 /// A value couldn't be parsed
25 UnparsableU32(LineType, ParseIntError),
26 /// Not enough numbers in line
27 LineTooShort(LineType),
28
29 /// COLORCORR contains too many numbers in strict mode
30 ExtraneousColorcorrNumbers,
31
32 /// Dimensions line had too few elements
33 DimensionsLineTooShort(usize, usize),
34 /// Dimensions line had too many elements
35 DimensionsLineTooLong(usize),
36
37 /// The length of a scanline (1) wasn't a match for the specified length (2)
38 WrongScanlineLength(usize, usize),
39 /// First pixel of a scanline is a run length marker
40 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(format:ImageFormat::Hdr.into(), err: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: &ParseFloatError) => Some(err),
87 DecoderError::UnparsableU32(_, err: &ParseIntError) => Some(err),
88 _ => None,
89 }
90 }
91}
92
93/// Lines which contain parsable data that can fail
94#[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(data: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
115/// Radiance HDR file signature
116pub const SIGNATURE: &[u8] = b"#?RADIANCE";
117const SIGNATURE_LENGTH: usize = 10;
118
119/// An Radiance HDR decoder
120#[derive(Debug)]
121pub struct HdrDecoder<R> {
122 r: R,
123 width: u32,
124 height: u32,
125 meta: HdrMetadata,
126}
127
128/// Refer to [wikipedia](https://en.wikipedia.org/wiki/RGBE_image_format)
129#[repr(C)]
130#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
131pub(crate) struct Rgbe8Pixel {
132 /// Color components
133 pub(crate) c: [u8; 3],
134 /// Exponent
135 pub(crate) e: u8,
136}
137
138/// Creates `Rgbe8Pixel` from components
139pub(crate) fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel {
140 Rgbe8Pixel { c: [r, g, b], e }
141}
142
143impl Rgbe8Pixel {
144 /// Converts `Rgbe8Pixel` into `Rgb<f32>` linearly
145 #[inline]
146 pub(crate) fn to_hdr(self) -> Rgb<f32> {
147 if self.e == 0 {
148 Rgb([0.0, 0.0, 0.0])
149 } else {
150 // let exp = f32::ldexp(1., self.e as isize - (128 + 8)); // unstable
151 let exp: f32 = f32::exp2(<f32 as From<_>>::from(self.e) - (128.0 + 8.0));
152 Rgb([
153 exp * <f32 as From<_>>::from(self.c[0]),
154 exp * <f32 as From<_>>::from(self.c[1]),
155 exp * <f32 as From<_>>::from(self.c[2]),
156 ])
157 }
158 }
159}
160
161impl<R: Read> HdrDecoder<R> {
162 /// Reads Radiance HDR image header from stream ```r```
163 /// if the header is valid, creates `HdrDecoder`
164 /// strict mode is enabled
165 pub fn new(reader: R) -> ImageResult<Self> {
166 HdrDecoder::with_strictness(reader, true)
167 }
168
169 /// Allows reading old Radiance HDR images
170 pub fn new_nonstrict(reader: R) -> ImageResult<Self> {
171 Self::with_strictness(reader, false)
172 }
173
174 /// Reads Radiance HDR image header from stream `reader`,
175 /// if the header is valid, creates `HdrDecoder`.
176 ///
177 /// strict enables strict mode
178 ///
179 /// Warning! Reading wrong file in non-strict mode
180 /// could consume file size worth of memory in the process.
181 pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult<HdrDecoder<R>> {
182 let mut attributes = HdrMetadata::new();
183
184 {
185 // scope to make borrowck happy
186 let r = &mut reader;
187 if strict {
188 let mut signature = [0; SIGNATURE_LENGTH];
189 r.read_exact(&mut signature)?;
190 if signature != SIGNATURE {
191 return Err(DecoderError::RadianceHdrSignatureInvalid.into());
192 } // no else
193 // skip signature line ending
194 read_line_u8(r)?;
195 } else {
196 // Old Radiance HDR files (*.pic) don't use signature
197 // Let them be parsed in non-strict mode
198 }
199 // read header data until empty line
200 loop {
201 match read_line_u8(r)? {
202 None => {
203 // EOF before end of header
204 return Err(DecoderError::TruncatedHeader.into());
205 }
206 Some(line) => {
207 if line.is_empty() {
208 // end of header
209 break;
210 } else if line[0] == b'#' {
211 // line[0] will not panic, line.len() == 0 is false here
212 // skip comments
213 continue;
214 } // no else
215 // process attribute line
216 let line = String::from_utf8_lossy(&line[..]);
217 attributes.update_header_info(&line, strict)?;
218 } // <= Some(line)
219 } // match read_line_u8()
220 } // loop
221 } // scope to end borrow of reader
222 // parse dimensions
223 let (width, height) = match read_line_u8(&mut reader)? {
224 None => {
225 // EOF instead of image dimensions
226 return Err(DecoderError::TruncatedDimensions.into());
227 }
228 Some(dimensions) => {
229 let dimensions = String::from_utf8_lossy(&dimensions[..]);
230 parse_dimensions_line(&dimensions, strict)?
231 }
232 };
233
234 // color type is always rgb8
235 if crate::utils::check_dimension_overflow(width, height, ColorType::Rgb8.bytes_per_pixel())
236 {
237 return Err(ImageError::Unsupported(
238 UnsupportedError::from_format_and_kind(
239 ImageFormat::Hdr.into(),
240 UnsupportedErrorKind::GenericFeature(format!(
241 "Image dimensions ({width}x{height}) are too large"
242 )),
243 ),
244 ));
245 }
246
247 Ok(HdrDecoder {
248 r: reader,
249
250 width,
251 height,
252 meta: HdrMetadata {
253 width,
254 height,
255 ..attributes
256 },
257 })
258 } // end with_strictness
259
260 /// Returns file metadata. Refer to `HdrMetadata` for details.
261 pub fn metadata(&self) -> HdrMetadata {
262 self.meta.clone()
263 }
264
265 /// Consumes decoder and returns a vector of transformed pixels
266 fn read_image_transform<T: Send, F: Send + Sync + Fn(Rgbe8Pixel) -> T>(
267 mut self,
268 f: F,
269 output_slice: &mut [T],
270 ) -> ImageResult<()> {
271 assert_eq!(
272 output_slice.len(),
273 self.width as usize * self.height as usize
274 );
275
276 // Don't read anything if image is empty
277 if self.width == 0 || self.height == 0 {
278 return Ok(());
279 }
280
281 let chunks_iter = output_slice.chunks_mut(self.width as usize);
282
283 let mut buf = vec![Default::default(); self.width as usize];
284 for chunk in chunks_iter {
285 // read_scanline overwrites the entire buffer or returns an Err,
286 // so not resetting the buffer here is ok.
287 read_scanline(&mut self.r, &mut buf[..])?;
288 for (dst, &pix) in chunk.iter_mut().zip(buf.iter()) {
289 *dst = f(pix);
290 }
291 }
292 Ok(())
293 }
294}
295
296impl<R: Read> ImageDecoder for HdrDecoder<R> {
297 fn dimensions(&self) -> (u32, u32) {
298 (self.meta.width, self.meta.height)
299 }
300
301 fn color_type(&self) -> ColorType {
302 ColorType::Rgb32F
303 }
304
305 fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
306 assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
307
308 let mut img = vec![Rgb([0.0, 0.0, 0.0]); self.width as usize * self.height as usize];
309 self.read_image_transform(|pix| pix.to_hdr(), &mut img[..])?;
310
311 for (i, Rgb(data)) in img.into_iter().enumerate() {
312 buf[(i * 12)..][..12].copy_from_slice(bytemuck::cast_slice(&data));
313 }
314
315 Ok(())
316 }
317
318 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
319 (*self).read_image(buf)
320 }
321}
322
323// Precondition: buf.len() > 0
324fn read_scanline<R: Read>(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
325 assert!(!buf.is_empty());
326 let width: usize = buf.len();
327 // first 4 bytes in scanline allow to determine compression method
328 let fb: Rgbe8Pixel = read_rgbe(r)?;
329 if fb.c[0] == 2 && fb.c[1] == 2 && fb.c[2] < 128 {
330 // denormalized pixel value (2,2,<128,_) indicates new per component RLE method
331 // decode_component guarantees that offset is within 0 .. width
332 // therefore we can skip bounds checking here, but we will not
333 decode_component(r, width, |offset: usize, value: u8| buf[offset].c[0] = value)?;
334 decode_component(r, width, |offset: usize, value: u8| buf[offset].c[1] = value)?;
335 decode_component(r, width, |offset: usize, value: u8| buf[offset].c[2] = value)?;
336 decode_component(r, width, |offset: usize, value: u8| buf[offset].e = value)?;
337 } else {
338 // old RLE method (it was considered old around 1991, should it be here?)
339 decode_old_rle(r, fb, buf)?;
340 }
341 Ok(())
342}
343
344#[inline(always)]
345fn read_byte<R: Read>(r: &mut R) -> io::Result<u8> {
346 let mut buf: [u8; 1] = [0u8];
347 r.read_exact(&mut buf[..])?;
348 Ok(buf[0])
349}
350
351// Guarantees that first parameter of set_component will be within pos .. pos+width
352#[inline]
353fn decode_component<R: Read, S: FnMut(usize, u8)>(
354 r: &mut R,
355 width: usize,
356 mut set_component: S,
357) -> ImageResult<()> {
358 let mut buf = [0; 128];
359 let mut pos = 0;
360 while pos < width {
361 // increment position by a number of decompressed values
362 pos += {
363 let rl = read_byte(r)?;
364 if rl <= 128 {
365 // sanity check
366 if pos + rl as usize > width {
367 return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
368 }
369 // read values
370 r.read_exact(&mut buf[0..rl as usize])?;
371 for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
372 set_component(pos + offset, value);
373 }
374 rl as usize
375 } else {
376 // run
377 let rl = rl - 128;
378 // sanity check
379 if pos + rl as usize > width {
380 return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
381 }
382 // fill with same value
383 let value = read_byte(r)?;
384 for offset in 0..rl as usize {
385 set_component(pos + offset, value);
386 }
387 rl as usize
388 }
389 };
390 }
391 if pos != width {
392 return Err(DecoderError::WrongScanlineLength(pos, width).into());
393 }
394 Ok(())
395}
396
397// Decodes scanline, places it into buf
398// Precondition: buf.len() > 0
399// fb - first 4 bytes of scanline
400fn decode_old_rle<R: Read>(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
401 assert!(!buf.is_empty());
402 let width = buf.len();
403 // convenience function.
404 // returns run length if pixel is a run length marker
405 #[inline]
406 fn rl_marker(pix: Rgbe8Pixel) -> Option<usize> {
407 if pix.c == [1, 1, 1] {
408 Some(pix.e as usize)
409 } else {
410 None
411 }
412 }
413 // first pixel in scanline should not be run length marker
414 // it is error if it is
415 if rl_marker(fb).is_some() {
416 return Err(DecoderError::FirstPixelRlMarker.into());
417 }
418 buf[0] = fb; // set first pixel of scanline
419
420 let mut x_off = 1; // current offset from beginning of a scanline
421 let mut rl_mult = 1; // current run length multiplier
422 let mut prev_pixel = fb;
423 while x_off < width {
424 let pix = read_rgbe(r)?;
425 // it's harder to forget to increase x_off if I write this this way.
426 x_off += {
427 if let Some(rl) = rl_marker(pix) {
428 // rl_mult takes care of consecutive RL markers
429 let rl = rl * rl_mult;
430 rl_mult *= 256;
431 if x_off + rl <= width {
432 // do run
433 for b in &mut buf[x_off..x_off + rl] {
434 *b = prev_pixel;
435 }
436 } else {
437 return Err(DecoderError::WrongScanlineLength(x_off + rl, width).into());
438 };
439 rl // value to increase x_off by
440 } else {
441 rl_mult = 1; // chain of consecutive RL markers is broken
442 prev_pixel = pix;
443 buf[x_off] = pix;
444 1 // value to increase x_off by
445 }
446 };
447 }
448 if x_off != width {
449 return Err(DecoderError::WrongScanlineLength(x_off, width).into());
450 }
451 Ok(())
452}
453
454fn read_rgbe<R: Read>(r: &mut R) -> io::Result<Rgbe8Pixel> {
455 let mut buf: [u8; 4] = [0u8; 4];
456 r.read_exact(&mut buf[..])?;
457 Ok(Rgbe8Pixel {
458 c: [buf[0], buf[1], buf[2]],
459 e: buf[3],
460 })
461}
462
463/// Metadata for Radiance HDR image
464#[derive(Debug, Clone)]
465pub struct HdrMetadata {
466 /// Width of decoded image. It could be either scanline length,
467 /// or scanline count, depending on image orientation.
468 pub width: u32,
469 /// Height of decoded image. It depends on orientation too.
470 pub height: u32,
471 /// Orientation matrix. For standard orientation it is ((1,0),(0,1)) - left to right, top to bottom.
472 /// First pair tells how resulting pixel coordinates change along a scanline.
473 /// Second pair tells how they change from one scanline to the next.
474 pub orientation: ((i8, i8), (i8, i8)),
475 /// Divide color values by exposure to get to get physical radiance in
476 /// watts/steradian/m<sup>2</sup>
477 ///
478 /// Image may not contain physical data, even if this field is set.
479 pub exposure: Option<f32>,
480 /// Divide color values by corresponding tuple member (r, g, b) to get to get physical radiance
481 /// in watts/steradian/m<sup>2</sup>
482 ///
483 /// Image may not contain physical data, even if this field is set.
484 pub color_correction: Option<(f32, f32, f32)>,
485 /// Pixel height divided by pixel width
486 pub pixel_aspect_ratio: Option<f32>,
487 /// All lines contained in image header are put here. Ordering of lines is preserved.
488 /// Lines in the form "key=value" are represented as ("key", "value").
489 /// All other lines are ("", "line")
490 pub custom_attributes: Vec<(String, String)>,
491}
492
493impl HdrMetadata {
494 fn new() -> HdrMetadata {
495 HdrMetadata {
496 width: 0,
497 height: 0,
498 orientation: ((1, 0), (0, 1)),
499 exposure: None,
500 color_correction: None,
501 pixel_aspect_ratio: None,
502 custom_attributes: vec![],
503 }
504 }
505
506 // Updates header info, in strict mode returns error for malformed lines (no '=' separator)
507 // unknown attributes are skipped
508 fn update_header_info(&mut self, line: &str, strict: bool) -> ImageResult<()> {
509 // split line at first '='
510 // old Radiance HDR files (*.pic) feature tabs in key, so vvv trim
511 let maybe_key_value = split_at_first(line, "=").map(|(key, value)| (key.trim(), value));
512 // save all header lines in custom_attributes
513 match maybe_key_value {
514 Some((key, val)) => self
515 .custom_attributes
516 .push((key.to_owned(), val.to_owned())),
517 None => self
518 .custom_attributes
519 .push((String::new(), line.to_owned())),
520 }
521 // parse known attributes
522 match maybe_key_value {
523 Some(("FORMAT", val)) => {
524 if val.trim() != "32-bit_rle_rgbe" {
525 // XYZE isn't supported yet
526 return Err(ImageError::Unsupported(
527 UnsupportedError::from_format_and_kind(
528 ImageFormat::Hdr.into(),
529 UnsupportedErrorKind::Format(ImageFormatHint::Name(limit_string_len(
530 val, 20,
531 ))),
532 ),
533 ));
534 }
535 }
536 Some(("EXPOSURE", val)) => {
537 match val.trim().parse::<f32>() {
538 Ok(v) => {
539 self.exposure = Some(self.exposure.unwrap_or(1.0) * v); // all encountered exposure values should be multiplied
540 }
541 Err(parse_error) => {
542 if strict {
543 return Err(DecoderError::UnparsableF32(
544 LineType::Exposure,
545 parse_error,
546 )
547 .into());
548 } // no else, skip this line in non-strict mode
549 }
550 };
551 }
552 Some(("PIXASPECT", val)) => {
553 match val.trim().parse::<f32>() {
554 Ok(v) => {
555 self.pixel_aspect_ratio = Some(self.pixel_aspect_ratio.unwrap_or(1.0) * v);
556 // all encountered exposure values should be multiplied
557 }
558 Err(parse_error) => {
559 if strict {
560 return Err(DecoderError::UnparsableF32(
561 LineType::Pixaspect,
562 parse_error,
563 )
564 .into());
565 } // no else, skip this line in non-strict mode
566 }
567 };
568 }
569 Some(("COLORCORR", val)) => {
570 let mut rgbcorr = [1.0, 1.0, 1.0];
571 match parse_space_separated_f32(val, &mut rgbcorr, LineType::Colorcorr) {
572 Ok(extra_numbers) => {
573 if strict && extra_numbers {
574 return Err(DecoderError::ExtraneousColorcorrNumbers.into());
575 } // no else, just ignore extra numbers
576 let (rc, gc, bc) = self.color_correction.unwrap_or((1.0, 1.0, 1.0));
577 self.color_correction =
578 Some((rc * rgbcorr[0], gc * rgbcorr[1], bc * rgbcorr[2]));
579 }
580 Err(err) => {
581 if strict {
582 return Err(err);
583 } // no else, skip malformed line in non-strict mode
584 }
585 }
586 }
587 None => {
588 // old Radiance HDR files (*.pic) contain commands in a header
589 // just skip them
590 }
591 _ => {
592 // skip unknown attribute
593 }
594 } // match attributes
595 Ok(())
596 }
597}
598
599fn parse_space_separated_f32(line: &str, vals: &mut [f32], line_tp: LineType) -> ImageResult<bool> {
600 let mut nums: SplitWhitespace<'_> = line.split_whitespace();
601 for val: &mut f32 in vals.iter_mut() {
602 if let Some(num: &str) = nums.next() {
603 match num.parse::<f32>() {
604 Ok(v: f32) => *val = v,
605 Err(err: ParseFloatError) => return Err(DecoderError::UnparsableF32(line_tp, err).into()),
606 }
607 } else {
608 // not enough numbers in line
609 return Err(DecoderError::LineTooShort(line_tp).into());
610 }
611 }
612 Ok(nums.next().is_some())
613}
614
615// Parses dimension line "-Y height +X width"
616// returns (width, height) or error
617fn parse_dimensions_line(line: &str, strict: bool) -> ImageResult<(u32, u32)> {
618 const DIMENSIONS_COUNT: usize = 4;
619
620 let mut dim_parts = line.split_whitespace();
621 let c1_tag = dim_parts
622 .next()
623 .ok_or(DecoderError::DimensionsLineTooShort(0, DIMENSIONS_COUNT))?;
624 let c1_str = dim_parts
625 .next()
626 .ok_or(DecoderError::DimensionsLineTooShort(1, DIMENSIONS_COUNT))?;
627 let c2_tag = dim_parts
628 .next()
629 .ok_or(DecoderError::DimensionsLineTooShort(2, DIMENSIONS_COUNT))?;
630 let c2_str = dim_parts
631 .next()
632 .ok_or(DecoderError::DimensionsLineTooShort(3, DIMENSIONS_COUNT))?;
633 if strict && dim_parts.next().is_some() {
634 // extra data in dimensions line
635 return Err(DecoderError::DimensionsLineTooLong(DIMENSIONS_COUNT).into());
636 } // no else
637 // dimensions line is in the form "-Y 10 +X 20"
638 // There are 8 possible orientations: +Y +X, +X -Y and so on
639 match (c1_tag, c2_tag) {
640 ("-Y", "+X") => {
641 // Common orientation (left-right, top-down)
642 // c1_str is height, c2_str is width
643 let height = c1_str
644 .parse::<u32>()
645 .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsHeight, pe))?;
646 let width = c2_str
647 .parse::<u32>()
648 .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsWidth, pe))?;
649 Ok((width, height))
650 }
651 _ => Err(ImageError::Unsupported(
652 UnsupportedError::from_format_and_kind(
653 ImageFormat::Hdr.into(),
654 UnsupportedErrorKind::GenericFeature(format!(
655 "Orientation {} {}",
656 limit_string_len(c1_tag, 4),
657 limit_string_len(c2_tag, 4)
658 )),
659 ),
660 )),
661 } // final expression. Returns value
662}
663
664// Returns string with no more than len+3 characters
665fn limit_string_len(s: &str, len: usize) -> String {
666 let s_char_len: usize = s.chars().count();
667 if s_char_len > len {
668 s.chars().take(len).chain("...".chars()).collect()
669 } else {
670 s.into()
671 }
672}
673
674// Splits string into (before separator, after separator) tuple
675// or None if separator isn't found
676fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> {
677 match s.find(separator) {
678 None | Some(0) => None,
679 Some(p: usize) if p >= s.len() - separator.len() => None,
680 Some(p: usize) => Some((&s[..p], &s[(p + separator.len())..])),
681 }
682}
683
684// Reads input until b"\n" or EOF
685// Returns vector of read bytes NOT including end of line characters
686// or return None to indicate end of file
687fn read_line_u8<R: Read>(r: &mut R) -> io::Result<Option<Vec<u8>>> {
688 let mut ret: Vec = Vec::with_capacity(16);
689 loop {
690 let mut byte: [u8; 1] = [0];
691 if r.read(&mut byte)? == 0 || byte[0] == b'\n' {
692 if ret.is_empty() && byte[0] != b'\n' {
693 return Ok(None);
694 } else {
695 return Ok(Some(ret));
696 }
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("".into()), "="), 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}
753