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