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