Coverage Report

Created: 2025-12-05 07:37

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