Coverage Report

Created: 2026-01-22 07:28

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