zune_hdr/
decoder.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8
9use alloc::collections::BTreeMap;
10use alloc::string::{String, ToString};
11use alloc::vec;
12use alloc::vec::Vec;
13use core::iter::Iterator;
14use core::option::Option::{self, *};
15use core::result::Result::{self, *};
16
17use zune_core::bytestream::{ZByteReaderTrait, ZReader};
18use zune_core::colorspace::ColorSpace;
19use zune_core::log::trace;
20use zune_core::options::DecoderOptions;
21
22use crate::errors::HdrDecodeErrors;
23
24/// A simple radiance HDR decoder
25///
26/// # Accessing metadata
27///
28/// Radiance files may contain metadata in it's headers as key value pairs,
29/// we save the metadata in a hashmap and provide a way to inspect that metadata by exposing
30/// the map as an API access method.
31///
32/// For sophisticated algorithms, they may use the metadata to further understand the data.
33pub struct HdrDecoder<T: ZByteReaderTrait> {
34    buf:             ZReader<T>,
35    options:         DecoderOptions,
36    metadata:        BTreeMap<String, String>,
37    width:           usize,
38    height:          usize,
39    decoded_headers: bool
40}
41
42impl<T> HdrDecoder<T>
43where
44    T: ZByteReaderTrait
45{
46    /// Create a new HDR decoder
47    ///
48    /// # Arguments
49    ///
50    /// * `data`: Raw HDR file contents
51    ///
52    /// returns: HdrDecoder
53    ///
54    /// # Examples
55    ///
56    /// ```no_run
57    /// use zune_core::bytestream::ZCursor;
58    /// use zune_hdr::HdrDecoder;
59    /// // read hdr file to memory
60    /// let file_data = std::io::BufReader::new(std::fs::File::open("sample.hdr").unwrap());
61    /// let decoder = HdrDecoder::new(file_data);
62    /// ```
63    pub fn new(data: T) -> HdrDecoder<T> {
64        Self::new_with_options(data, DecoderOptions::default())
65    }
66
67    /// Create a new HDR decoder with the specified options
68    ///
69    /// # Arguments
70    ///
71    /// * `data`: Raw HDR file contents already in memory
72    /// * `options`: Decoder options that influence how decoding occurs
73    ///
74    /// returns: HdrDecoder
75    ///
76    /// # Examples
77    ///
78    /// ```no_run
79    /// use std::io::BufReader;
80    /// use zune_core::options::DecoderOptions;
81    /// use zune_hdr::HdrDecoder;
82    /// // read hdr file to memory
83    /// let file_data = std::fs::File::open("sample.hdr").unwrap();
84    /// // set that the decoder does not decode images greater than
85    /// // 50 px width
86    /// let options = DecoderOptions::default().set_max_width(50);
87    /// // use the options set
88    /// let decoder = HdrDecoder::new_with_options(BufReader::new(file_data),options);
89    /// ```
90    pub fn new_with_options(data: T, options: DecoderOptions) -> HdrDecoder<T> {
91        HdrDecoder {
92            buf: ZReader::new(data),
93            options,
94            width: 0,
95            height: 0,
96            metadata: BTreeMap::new(),
97            decoded_headers: false
98        }
99    }
100    /// Get key value metadata found in the header
101    ///
102    ///
103    /// In case the key or value contains non-valid UTF-8, the
104    /// characters are replaced with [REPLACEMENT_CHARACTER](core::char::REPLACEMENT_CHARACTER)
105    pub const fn metadata(&self) -> &BTreeMap<String, String> {
106        &self.metadata
107    }
108    /// Decode headers for the HDR image
109    ///
110    /// The struct is modified in place and data can be
111    /// extracted from appropriate getters.
112    pub fn decode_headers(&mut self) -> Result<(), HdrDecodeErrors> {
113        // maximum size for which we expect the buffer to be
114        let mut max_header_size = vec![0; 1024];
115
116        if self.decoded_headers {
117            return Ok(());
118        }
119        self.get_buffer_until(b'\n', &mut max_header_size)?;
120
121        if !(max_header_size.starts_with(b"#?RADIANCE\n")
122            || max_header_size.starts_with(b"#?RGBE\n"))
123        {
124            return Err(HdrDecodeErrors::InvalidMagicBytes);
125        }
126
127        loop {
128            let size = self.get_buffer_until(b'\n', &mut max_header_size)?;
129            if max_header_size.starts_with(b"#")
130            // comment
131            {
132                continue;
133            }
134            if max_header_size[..size].contains(&b'=') {
135                // key value, it should be lossy to avoid failure when the key is not valid
136                // utf-8, we throw garbage to the dictionary if the image is garbage
137                let keys_and_values = String::from_utf8_lossy(&max_header_size[..size]);
138
139                let mut keys_and_values_split = keys_and_values.trim().split('=');
140                let key = keys_and_values_split.next().unwrap().trim().to_string();
141                let value = keys_and_values_split.next().unwrap().trim().to_string();
142                self.metadata.insert(key, value);
143            }
144
145            if size == 0 || max_header_size[0] == b'\n' {
146                trace!("Metadata: {:?}", self.metadata);
147                break;
148            }
149        }
150        let header_size = self.get_buffer_until(b' ', &mut max_header_size)?;
151
152        let first_type = String::from_utf8_lossy(&max_header_size[..header_size])
153            .trim()
154            .to_string();
155
156        let header_size = self.get_buffer_until(b' ', &mut max_header_size)?;
157
158        let coords1 = String::from_utf8_lossy(&max_header_size[..header_size])
159            .trim()
160            .to_string();
161
162        let header_size = self.get_buffer_until(b' ', &mut max_header_size)?;
163
164        let second_type = String::from_utf8_lossy(&max_header_size[..header_size])
165            .trim()
166            .to_string();
167
168        let header_size = self.get_buffer_until(b'\n', &mut max_header_size)?;
169
170        let coords2 = String::from_utf8_lossy(&max_header_size[..header_size])
171            .trim()
172            .to_string();
173
174        match (first_type.as_str(), second_type.as_str()) {
175            ("-Y", "+X") => {
176                self.height = coords1.parse::<usize>()?;
177                self.width = coords2.parse::<usize>()?;
178            }
179            ("+X", "-Y") => {
180                self.height = coords2.parse::<usize>()?;
181                self.width = coords1.parse::<usize>()?;
182            }
183            (_, _) => {
184                return Err(HdrDecodeErrors::UnsupportedOrientation(
185                    first_type,
186                    second_type
187                ));
188            }
189        }
190        if self.height > self.options.max_height() {
191            return Err(HdrDecodeErrors::TooLargeDimensions(
192                "height",
193                self.options.max_height(),
194                self.height
195            ));
196        }
197
198        if self.width > self.options.max_width() {
199            return Err(HdrDecodeErrors::TooLargeDimensions(
200                "width",
201                self.options.max_width(),
202                self.width
203            ));
204        }
205
206        trace!("Width: {}", self.width);
207        trace!("Height: {}", self.height);
208
209        self.decoded_headers = true;
210
211        Ok(())
212    }
213
214    /// Get image dimensions as a tuple of width and height
215    /// or `None` if the image hasn't been decoded.
216    ///
217    /// # Returns
218    /// - `Some(width,height)`: Image dimensions
219    /// -  None : The image headers haven't been decoded
220    pub const fn dimensions(&self) -> Option<(usize, usize)> {
221        if self.decoded_headers {
222            Some((self.width, self.height))
223        } else {
224            None
225        }
226    }
227
228    /// Return the input colorspace of the image
229    ///
230    /// # Returns
231    /// -`Some(Colorspace)`: Input colorspace
232    /// - None : Indicates the headers weren't decoded
233    pub fn get_colorspace(&self) -> Option<ColorSpace> {
234        if self.decoded_headers {
235            Some(ColorSpace::RGB)
236        } else {
237            None
238        }
239    }
240
241    /// Decode HDR file return a vector containing decoded
242    /// coefficients
243    ///
244    /// # Returns
245    /// - `Ok(Vec<f32>)`: The actual decoded coefficients
246    /// - `Err(HdrDecodeErrors)`: Indicates an unrecoverable
247    ///  error occurred during decoding.
248    pub fn decode(&mut self) -> Result<Vec<f32>, HdrDecodeErrors> {
249        self.decode_headers()?;
250        let mut buffer = vec![0.0f32; self.width * self.height * 3];
251
252        self.decode_into(&mut buffer)?;
253
254        Ok(buffer)
255    }
256    /// Return the number of bytes required to hold a decoded image frame
257    /// decoded using the given input transformations
258    ///
259    /// # Returns
260    ///  - `Some(usize)`: Minimum size for a buffer needed to decode the image
261    ///  - `None`: Indicates the image headers were not decoded or
262    /// `width*height*colorspace` calculation  overflows a usize
263    ///
264    pub fn output_buffer_size(&self) -> Option<usize> {
265        if self.decoded_headers {
266            Some(self.width.checked_mul(self.height)?.checked_mul(3)?)
267        } else {
268            None
269        }
270    }
271
272    /// Decode into a pre-allocated buffer
273    ///
274    /// It is an error if the buffer size is smaller than
275    /// [`output_buffer_size()`](Self::output_buffer_size)
276    ///
277    /// If the buffer is bigger than expected, we ignore the end padding bytes
278    ///
279    /// # Example
280    ///
281    /// - Read  headers and then alloc a buffer big enough to hold the image
282    ///
283    /// ```no_run
284    /// use zune_core::bytestream::ZCursor;
285    /// use zune_hdr::HdrDecoder;
286    /// let mut decoder = HdrDecoder::new(ZCursor::new(&[]));
287    /// // before we get output, we must decode the headers to get width
288    /// // height, and input colorspace
289    /// decoder.decode_headers().unwrap();
290    ///
291    /// let mut out = vec![0.0;decoder.output_buffer_size().unwrap()];
292    /// // write into out
293    /// decoder.decode_into(&mut out).unwrap();
294    /// ```
295    pub fn decode_into(&mut self, buffer: &mut [f32]) -> Result<(), HdrDecodeErrors> {
296        if !self.decoded_headers {
297            self.decode_headers()?;
298        }
299
300        let output_size = self.output_buffer_size().unwrap();
301
302        if buffer.len() < output_size {
303            return Err(HdrDecodeErrors::TooSmallOutputArray(
304                output_size,
305                buffer.len()
306            ));
307        }
308        if self.width == 0 {
309           return Err(HdrDecodeErrors::Generic("Width cannot be 0"));
310        }
311
312        // single width scanline
313        let mut scanline = vec![0_u8; self.width * 4]; // R,G,B,E
314
315        let output_scanline_size = self.width * 3; // RGB, * width gives us size of one scanline
316
317        // read flat data
318        for out_scanline in buffer
319            .chunks_exact_mut(output_scanline_size)
320            .take(self.height)
321        {
322            if self.width < 8 || self.width > 0x7fff {
323                self.decompress(&mut scanline, self.width as i32, 0)?;
324                convert_scanline(&scanline, out_scanline);
325                continue;
326            }
327
328            let mut i = self.buf.read_u8();
329
330            if i != 2 {
331                // undo byte read
332                self.buf.rewind(1)?;
333
334                self.decompress(&mut scanline, self.width as i32, 0)?;
335                convert_scanline(&scanline, out_scanline);
336                continue;
337            }
338
339            scanline[1] = self.buf.read_u8_err()?;
340            scanline[2] = self.buf.read_u8_err()?;
341            i = self.buf.read_u8_err()?;
342
343            if scanline[1] != 2 || (scanline[2] & 128) != 0 {
344                scanline[0] = 2;
345                scanline[3] = i;
346
347                self.decompress(&mut scanline[4..], self.width as i32 - 1, 0)?;
348                convert_scanline(&scanline, out_scanline);
349                continue;
350            }
351
352            for i in 0..4 {
353                let new_scanline = &mut scanline[i..];
354
355                let mut j = 0;
356
357                loop {
358                    if j >= self.width * 4 {
359                        break;
360                    }
361                    let mut run = i32::from(self.buf.read_u8_err()?);
362
363                    if run > 128 {
364                        let val = self.buf.read_u8();
365                        run &= 127;
366
367                        while run > 0 {
368                            run -= 1;
369
370                            if j >= self.width * 4 {
371                                break;
372                            }
373                            new_scanline[j] = val;
374                            j += 4;
375                        }
376                    } else if run > 0 {
377                        while run > 0 {
378                            run -= 1;
379
380                            if j >= self.width * 4 {
381                                break;
382                            }
383
384                            new_scanline[j] = self.buf.read_u8();
385                            j += 4;
386                        }
387                    }
388                }
389            }
390            convert_scanline(&scanline, out_scanline);
391        }
392
393        Ok(())
394    }
395
396    fn decompress(
397        &mut self, scanline: &mut [u8], mut width: i32, mut scanline_offset: usize
398    ) -> Result<(), HdrDecodeErrors> {
399        let mut shift = 0;
400
401        while width > 0 {
402            scanline[scanline_offset] = self.buf.read_u8_err()?;
403            scanline[scanline_offset + 1] = self.buf.read_u8_err()?;
404            scanline[scanline_offset + 2] = self.buf.read_u8_err()?;
405            scanline[scanline_offset + 3] = self.buf.read_u8_err()?;
406
407            if scanline[scanline_offset] == 1
408                && scanline[scanline_offset + 1] == 1
409                && scanline[scanline_offset + 2] == 1
410            {
411                let run = scanline[scanline_offset + 3];
412
413                let mut i = i32::from(run) << shift;
414
415                while width > 0 && scanline_offset > 4 && i > 0 {
416                    scanline.copy_within(scanline_offset - 4..scanline_offset, 4);
417                    scanline_offset += 4;
418                    i -= 1;
419                    width -= 4;
420                }
421                shift += 8;
422
423                if shift > 16 {
424                    break;
425                }
426            } else {
427                scanline_offset += 4;
428                width -= 1;
429                shift = 0;
430            }
431        }
432        Ok(())
433    }
434
435    /// Get a whole radiance line and increment the buffer
436    /// cursor past that line.
437    ///
438    /// This will write to `write_to` appropriately
439    /// resizing the buffer in case the line spans a great length
440    fn get_buffer_until(
441        &mut self, needle: u8, write_to: &mut Vec<u8>
442    ) -> Result<usize, HdrDecodeErrors> {
443        write_to.clear();
444        let start = self.buf.position()?;
445
446        while !self.buf.eof()? {
447            let byte = self.buf.read_u8_err()?;
448            write_to.push(byte);
449
450            if byte == needle {
451                break;
452            }
453        }
454        let end = self.buf.position()?;
455
456        Ok(usize::try_from(end - start).unwrap())
457    }
458}
459
460fn convert_scanline(in_scanline: &[u8], out_scanline: &mut [f32]) {
461    for (rgbe, out) in in_scanline
462        .chunks_exact(4)
463        .zip(out_scanline.chunks_exact_mut(3))
464    {
465        if rgbe[3] == 0 {
466            out[0..3].fill(0.0);
467        } else {
468            // separate concerns to generate code that has better
469            //  ILP
470            let epxo = i32::from(rgbe[3]) - 128;
471
472            if epxo.is_positive() {
473                out[0] = convert_pos(i32::from(rgbe[0]), epxo);
474                out[1] = convert_pos(i32::from(rgbe[1]), epxo);
475                out[2] = convert_pos(i32::from(rgbe[2]), epxo);
476            } else {
477                out[0] = convert_neg(i32::from(rgbe[0]), epxo);
478                out[1] = convert_neg(i32::from(rgbe[1]), epxo);
479                out[2] = convert_neg(i32::from(rgbe[2]), epxo);
480            }
481        }
482    }
483}
484
485fn ldexp_pos(x: f32, exp: u32) -> f32 {
486    let pow = 1_u32.wrapping_shl(exp) as f32;
487    x * pow
488}
489fn ldexp_neg(x: f32, exp: u32) -> f32 {
490    let pow = 1_u32.wrapping_shl(exp) as f32;
491    x / pow
492}
493/// Fast calculation of  x*(2^exp).
494///
495/// exp is assumed to be integer
496// #[inline]
497// fn ldxep(x: f32, exp: i32) -> f32 {
498//     let pow = (1_i32 << (exp.abs() & 31)) as f32;
499//     if exp.is_negative() {
500//         // if negative 2 ^ exp is the same as 1 / (1<<exp.abs()) since
501//         // 2^(-exp) is sexpressed as 1/(2^exp)
502//         x / pow
503//     } else {
504//         // 2^exp is same as 1<<exp, but latter is way faster
505//         x * pow
506//     }
507// }
508
509#[inline]
510fn convert_pos(val: i32, exponent: i32) -> f32 {
511    let v = (val as f32) / 256.0;
512    ldexp_pos(v, exponent.unsigned_abs() & 31)
513}
514
515#[inline]
516fn convert_neg(val: i32, exponent: i32) -> f32 {
517    let v = (val as f32) / 256.0;
518    ldexp_neg(v, exponent.unsigned_abs() & 31)
519}