Coverage Report

Created: 2026-02-26 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/codecs/hdr/decoder.rs
Line
Count
Source
1
use std::io::{self, Read};
2
3
use std::num::{ParseFloatError, ParseIntError};
4
use std::{error, fmt};
5
6
use crate::error::{
7
    DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind,
8
};
9
use crate::io::image_reader_type::SpecCompliance;
10
use crate::{ColorType, ImageDecoder, ImageFormat, Limits, Rgb};
11
12
/// Errors that can occur during decoding and parsing of a HDR image
13
#[derive(Debug, Clone, PartialEq, Eq)]
14
enum 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
    /// The end of the header, if it exists, is far enough in the file that
22
    /// this is unlikely to be a valid image
23
    HeaderTooLong,
24
25
    /// A value couldn't be parsed
26
    UnparsableF32(LineType, ParseFloatError),
27
    /// A value couldn't be parsed
28
    UnparsableU32(LineType, ParseIntError),
29
    /// Not enough numbers in line
30
    LineTooShort(LineType),
31
32
    /// COLORCORR contains too many numbers in strict mode
33
    ExtraneousColorcorrNumbers,
34
35
    /// Dimensions line had too few elements
36
    DimensionsLineTooShort(usize, usize),
37
    /// Dimensions line had too many elements
38
    DimensionsLineTooLong(usize),
39
40
    /// The length of a scanline (1) wasn't a match for the specified length (2)
41
    WrongScanlineLength(usize, usize),
42
    /// A chain of run length instructions would produce a too large run
43
    OverlyLongRepeat,
44
    /// First pixel of a scanline is a run length marker
45
    FirstPixelRlMarker,
46
}
47
48
impl fmt::Display for DecoderError {
49
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50
0
        match self {
51
            DecoderError::RadianceHdrSignatureInvalid => {
52
0
                f.write_str("Radiance HDR signature not found")
53
            }
54
0
            DecoderError::TruncatedHeader => f.write_str("EOF in header"),
55
0
            DecoderError::TruncatedDimensions => f.write_str("EOF in dimensions line"),
56
0
            DecoderError::HeaderTooLong => f.write_fmt(format_args!(
57
0
                "Header end not in the first {MAX_HEADER_LENGTH} bytes, unlikely to be valid image"
58
            )),
59
0
            DecoderError::UnparsableF32(line, pe) => {
60
0
                f.write_fmt(format_args!("Cannot parse {line} value as f32: {pe}"))
61
            }
62
0
            DecoderError::UnparsableU32(line, pe) => {
63
0
                f.write_fmt(format_args!("Cannot parse {line} value as u32: {pe}"))
64
            }
65
0
            DecoderError::LineTooShort(line) => {
66
0
                f.write_fmt(format_args!("Not enough numbers in {line}"))
67
            }
68
0
            DecoderError::ExtraneousColorcorrNumbers => f.write_str("Extra numbers in COLORCORR"),
69
0
            DecoderError::DimensionsLineTooShort(elements, expected) => f.write_fmt(format_args!(
70
0
                "Dimensions line too short: have {elements} elements, expected {expected}"
71
            )),
72
0
            DecoderError::DimensionsLineTooLong(expected) => f.write_fmt(format_args!(
73
0
                "Dimensions line too long, expected {expected} elements"
74
            )),
75
0
            DecoderError::WrongScanlineLength(len, expected) => f.write_fmt(format_args!(
76
0
                "Wrong length of decoded scanline: got {len}, expected {expected}"
77
            )),
78
0
            DecoderError::OverlyLongRepeat => f.write_str(
79
0
                "Sequence of run length markers produces run longer than max image width",
80
            ),
81
            DecoderError::FirstPixelRlMarker => {
82
0
                f.write_str("First pixel of a scanline shouldn't be run length marker")
83
            }
84
        }
85
0
    }
86
}
87
88
impl From<DecoderError> for ImageError {
89
3.70k
    fn from(e: DecoderError) -> ImageError {
90
3.70k
        ImageError::Decoding(DecodingError::new(ImageFormat::Hdr.into(), e))
91
3.70k
    }
92
}
93
94
impl error::Error for DecoderError {
95
0
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
96
0
        match self {
97
0
            DecoderError::UnparsableF32(_, err) => Some(err),
98
0
            DecoderError::UnparsableU32(_, err) => Some(err),
99
0
            _ => None,
100
        }
101
0
    }
102
}
103
104
/// Lines which contain parsable data that can fail
105
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
106
enum LineType {
107
    Exposure,
108
    Pixaspect,
109
    Colorcorr,
110
    DimensionsHeight,
111
    DimensionsWidth,
112
}
113
114
impl fmt::Display for LineType {
115
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116
0
        f.write_str(match self {
117
0
            LineType::Exposure => "EXPOSURE",
118
0
            LineType::Pixaspect => "PIXASPECT",
119
0
            LineType::Colorcorr => "COLORCORR",
120
0
            LineType::DimensionsHeight => "height dimension",
121
0
            LineType::DimensionsWidth => "width dimension",
122
        })
123
0
    }
