zune_hdr/
encoder.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software; You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
5 */
6
7//! Radiance HDR encoder
8
9use alloc::{format, vec};
10use std::collections::HashMap;
11
12use zune_core::bytestream::{ZByteIoError, ZByteWriterTrait, ZWriter};
13use zune_core::colorspace::ColorSpace;
14use zune_core::options::EncoderOptions;
15
16use crate::errors::HdrEncodeErrors;
17
18/// A simple HDR encoder
19///
20/// Data is expected to be in `f32` and its size should be
21/// `width*height*3`
22pub struct HdrEncoder<'a> {
23    data:    &'a [f32],
24    headers: Option<&'a HashMap<String, String>>,
25    options: EncoderOptions
26}
27
28impl<'a> HdrEncoder<'a> {
29    /// Create a new HDR encoder context that can encode
30    /// the provided data
31    ///
32    /// # Arguments
33    ///  - `data`: Data to encode
34    ///  - `options`: Contains metadata for data, including width and height
35    pub fn new(data: &'a [f32], options: EncoderOptions) -> HdrEncoder<'a> {
36        Self {
37            data,
38            headers: None,
39            options
40        }
41    }
42    /// Add extra headers to be encoded  with the image
43    ///
44    /// This must be called before you call [`encode`](crate::encoder::HdrEncoder::encode)
45    /// otherwise it will have no effect.
46    ///
47    /// # Arguments:
48    /// - headers: A hashmap containing keys and values, the values will be encoded as key=value
49    /// in the hdr header before encoding
50    pub fn add_headers(&mut self, headers: &'a HashMap<String, String>) {
51        self.headers = Some(headers)
52    }
53
54    /// Calculate buffer with padding size needed for
55    /// encoding this into a vec
56    ///
57    /// This is a given upper limit and doesn't specifically mean that
58    /// your buffer should exactly be that size.
59    ///
60    /// The size of the output will depend on the nature of your data
61    pub fn expected_buffer_size(&self) -> Option<usize> {
62        self.options
63            .width()
64            .checked_mul(self.options.height())?
65            .checked_mul(4)?
66            .checked_add(1024)
67    }
68
69    /// Encode into a sink
70    ///
71    /// The encoder expects colorspace to be in RGB and the  length to match
72    ///
73    /// otherwise it's an error to try and encode
74    ///
75    /// The floating point data is expected to be normalized between 0.0 and 1.0, it will not be clipped
76    /// if not in this range
77    ///
78    /// The size of output cannot be determined up until compression is over hence
79    /// we cannot be sure if the output buffer will be enough.
80    ///
81    /// The library provides [expected_buffer_size](crate::HdrEncoder::expected_buffer_size) which
82    /// guarantees that if your buffer is that big, encoding will always succeed
83    ///
84    /// # Arguments:
85    /// - out: The output buffer to write bytes into
86    ///
87    /// # Returns
88    /// - Ok(usize):  The number of bytes written into out
89    /// - Err(HdrEncodeErrors): An error if something occurred
90    ///
91    /// # Examples
92    /// - Encode a black image of 10x10
93    ///```
94    /// use zune_core::bit_depth::BitDepth;
95    /// use zune_core::colorspace::ColorSpace;
96    /// use zune_core::options::EncoderOptions;
97    /// use zune_hdr::HdrEncoder;
98    /// let w = 10;
99    /// let h = 10;
100    /// let comp = 3;
101    /// let data = vec![0.0_f32;w*h*comp];
102    /// let opts = EncoderOptions::new(w,h,ColorSpace::RGB,BitDepth::Float32);
103    /// let encoder = HdrEncoder::new(&data,opts);
104    /// // create output buffer , this is the upper limit on it
105    /// let mut output = Vec::with_capacity(encoder.expected_buffer_size().unwrap());
106    /// let size = encoder.encode(&mut output).unwrap();
107    ///```  
108    ///
109    /// - Encode but directly write to a file
110    ///```no_run
111    /// use std::fs::OpenOptions;
112    /// use std::io::{BufReader, BufWriter};
113    /// use zune_core::bit_depth::BitDepth;
114    /// use zune_core::colorspace::ColorSpace;
115    /// use zune_core::options::EncoderOptions;
116    /// use zune_hdr::HdrEncoder;
117    /// let w = 10;
118    /// let h = 10;
119    /// let comp = 3;
120    /// let data = vec![0.0_f32;w*h*comp];
121    /// let opts = EncoderOptions::new(w,h,ColorSpace::RGB,BitDepth::Float32);
122    /// let encoder = HdrEncoder::new(&data,opts);
123    /// // create output buffer , this is the upper limit on it
124    /// let mut output = OpenOptions::new().create(true).write(true).truncate(true).open("./black.hdr").unwrap();
125    /// let mut buffered_output = BufWriter::new(output);
126    /// let size = encoder.encode(&mut buffered_output).unwrap();
127    ///
128    /// ```
129    pub fn encode<T: ZByteWriterTrait>(&self, out: T) -> Result<usize, HdrEncodeErrors> {
130        let expected = self
131            .options
132            .width()
133            .checked_mul(self.options.height())
134            .ok_or(HdrEncodeErrors::Static("overflow detected"))?
135            .checked_mul(3)
136            .ok_or(HdrEncodeErrors::Static("overflow detected"))?;
137
138        let found = self.data.len();
139
140        if expected != found {
141            return Err(HdrEncodeErrors::WrongInputSize(expected, found));
142        }
143        if self.options.colorspace() != ColorSpace::RGB {
144            return Err(HdrEncodeErrors::UnsupportedColorspace(
145                self.options.colorspace()
146            ));
147        }
148        let mut writer = ZWriter::new(out);
149        // reserve space
150        let size = self
151            .expected_buffer_size()
152            .ok_or(HdrEncodeErrors::Static("overflow detected"))?;
153        writer.reserve(size)?;
154        // write headers
155        {
156            writer.write_all(b"#?RADIANCE\n")?;
157            writer.write_all(b"SOFTWARE=zune-hdr\n")?;
158            if let Some(headers) = self.headers {
159                for (k, v) in headers {
160                    writer.write_all(format!("{}={}\n", k, v).as_bytes())?;
161                }
162            }
163            writer.write_all(b"FORMAT=32-bit_rle_rgbe\n\n")?;
164
165            // write lengths
166            let length_format =
167                format!("-Y {} +X {}\n", self.options.height(), self.options.width());
168
169            writer.write_all(length_format.as_bytes())?;
170        }
171        let width = self.options.width();
172
173        let scanline_stride = width * 3;
174
175        let mut in_scanline = vec![0_u8; width * 4]; // RGBE
176
177        for scanline in self.data.chunks_exact(scanline_stride) {
178            if !(8..=0x7fff).contains(&width) {
179                for (pixels, out) in scanline
180                    .chunks_exact(3)
181                    .zip(in_scanline.chunks_exact_mut(4))
182                {
183                    float_to_rgbe(pixels.try_into().unwrap(), out.try_into().unwrap());
184                }
185                writer.write_all(&in_scanline)?;
186            } else {
187                let bytes = [2, 2, (width >> 8) as u8, (width & 255) as u8];
188                writer.write_const_bytes(&bytes)?;
189
190                for (pixels, out) in scanline
191                    .chunks_exact(3)
192                    .zip(in_scanline.chunks_exact_mut(4))
193                {
194                    float_to_rgbe(pixels.try_into().unwrap(), out.try_into().unwrap());
195                }
196                for i in 0..4 {
197                    rle(&in_scanline[i..], &mut writer, width)?;
198                }
199            }
200        }
201        // truncate position to where we reached
202        let position = writer.bytes_written();
203        Ok(position)
204    }
205}
206
207fn rle<T: ZByteWriterTrait>(
208    data: &[u8], writer: &mut ZWriter<T>, width: usize
209) -> Result<(), ZByteIoError> {
210    const MIN_RLE: usize = 4;
211    let mut cur = 0;
212
213    while cur < width {
214        let mut run_count = 0;
215        let mut old_run_count = 0;
216        let mut beg_run = cur;
217        let mut buf: [u8; 2] = [0; 2];
218
219        while run_count < MIN_RLE && beg_run < width {
220            beg_run += run_count;
221            old_run_count = run_count;
222            run_count = 1;
223
224            while (beg_run + run_count < width)
225                && (run_count < 127)
226                && (data[beg_run * 4] == data[(beg_run + run_count) * 4])
227            {
228                run_count += 1;
229            }
230        }
231
232        if (old_run_count > 1) && (old_run_count == beg_run - cur) {
233            buf[0] = (128 + old_run_count) as u8;
234            buf[1] = data[cur * 4];
235            writer.write_all(&buf)?;
236            cur = beg_run;
237        }
238
239        while cur < beg_run {
240            let nonrun_count = 128.min(beg_run - cur);
241            buf[0] = nonrun_count as u8;
242            writer.write_u8(buf[0]);
243            for i in 0..nonrun_count {
244                writer.write_u8_err(data[(cur + i) * 4])?;
245            }
246
247            cur += nonrun_count;
248        }
249
250        if run_count >= MIN_RLE {
251            buf[0] = (128 + run_count) as u8;
252            buf[1] = data[beg_run * 4];
253            writer.write_all(&buf)?;
254            cur += run_count;
255        }
256    }
257    Ok(())
258}
259
260fn float_to_rgbe(rgb: &[f32; 3], rgbe: &mut [u8; 4]) {
261    let v = rgb.iter().fold(f32::MIN, |x, y| x.max(*y));
262
263    if v > 1e-32 {
264        let old_v = v;
265        let (mut v, e) = frexp(v);
266        v = v * 256. / old_v;
267        rgbe[0] = (rgb[0] * v).clamp(0.0, 255.0) as u8;
268        rgbe[1] = (rgb[1] * v).clamp(0.0, 255.0) as u8;
269        rgbe[2] = (rgb[2] * v).clamp(0.0, 255.0) as u8;
270
271        rgbe[3] = (e.wrapping_add(128)) as u8;
272    } else {
273        rgbe.fill(0);
274    }
275}
276
277#[rustfmt::skip]
278pub fn abs(num: f32) -> f32
279{
280    // standard compliant
281    // handles NAN and infinity.
282    // pretty cool
283    f32::from_bits(num.to_bits() & (i32::MAX as u32))
284}
285
286/// Implementation of signum that works in no_std
287#[rustfmt::skip]
288pub fn signum(num: f32) -> f32
289{
290    if num.is_nan() { f32::NAN } else if num.is_infinite()
291    {
292        if num.is_sign_positive() { 0.0 } else { -0.0 }
293    } else if num > 0.0 { 1.0 } else { -1.0 }
294}
295
296fn floor(num: f32) -> f32 {
297    if num.is_nan() || num.is_infinite() {
298        /* handle infinities and nan */
299        return num;
300    }
301    let n = num as u64;
302    let d = n as f32;
303
304    if d == num || num >= 0.0 {
305        d
306    } else {
307        d - 1.0
308    }
309}
310
311/// Fast log2 approximation
312/// (we really don't need that accurate)
313#[allow(clippy::cast_precision_loss, clippy::cast_sign_loss)]
314fn fast_log2(x: f32) -> f32 {
315    /*
316     * Fast log approximation from
317     * https://github.com/romeric/fastapprox
318     *
319     * Some pretty good stuff.
320     */
321    let vx = x.to_bits();
322    let mx = (vx & 0x007F_FFFF) | 0x3f00_0000;
323    let mx_f = f32::from_bits(mx);
324
325    let mut y = vx as f32;
326    // 1/(1<<23)
327    y *= 1.192_092_9e-7;
328
329    y - 124.225_52 - 1.498_030_3 * mx_f - 1.725_88 / (0.352_088_72 + mx_f)
330}
331
332/// non standard frexp implementation
333fn frexp(s: f32) -> (f32, i32) {
334    // from https://stackoverflow.com/a/55696477
335    if 0.0 == s {
336        (s, 0)
337    } else {
338        let lg = fast_log2(abs(s));
339        let lg_floor = floor(lg);
340        // Note: This is the only reason we need the standard library
341        // I haven't found a good exp2 function, fast_exp2 doesn't work
342        // and libm/musl exp2 introduces visible color distortions and is slow, so for
343        // now let's stick to whatever the platform provides
344        let x = (lg - lg_floor - 1.0).exp2();
345        let exp = lg_floor + 1.0;
346        (signum(s) * x, exp as i32)
347    }
348}