/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 | | } |