124
}
125
126
/// Radiance HDR file signature
127
pub const SIGNATURE: &[u8] = b"#?RADIANCE";
128
const SIGNATURE_LENGTH: usize = 10;
129
130
/// An arbitrary and generous limit on the length of the image header.
131
///
132
/// The HdrDecoder retains essentially the entire header in memory, because any
133
/// line could be a custom attribute, so a limit is useful to avoid allocating
134
/// too much.
135
///
136
/// Older images produced by Radiance tools often included the commands used to
137
/// generate the image;in particular, for composite images this could grow
138
/// rather large: some historical images have headers of up to 2-3 kilobytes.
139
const MAX_HEADER_LENGTH: usize = 1 << 16;
140
141
/// An Radiance HDR decoder
142
#[derive(Debug)]
143
pub struct HdrDecoder<R> {
144
    r: R,
145
    meta: HdrMetadata,
146
}
147
148
/// Refer to [wikipedia](https://en.wikipedia.org/wiki/RGBE_image_format)
149
#[repr(C)]
150
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
151
pub(crate) struct Rgbe8Pixel {
152
    /// Color components
153
    pub(crate) c: [u8; 3],
154
    /// Exponent
155
    pub(crate) e: u8,
156
}
157
158
/// Creates `Rgbe8Pixel` from components
159
0
pub(crate) fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel {
160
0
    Rgbe8Pixel { c: [r, g, b], e }
161
0
}
162
163
impl Rgbe8Pixel {
164
    /// Converts `Rgbe8Pixel` into `Rgb<f32>` linearly
165
    #[inline]
166
102M
    pub(crate) fn to_hdr(self) -> Rgb<f32> {
167
        // Directly construct the exponent 2.0^{e - 128 - 8}; because the normal
168
        // exponent value range of f32, 1..=254, is slightly smaller than the
169
        // range for rgbe8 (1..=255), a special case is needed to create the
170
        // subnormal intermediate value 2^{e - 128} for e=1; the branch also
171
        // implements the special case mapping of e=0 to exp=0.0.
172
102M
        let exp = f32::from_bits(if self.e > 1 {
173
84.7M
            ((self.e - 1) as u32) << 23
174
        } else {
175
17.6M
            (self.e as u32) << 22
176
        }) * 0.00390625;
177
178
102M
        Rgb([
179
102M
            exp * <f32 as From<_>>::from(self.c[0]),
180
102M
            exp * <f32 as From<_>>::from(self.c[1]),
181
102M
            exp * <f32 as From<_>>::from(self.c[2]),
182
102M
        ])
183
102M
    }
184
}
185
186
impl<R: Read> HdrDecoder<R> {
187
    /// Reads Radiance HDR image header from stream ```r```
188
    /// if the header is valid, creates `HdrDecoder`
189
    /// strict mode is enabled
190
0
    pub fn new(reader: R) -> ImageResult<Self> {
191
0
        HdrDecoder::with_strictness(reader, true)
192
0
    }
193
194
    /// Allows reading old Radiance HDR images
195
    #[deprecated(note = "Use `new_with_spec_compliance(reader, SpecCompliance::Lenient)` instead")]
196
0
    pub fn new_nonstrict(reader: R) -> ImageResult<Self> {
197
0
        Self::with_strictness(reader, false)
198
0
    }
199
200
    /// Create a new decoder with the given spec compliance mode.
201
4.13k
    pub(crate) fn new_with_spec_compliance(reader: R, spec: SpecCompliance) -> ImageResult<Self> {
202
4.13k
        Self::with_strictness(reader, spec == SpecCompliance::Strict)
203
4.13k
    }
204
205
    /// Reads Radiance HDR image header from stream `reader`,
206
    /// if the header is valid, creates `HdrDecoder`.
207
    ///
208
    /// strict enables strict mode
209
    ///
210
    /// Warning! Reading wrong file in non-strict mode could consume up to a few
211
    ///   megabytes of memory before this errors, if the file is large enough.
212
4.13k
    pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult<HdrDecoder<R>> {
213
4.13k
        let mut attributes = HdrMetadata::new();
214
215
        // Limit the total header length, ensuring that the total memory allocated
216
        // for lines and is not much more than a constant multiple of the length.
217
        // Because a new entry in attributes.custom_attributes is made for each
218
        // line, the constant may be quite large (at least `size_of::<String>()`,
219
        // likely no more than 100 depending on allocation overhead.); but even
220
        // so, in total no more than a few MB will be allocated.
221
4.13k
        let mut remaining_limit = MAX_HEADER_LENGTH;
222
        {
223
            // scope to make borrowck happy
224
4.13k
            let r = &mut reader;
225
4.13k
            if strict {
226
0
                let mut signature = [0; SIGNATURE_LENGTH];
227
0
                r.read_exact(&mut signature)?;
228
0
                if signature != SIGNATURE {
229
0
                    return Err(DecoderError::RadianceHdrSignatureInvalid.into());
230
0
                } // no else
231
                  // skip signature line ending
232
0
                read_line_u8(r, remaining_limit)?;
233
4.13k
            } else {
234
4.13k
                // Old Radiance HDR files (*.pic) don't use signature
235
4.13k
                // Let them be parsed in non-strict mode
236
4.13k
            }
237
            // read header data until empty line
238
            loop {
239
371k
                match read_line_u8(r, remaining_limit)? {
240
                    None => {
241
                        // EOF before end of header
242
1.39k
                        return Err(DecoderError::TruncatedHeader.into());
243
                    }
244
370k
                    Some(line) => {
245
370k
                        remaining_limit = remaining_limit.saturating_sub(line.len() + 1);
246
247
370k
                        if line.is_empty() {
248
                            // end of header
249
2.48k
                            break;
250
367k
                        } else if line[0] == b'#' {
251
                            // line[0] will not panic, line.len() == 0 is false here
252
                            // skip comments
253
2.55k
                            continue;
254
365k
                        } // no else
255
                          // process attribute line
256
365k
                        let line = String::from_utf8_lossy(&line[..]);
257
365k
                        attributes.update_header_info(&line, strict)?;
258
                    } // <= Some(line)
259
                } // match read_line_u8()
260
            } // loop
261
        } // scope to end borrow of reader
262
          // parse dimensions
263
2.48k
        let (width, height) = match read_line_u8(&mut reader, remaining_limit)? {
264
            None => {
265
                // EOF instead of image dimensions
266
2
                return Err(DecoderError::TruncatedDimensions.into());
267
            }
268
2.47k
            Some(dimensions) => {
269
2.47k
                let dimensions = String::from_utf8_lossy(&dimensions[..]);
270
2.47k
                parse_dimensions_line(&dimensions, strict)?
271
            }
272
        };
273
274
        // color type is always rgb8
275
1.62k
        if crate::utils::check_dimension_overflow(width, height, ColorType::Rgb8.bytes_per_pixel())
276
        {
277
1
            return Err(ImageError::Unsupported(
278
1
                UnsupportedError::from_format_and_kind(
279
1
                    ImageFormat::Hdr.into(),
280
1
                    UnsupportedErrorKind::GenericFeature(format!(
281
1
                        "Image dimensions ({width}x{height}) are too large"
282
1
                    )),
283
1
                ),
284
1
            ));
285
1.62k
        }
286
287
1.62k
        Ok(HdrDecoder {
288
1.62k
            r: reader,
289
1.62k
290
1.62k
            meta: HdrMetadata {
291
1.62k
                width,
292
1.62k
                height,
293
1.62k
                ..attributes
294
1.62k
            },
295
1.62k
        })
296
4.13k
    } // end with_strictness
297
298
    /// Returns file metadata. Refer to `HdrMetadata` for details.
299
0
    pub fn metadata(&self) -> HdrMetadata {
300
0
        self.meta.clone()
301
0
    }
302
}
303
304
impl<R: Read> ImageDecoder for HdrDecoder<R> {
305
6.09k
    fn dimensions(&self) -> (u32, u32) {
306
6.09k
        (self.meta.width, self.meta.height)
307
6.09k
    }
308
309
6.09k
    fn color_type(&self) -> ColorType {
310
6.09k
        ColorType::Rgb32F
311
6.09k
    }
312
313
1.50k
    fn set_limits(&mut self, mut limits: Limits) -> ImageResult<()> {
314
1.50k
        limits.check_support(&crate::LimitSupport::default())?;
315
1.50k
        limits.check_dimensions(self.meta.width, self.meta.height)?;
316
317
1.50k
        let scanline_space = (self.meta.width as u64) * (size_of::<Rgbe8Pixel>() as u64);
318
1.50k
        limits.reserve(scanline_space)?;
319
1.49k
        Ok(())
320
1.50k
    }
321
322
1.49k
    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
323
1.49k
        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
324
325
        // Don't read anything if image is empty
326
1.49k
        if self.meta.width == 0 || self.meta.height == 0 {
327
48
            return Ok(());
328
1.44k
        }
329
330
1.44k
        let mut scanline = vec![Default::default(); self.meta.width as usize];
331
332
        const PIXEL_SIZE: usize = size_of::<Rgb<f32>>();
333
1.44k
        let line_bytes = self.meta.width as usize * PIXEL_SIZE;
334
335
1.44k
        let chunks_iter = buf.chunks_exact_mut(line_bytes);
336
127k
        for chunk in chunks_iter {
337
            // read_scanline overwrites the entire buffer or returns an Err,
338
            // so not resetting the buffer here is ok.
339
126k
            read_scanline(&mut self.r, &mut scanline[..])?;
340
125k
            let dst_chunks = chunk.as_chunks_mut::<PIXEL_SIZE>().0.iter_mut();
341
102M
            for (dst, &pix) in dst_chunks.zip(scanline.iter()) {
342
102M
                dst.copy_from_slice(bytemuck::cast_slice(&pix.to_hdr().0));
343
102M
            }
344
        }
345
346
190
        Ok(())
347
1.49k
    }
348
349
1.49k
    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
350
1.49k
        (*self).read_image(buf)
351
1.49k
    }
352
}
353
354
// Precondition: buf.len() > 0
355
126k
fn read_scanline<R: Read>(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
356
126k
    assert!(!buf.is_empty());
357
126k
    let width = buf.len();
358
    // first 4 bytes in scanline allow to determine compression method
359
126k
    let fb = read_rgbe(r)?;
360
126k
    if fb.c[0] == 2 && fb.c[1] == 2 && fb.c[2] < 128 {
361
        // denormalized pixel value (2,2,<128,_) indicates new per component RLE method
362
        // decode_component guarantees that offset is within 0 .. width
363
        // therefore we can skip bounds checking here, but we will not
364
164M
        decode_component(r, width, |offset, value| buf[offset].c[0] = value)?;
365
131M
        decode_component(r, width, |offset, value| buf[offset].c[1] = value)?;
366
82.5M
        decode_component(r, width, |offset, value| buf[offset].c[2] = value)?;
367
29.8M
        decode_component(r, width, |offset, value| buf[offset].e = value)?;
368
    } else {
369
        // old RLE method (it was considered old around 1991, should it be here?)
370
124k
        decode_old_rle(r, fb, buf)?;
371
    }
372
125k
    Ok(())
373
126k
}
374
375
#[inline(always)]
376
8.71M
fn read_byte<R: Read>(r: &mut R) -> io::Result<u8> {
377
8.71M
    let mut buf = [0u8];
378
8.71M
    r.read_exact(&mut buf[..])?;
379
8.71M
    Ok(buf[0])
380
8.71M
}
381
382
// Guarantees that first parameter of set_component will be within pos .. pos+width
383
#[inline]
384
8.72k
fn decode_component<R: Read, S: FnMut(usize, u8)>(
385
8.72k
    r: &mut R,
386
8.72k
    width: usize,
387
8.72k
    mut set_component: S,
388
8.72k
) -> ImageResult<()> {
389
8.72k
    let mut buf = [0; 128];
390
8.72k
    let mut pos = 0;
391
4.51M
    while pos < width {
392
        // increment position by a number of decompressed values
393
        pos += {
394
4.51M
            let rl = read_byte(r)?;
395
4.51M
            if rl <= 128 {
396
                // sanity check
397
306k
                if pos + rl as usize > width {
398
134
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
399
306k
                }
400
                // read values
401
306k
                r.read_exact(&mut buf[0..rl as usize])?;
402
4.22M
                for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
403
4.22M
                    set_component(pos + offset, value);
404
4.22M
                }
405
306k
                rl as usize
406
            } else {
407
                // run
408
4.20M
                let rl = rl - 128;
409
                // sanity check
410
4.20M
                if pos + rl as usize > width {
411
97
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
412
4.20M
                }
413
                // fill with same value
414
4.20M
                let value = read_byte(r)?;
415
403M
                for offset in 0..rl as usize {
416
403M
                    set_component(pos + offset, value);
417
403M
                }
418
4.20M
                rl as usize
419
            }
420
        };
421
    }
422
7.94k
    if pos != width {
423
0
        return Err(DecoderError::WrongScanlineLength(pos, width).into());
424
7.94k
    }
425
7.94k
    Ok(())
426
8.72k
}
image::codecs::hdr::decoder::decode_component::<std::io::cursor::Cursor<&[u8]>, image::codecs::hdr::decoder::read_scanline<std::io::cursor::Cursor<&[u8]>>::{closure#0}>
Line
Count
Source
384
2.46k
fn decode_component<R: Read, S: FnMut(usize, u8)>(
385
2.46k
    r: &mut R,
386
2.46k
    width: usize,
387
2.46k
    mut set_component: S,
388
2.46k
) -> ImageResult<()> {
389
2.46k
    let mut buf = [0; 128];
390
2.46k
    let mut pos = 0;
391
1.84M
    while pos < width {
392
        // increment position by a number of decompressed values
393
        pos += {
394
1.84M
            let rl = read_byte(r)?;
395
1.84M
            if rl <= 128 {
396
                // sanity check
397
132k
                if pos + rl as usize > width {
398
23
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
399
131k
                }
400
                // read values
401
131k
                r.read_exact(&mut buf[0..rl as usize])?;
402
1.85M
                for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
403
1.85M
                    set_component(pos + offset, value);
404
1.85M
                }
405
131k
                rl as usize
406
            } else {
407
                // run
408
1.70M
                let rl = rl - 128;
409
                // sanity check
410
1.70M
                if pos + rl as usize > width {
411
25
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
412
1.70M
                }
413
                // fill with same value
414
1.70M
                let value = read_byte(r)?;
415
162M
                for offset in 0..rl as usize {
416
162M
                    set_component(pos + offset, value);
417
162M
                }
418
1.70M
                rl as usize
419
            }
420
        };
421
    }
422
2.28k
    if pos != width {
423
0
        return Err(DecoderError::WrongScanlineLength(pos, width).into());
424
2.28k
    }
425
2.28k
    Ok(())
426
2.46k
}
image::codecs::hdr::decoder::decode_component::<std::io::cursor::Cursor<&[u8]>, image::codecs::hdr::decoder::read_scanline<std::io::cursor::Cursor<&[u8]>>::{closure#2}>
Line
Count
Source
384
2.09k
fn decode_component<R: Read, S: FnMut(usize, u8)>(
385
2.09k
    r: &mut R,
386
2.09k
    width: usize,
387
2.09k
    mut set_component: S,
388
2.09k
) -> ImageResult<()> {
389
2.09k
    let mut buf = [0; 128];
390
2.09k
    let mut pos = 0;
391
912k
    while pos < width {
392
        // increment position by a number of decompressed values
393
        pos += {
394
910k
            let rl = read_byte(r)?;
395
910k
            if rl <= 128 {
396
                // sanity check
397
57.3k
                if pos + rl as usize > width {
398
45
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
399
57.3k
                }
400
                // read values
401
57.3k
                r.read_exact(&mut buf[0..rl as usize])?;
402
807k
                for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
403
807k
                    set_component(pos + offset, value);
404
807k
                }
405
57.2k
                rl as usize
406
            } else {
407
                // run
408
853k
                let rl = rl - 128;
409
                // sanity check
410
853k
                if pos + rl as usize > width {
411
29
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
412
853k
                }
413
                // fill with same value
414
853k
                let value = read_byte(r)?;
415
81.6M
                for offset in 0..rl as usize {
416
81.6M
                    set_component(pos + offset, value);
417
81.6M
                }
418
853k
                rl as usize
419
            }
420
        };
421
    }
422
1.88k
    if pos != width {
423
0
        return Err(DecoderError::WrongScanlineLength(pos, width).into());
424
1.88k
    }
425
1.88k
    Ok(())
426
2.09k
}
image::codecs::hdr::decoder::decode_component::<std::io::cursor::Cursor<&[u8]>, image::codecs::hdr::decoder::read_scanline<std::io::cursor::Cursor<&[u8]>>::{closure#3}>
Line
Count
Source
384
1.88k
fn decode_component<R: Read, S: FnMut(usize, u8)>(
385
1.88k
    r: &mut R,
386
1.88k
    width: usize,
387
1.88k
    mut set_component: S,
388
1.88k
) -> ImageResult<()> {
389
1.88k
    let mut buf = [0; 128];
390
1.88k
    let mut pos = 0;
391
321k
    while pos < width {
392
        // increment position by a number of decompressed values
393
        pos += {
394
320k
            let rl = read_byte(r)?;
395
320k
            if rl <= 128 {
396
                // sanity check
397
16.0k
                if pos + rl as usize > width {
398
43
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
399
15.9k
                }
400
                // read values
401
15.9k
                r.read_exact(&mut buf[0..rl as usize])?;
402
321k
                for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
403
321k
                    set_component(pos + offset, value);
404
321k
                }
405
15.9k
                rl as usize
406
            } else {
407
                // run
408
304k
                let rl = rl - 128;
409
                // sanity check
410
304k
                if pos + rl as usize > width {
411
25
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
412
304k
                }
413
                // fill with same value
414
304k
                let value = read_byte(r)?;
415
29.5M
                for offset in 0..rl as usize {
416
29.5M
                    set_component(pos + offset, value);
417
29.5M
                }
418
304k
                rl as usize
419
            }
420
        };
421
    }
422
1.67k
    if pos != width {
423
0
        return Err(DecoderError::WrongScanlineLength(pos, width).into());
424
1.67k
    }
425
1.67k
    Ok(())
426
1.88k
}
image::codecs::hdr::decoder::decode_component::<std::io::cursor::Cursor<&[u8]>, image::codecs::hdr::decoder::read_scanline<std::io::cursor::Cursor<&[u8]>>::{closure#1}>
Line
Count
Source
384
2.28k
fn decode_component<R: Read, S: FnMut(usize, u8)>(
385
2.28k
    r: &mut R,
386
2.28k
    width: usize,
387
2.28k
    mut set_component: S,
388
2.28k
) -> ImageResult<()> {
389
2.28k
    let mut buf = [0; 128];
390
2.28k
    let mut pos = 0;
391
1.44M
    while pos < width {
392
        // increment position by a number of decompressed values
393
        pos += {
394
1.43M
            let rl = read_byte(r)?;
395
1.43M
            if rl <= 128 {
396
                // sanity check
397
101k
                if pos + rl as usize > width {
398
23
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
399
101k
                }
400
                // read values
401
101k
                r.read_exact(&mut buf[0..rl as usize])?;
402
1.24M
                for (offset, &value) in buf[0..rl as usize].iter().enumerate() {
403
1.24M
                    set_component(pos + offset, value);
404
1.24M
                }
405
101k
                rl as usize
406
            } else {
407
                // run
408
1.33M
                let rl = rl - 128;
409
                // sanity check
410
1.33M
                if pos + rl as usize > width {
411
18
                    return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into());
412
1.33M
                }
413
                // fill with same value
414
1.33M
                let value = read_byte(r)?;
415
129M
                for offset in 0..rl as usize {
416
129M
                    set_component(pos + offset, value);
417
129M
                }
418
1.33M
                rl as usize
419
            }
420
        };
421
    }
422
2.09k
    if pos != width {
423
0
        return Err(DecoderError::WrongScanlineLength(pos, width).into());
424
2.09k
    }
425
2.09k
    Ok(())
426
2.28k
}
427
428
// Decodes scanline, places it into buf
429
// Precondition: buf.len() > 0
430
// fb - first 4 bytes of scanline
431
124k
fn decode_old_rle<R: Read>(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
432
124k
    assert!(!buf.is_empty());
433
124k
    let width = buf.len();
434
    // convenience function.
435
    // returns run length if pixel is a run length marker
436
    #[inline]
437
393k
    fn rl_marker(pix: Rgbe8Pixel) -> Option<usize> {
438
393k
        if pix.c == [1, 1, 1] {
439
2.10k
            Some(pix.e as usize)
440
        } else {
441
391k
            None
442
        }
443
393k
    }
444
    // first pixel in scanline should not be run length marker
445
    // it is error if it is
446
124k
    if rl_marker(fb).is_some() {
447
7
        return Err(DecoderError::FirstPixelRlMarker.into());
448
124k
    }
449
124k
    buf[0] = fb; // set first pixel of scanline
450
451
124k
    let mut x_off = 1; // current offset from beginning of a scanline
452
124k
    let mut rl_shift: u32 = 0; // current run length shift value
453
124k
    let mut prev_pixel = fb;
454
393k
    while x_off < width {
455
269k
        let pix = read_rgbe(r)?;
456
        // it's harder to forget to increase x_off if I write this this way.
457
        x_off += {
458
269k
            if let Some(rl) = rl_marker(pix) {
459
2.10k
                if rl_shift >= 32 {
460
196
                    if rl != 0 {
461
                        // run length of >= 2^32 rl
462
1
                        return Err(DecoderError::OverlyLongRepeat.into());
463
                    } else {
464
195
                        0
465
                    }
466
                } else {
467
                    // rl_shift takes care of consecutive RL markers
468
1.90k
                    let rl = rl << rl_shift;
469
1.90k
                    rl_shift += 8;
470
1.90k
                    if x_off + rl <= width {
471
                        // do run
472
176M
                        for b in &mut buf[x_off..x_off + rl] {
473
176M
                            *b = prev_pixel;
474
176M
                        }
475
                    } else {
476
43
                        return Err(DecoderError::WrongScanlineLength(x_off + rl, width).into());
477
                    };
478
1.86k
                    rl // value to increase x_off by
479
                }
480
            } else {
481
267k
                rl_shift = 0; // chain of consecutive RL markers is broken
482
267k
                prev_pixel = pix;
483
267k
                buf[x_off] = pix;
484
267k
                1 // value to increase x_off by
485
            }
486
        };
487
    }
488
123k
    if x_off != width {
489
0
        return Err(DecoderError::WrongScanlineLength(x_off, width).into());
490
123k
    }
491
123k
    Ok(())
492
124k
}
493
494
396k
fn read_rgbe<R: Read>(r: &mut R) -> io::Result<Rgbe8Pixel> {
495
396k
    let mut buf = [0u8; 4];
496
396k
    r.read_exact(&mut buf[..])?;
497
396k
    Ok(Rgbe8Pixel {
498
396k
        c: [buf[0], buf[1], buf[2]],
499
396k
        e: buf[3],
500
396k
    })
501
396k
}
502
503
/// Metadata for Radiance HDR image
504
#[derive(Debug, Clone)]
505
pub struct HdrMetadata {
506
    /// Width of decoded image. It could be either scanline length,
507
    /// or scanline count, depending on image orientation.
508
    pub width: u32,
509
    /// Height of decoded image. It depends on orientation too.
510
    pub height: u32,
511
    /// Orientation matrix. For standard orientation it is ((1,0),(0,1)) - left to right, top to bottom.
512
    /// First pair tells how resulting pixel coordinates change along a scanline.
513
    /// Second pair tells how they change from one scanline to the next.
514
    pub orientation: ((i8, i8), (i8, i8)),
515
    /// Divide color values by exposure to get to get physical radiance in
516
    /// watts/steradian/m<sup>2</sup>
517
    ///
518
    /// Image may not contain physical data, even if this field is set.
519
    pub exposure: Option<f32>,
520
    /// Divide color values by corresponding tuple member (r, g, b) to get to get physical radiance
521
    /// in watts/steradian/m<sup>2</sup>
522
    ///
523
    /// Image may not contain physical data, even if this field is set.
524
    pub color_correction: Option<(f32, f32, f32)>,
525
    /// Pixel height divided by pixel width
526
    pub pixel_aspect_ratio: Option<f32>,
527
    /// All lines contained in image header are put here. Ordering of lines is preserved.
528
    /// Lines in the form "key=value" are represented as ("key", "value").
529
    /// All other lines are ("", "line")
530
    pub custom_attributes: Vec<(String, String)>,
531
}
532
533
impl HdrMetadata {
534
4.13k
    fn new() -> HdrMetadata {
535
4.13k
        HdrMetadata {
536
4.13k
            width: 0,
537
4.13k
            height: 0,
538
4.13k
            orientation: ((1, 0), (0, 1)),
539
4.13k
            exposure: None,
540
4.13k
            color_correction: None,
541
4.13k
            pixel_aspect_ratio: None,
542
4.13k
            custom_attributes: vec![],
543
4.13k
        }
544
4.13k
    }
545
546
    // Updates header info, in strict mode returns error for malformed lines (no '=' separator)
547
    // unknown attributes are skipped
548
365k
    fn update_header_info(&mut self, line: &str, strict: bool) -> ImageResult<()> {
549
        // split line at first '='
550
        // old Radiance HDR files (*.pic) feature tabs in key, so                vvv trim
551
365k
        let maybe_key_value = split_at_first(line, "=").map(|(key, value)| (key.trim(), value));
552
        // save all header lines in custom_attributes
553
365k
        match maybe_key_value {
554
24.2k
            Some((key, val)) => self
555
24.2k
                .custom_attributes
556
24.2k
                .push((key.to_owned(), val.to_owned())),
557
341k
            None => self
558
341k
                .custom_attributes
559
341k
                .push((String::new(), line.to_owned())),
560
        }
561
        // parse known attributes
562
365k
        match maybe_key_value {
563
24.2k
            Some(("FORMAT", val)) => {
564
483
                if val.trim() != "32-bit_rle_rgbe" {
565
                    // XYZE isn't supported yet
566
221
                    return Err(ImageError::Unsupported(
567
221
                        UnsupportedError::from_format_and_kind(
568
221
                            ImageFormat::Hdr.into(),
569
221
                            UnsupportedErrorKind::Format(ImageFormatHint::Name(limit_string_len(
570
221
                                val, 20,
571
221
                            ))),
572
221
                        ),
573
221
                    ));
574
262
                }
575
            }
576
23.7k
            Some(("EXPOSURE", val)) => {
577
819
                match val.trim().parse::<f32>() {
578
610
                    Ok(v) => {
579
610
                        self.exposure = Some(self.exposure.unwrap_or(1.0) * v); // all encountered exposure values should be multiplied
580
610
                    }
581
209
                    Err(parse_error) => {
582
209
                        if strict {
583
0
                            return Err(DecoderError::UnparsableF32(
584
0
                                LineType::Exposure,
585
0
                                parse_error,
586
0
                            )
587
0
                            .into());
588
209
                        } // no else, skip this line in non-strict mode
589
                    }
590
                }
591
            }
592
22.9k
            Some(("PIXASPECT", val)) => {
593
1.09k
                match val.trim().parse::<f32>() {
594
819
                    Ok(v) => {
595
819
                        self.pixel_aspect_ratio = Some(self.pixel_aspect_ratio.unwrap_or(1.0) * v);
596
819
                        // all encountered exposure values should be multiplied
597
819
                    }
598
276
                    Err(parse_error) => {
599
276
                        if strict {
600
0
                            return Err(DecoderError::UnparsableF32(
601
0
                                LineType::Pixaspect,
602
0
                                parse_error,
603
0
                            )
604
0
                            .into());
605
276
                        } // no else, skip this line in non-strict mode
606
                    }
607
                }
608
            }
609
21.8k
            Some(("COLORCORR", val)) => {
610
2.98k
                let mut rgbcorr = [1.0, 1.0, 1.0];
611
2.98k
                match parse_space_separated_f32(val, &mut rgbcorr, LineType::Colorcorr) {
612
1.56k
                    Ok(extra_numbers) => {
613
1.56k
                        if strict && extra_numbers {
614
0
                            return Err(DecoderError::ExtraneousColorcorrNumbers.into());
615
1.56k
                        } // no else, just ignore extra numbers
616
1.56k
                        let (rc, gc, bc) = self.color_correction.unwrap_or((1.0, 1.0, 1.0));
617
1.56k
                        self.color_correction =
618
1.56k
                            Some((rc * rgbcorr[0], gc * rgbcorr[1], bc * rgbcorr[2]));
619
                    }
620
1.42k
                    Err(err) => {
621
1.42k
                        if strict {
622
0
                            return Err(err);
623
1.42k
                        } // no else, skip malformed line in non-strict mode
624
                    }
625
                }
626
            }
627
341k
            None => {
628
341k
                // old Radiance HDR files (*.pic) contain commands in a header
629
341k
                // just skip them
630
341k
            }
631
18.8k
            _ => {
632
18.8k
                // skip unknown attribute
633
18.8k
            }
634
        } // match attributes
635
365k
        Ok(())
636
365k
    }
637
}
638
639
2.98k
fn parse_space_separated_f32(line: &str, vals: &mut [f32], line_tp: LineType) -> ImageResult<bool> {
640
2.98k
    let mut nums = line.split_whitespace();
641
7.45k
    for val in vals.iter_mut() {
642
7.45k
        if let Some(num) = nums.next() {
643
6.77k
            match num.parse::<f32>() {
644
6.03k
                Ok(v) => *val = v,
645
742
                Err(err) => return Err(DecoderError::UnparsableF32(line_tp, err).into()),
646
            }
647
        } else {
648
            // not enough numbers in line
649
679
            return Err(DecoderError::LineTooShort(line_tp).into());
650
        }
651
    }
652
1.56k
    Ok(nums.next().is_some())
653
2.98k
}
654
655
// Parses dimension line "-Y height +X width"
656
// returns (width, height) or error
657
2.47k
fn parse_dimensions_line(line: &str, strict: bool) -> ImageResult<(u32, u32)> {
658
    const DIMENSIONS_COUNT: usize = 4;
659
660
2.47k
    let mut dim_parts = line.split_whitespace();
661
2.47k
    let c1_tag = dim_parts
662
2.47k
        .next()
663
2.47k
        .ok_or(DecoderError::DimensionsLineTooShort(0, DIMENSIONS_COUNT))?;
664
2.40k
    let c1_str = dim_parts
665
2.40k
        .next()
666
2.40k
        .ok_or(DecoderError::DimensionsLineTooShort(1, DIMENSIONS_COUNT))?;
667
2.14k
    let c2_tag = dim_parts
668
2.14k
        .next()
669
2.14k
        .ok_or(DecoderError::DimensionsLineTooShort(2, DIMENSIONS_COUNT))?;
670
2.12k
    let c2_str = dim_parts
671
2.12k
        .next()
672
2.12k
        .ok_or(DecoderError::DimensionsLineTooShort(3, DIMENSIONS_COUNT))?;
673
2.11k
    if strict && dim_parts.next().is_some() {
674
        // extra data in dimensions line
675
0
        return Err(DecoderError::DimensionsLineTooLong(DIMENSIONS_COUNT).into());
676
2.11k
    } // no else
677
      // dimensions line is in the form "-Y 10 +X 20"
678
      // There are 8 possible orientations: +Y +X, +X -Y and so on
679
2.11k
    match (c1_tag, c2_tag) {
680
2.11k
        ("-Y", "+X") => {
681
            // Common orientation (left-right, top-down)
682
            // c1_str is height, c2_str is width
683
1.82k
            let height = c1_str
684
1.82k
                .parse::<u32>()
685
1.82k
                .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsHeight, pe))?;
686
1.73k
            let width = c2_str
687
1.73k
                .parse::<u32>()
688
1.73k
                .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsWidth, pe))?;
689
1.62k
            Ok((width, height))
690
        }
691
285
        _ => Err(ImageError::Unsupported(
692
285
            UnsupportedError::from_format_and_kind(
693
285
                ImageFormat::Hdr.into(),
694
285
                UnsupportedErrorKind::GenericFeature(format!(
695
285
                    "Orientation {} {}",
696
285
                    limit_string_len(c1_tag, 4),
697
285
                    limit_string_len(c2_tag, 4)
698
285
                )),
699
285
            ),
700
285
        )),
701
    } // final expression. Returns value
702
2.47k
}
703
704
// Returns string with no more than len+3 characters
705
791
fn limit_string_len(s: &str, len: usize) -> String {
706
791
    let s_char_len = s.chars().count();
707
791
    if s_char_len > len {
708
372
        s.chars().take(len).chain("...".chars()).collect()
709
    } else {
710
419
        s.into()
711
    }
712
791
}
713
714
// Splits string into (before separator, after separator) tuple
715
// or None if separator isn't found
716
365k
fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> {
717
365k
    match s.find(separator) {
718
340k
        None | Some(0) => None,
719
24.8k
        Some(p) if p >= s.len() - separator.len() => None,
720
24.2k
        Some(p) => Some((&s[..p], &s[(p + separator.len())..])),
721
    }
722
365k
}
723
724
// Reads input until b"\n" or EOF
725
// Returns vector of read bytes NOT including end of line characters
726
//   or return None to indicate end of file
727
// Returns an error if the line would require more than max_len bytes
728
374k
fn read_line_u8<R: Read>(r: &mut R, max_len: usize) -> ImageResult<Option<Vec<u8>>> {
729
    // keeping repeated redundant allocations to avoid added complexity of having a `&mut tmp` argument
730
    #[allow(clippy::disallowed_methods)]
731
374k
    let mut ret = Vec::with_capacity(16);
732
    loop {
733
7.09M
        let mut byte = [0];
734
7.09M
        if r.read(&mut byte)? == 0 || byte[0] == b'\n' {
735
374k
            if ret.is_empty() && byte[0] != b'\n' {
736
1.39k
                return Ok(None);
737
372k
            }
738
372k
            return Ok(Some(ret));
739
6.72M
        }
740
741
6.72M
        if ret.len() >= max_len {
742
37
            return Err(DecoderError::HeaderTooLong.into());
743
6.72M
        }
744
6.72M
        ret.push(byte[0]);
745
    }
746
374k
}
747
748
#[cfg(test)]
749
mod tests {
750
    use std::{borrow::Cow, io::Cursor};
751
752
    use super::*;
753
754
    #[test]
755
    fn split_at_first_test() {
756
        assert_eq!(split_at_first(&Cow::Owned(String::new()), "="), None);
757
        assert_eq!(split_at_first(&Cow::Owned("=".into()), "="), None);
758
        assert_eq!(split_at_first(&Cow::Owned("= ".into()), "="), None);
759
        assert_eq!(
760
            split_at_first(&Cow::Owned(" = ".into()), "="),
761
            Some((" ", " "))
762
        );
763
        assert_eq!(
764
            split_at_first(&Cow::Owned("EXPOSURE= ".into()), "="),
765
            Some(("EXPOSURE", " "))
766
        );
767
        assert_eq!(
768
            split_at_first(&Cow::Owned("EXPOSURE= =".into()), "="),
769
            Some(("EXPOSURE", " ="))
770
        );
771
        assert_eq!(
772
            split_at_first(&Cow::Owned("EXPOSURE== =".into()), "=="),
773
            Some(("EXPOSURE", " ="))
774
        );
775
        assert_eq!(split_at_first(&Cow::Owned("EXPOSURE".into()), ""), None);
776
    }
777
778
    #[test]
779
    fn read_line_u8_test() {
780
        let buf: Vec<_> = (&b"One\nTwo\nThree\nFour\n\n\n"[..]).into();
781
        let input = &mut Cursor::new(buf);
782
        let read_line = |input: &mut Cursor<Vec<u8>>| -> Option<Vec<u8>> {
783
            read_line_u8(input, usize::MAX).unwrap()
784
        };
785
786
        assert_eq!(&read_line(input).unwrap()[..], &b"One"[..]);
787
        assert_eq!(&read_line(input).unwrap()[..], &b"Two"[..]);
788
        assert_eq!(&read_line(input).unwrap()[..], &b"Three"[..]);
789
        assert_eq!(&read_line(input).unwrap()[..], &b"Four"[..]);
790
        assert_eq!(&read_line(input).unwrap()[..], &b""[..]);
791
        assert_eq!(&read_line(input).unwrap()[..], &b""[..]);
792
        assert_eq!(read_line(input), None);
793
    }
794
795
    #[test]
796
    fn dimension_overflow() {
797
        let data = b"#?RADIANCE\nFORMAT=32-bit_rle_rgbe\n\n -Y 4294967295 +X 4294967295";
798
799
        assert!(HdrDecoder::new(Cursor::new(data)).is_err());
800
        assert!(
801
            HdrDecoder::new_with_spec_compliance(Cursor::new(data), SpecCompliance::Lenient)
802
                .is_err()
803
        );
804
    }
805
}