1use alloc::vec::Vec;
10use alloc::{format, vec};
11use std::collections::HashMap;
12
13use zune_core::bytestream::ZByteWriter;
14use zune_core::colorspace::ColorSpace;
15use zune_core::options::EncoderOptions;
16
17use crate::errors::HdrEncodeErrors;
18
19pub struct HdrEncoder<'a> {
21 data: &'a [f32],
22 headers: Option<&'a HashMap<String, String>>,
23 options: EncoderOptions
24}
25
26impl<'a> HdrEncoder<'a> {
27 pub fn new(data: &'a [f32], options: EncoderOptions) -> HdrEncoder<'a> {
34 Self {
35 data,
36 headers: None,
37 options
38 }
39 }
40 pub fn add_headers(&mut self, headers: &'a HashMap<String, String>) {
49 self.headers = Some(headers)
50 }
51
52 pub fn expected_buffer_size(&self) -> Option<usize> {
60 self.options
61 .get_width()
62 .checked_mul(self.options.get_height())?
63 .checked_mul(4)?
64 .checked_add(1024)
65 }
66
67 pub fn encode(&self) -> Result<Vec<u8>, HdrEncodeErrors> {
84 let expected = self
85 .options
86 .get_width()
87 .checked_mul(self.options.get_height())
88 .unwrap()
89 .checked_mul(3) .unwrap();
91 let found = self.data.len();
92
93 if expected != found {
94 return Err(HdrEncodeErrors::WrongInputSize(expected, found));
95 }
96 if self.options.get_colorspace() != ColorSpace::RGB {
97 return Err(HdrEncodeErrors::UnsupportedColorspace(
98 self.options.get_colorspace()
99 ));
100 }
101 let size = self
102 .expected_buffer_size()
103 .ok_or_else(|| HdrEncodeErrors::Static("overflow detected"))?;
104
105 let mut out = vec![0_u8; size];
106
107 let pos = self.encode_into(&mut out)?;
108 out.truncate(pos);
109
110 Ok(out)
111 }
112
113 pub fn encode_into(&self, out: &mut [u8]) -> Result<usize, HdrEncodeErrors> {
148 let mut writer = ZByteWriter::new(out);
149
150 {
152 writer.write_all(b"#?RADIANCE\n")?;
153 writer.write_all(b"SOFTWARE=zune-hdr\n")?;
154 if let Some(headers) = self.headers {
155 for (k, v) in headers {
156 writer.write_all(format!("{}={}\n", k, v).as_bytes())?;
157 }
158 }
159 writer.write_all(b"FORMAT=32-bit_rle_rgbe\n\n")?;
160
161 let length_format = format!(
163 "-Y {} +X {}\n",
164 self.options.get_height(),
165 self.options.get_width()
166 );
167
168 writer.write_all(length_format.as_bytes())?;
169 }
170 let width = self.options.get_width();
171
172 let scanline_stride = width * 3;
173
174 let mut in_scanline = vec![0_u8; width * 4]; for scanline in self.data.chunks_exact(scanline_stride) {
177 if !(8..=0x7fff).contains(&width) {
178 for (pixels, out) in scanline
179 .chunks_exact(3)
180 .zip(in_scanline.chunks_exact_mut(4))
181 {
182 float_to_rgbe(pixels.try_into().unwrap(), out.try_into().unwrap());
183 }
184 writer.write_all(&in_scanline)?;
185 } else {
186 writer.write_u8_err(2)?;
187 writer.write_u8_err(2)?;
188 writer.write_u8_err((width >> 8) as u8)?;
189 writer.write_u8_err((width & 255) as u8)?;
190
191 for (pixels, out) in scanline
192 .chunks_exact(3)
193 .zip(in_scanline.chunks_exact_mut(4))
194 {
195 float_to_rgbe(pixels.try_into().unwrap(), out.try_into().unwrap());
196 }
197 for i in 0..4 {
198 rle(&in_scanline[i..], &mut writer, width)?;
199 }
200 }
201 }
202 let position = writer.position();
204 Ok(position)
205 }
206}
207
208fn rle(data: &[u8], writer: &mut ZByteWriter, width: usize) -> Result<(), &'static str> {
209 const MIN_RLE: usize = 4;
210 let mut cur = 0;
211
212 while cur < width {
213 let mut run_count = 0;
214 let mut old_run_count = 0;
215 let mut beg_run = cur;
216 let mut buf: [u8; 2] = [0; 2];
217
218 while run_count < MIN_RLE && beg_run < width {
219 beg_run += run_count;
220 old_run_count = run_count;
221 run_count = 1;
222
223 while (beg_run + run_count < width)
224 && (run_count < 127)
225 && (data[beg_run * 4] == data[(beg_run + run_count) * 4])
226 {
227 run_count += 1;
228 }
229 }
230
231 if (old_run_count > 1) && (old_run_count == beg_run - cur) {
232 buf[0] = (128 + old_run_count) as u8;
233 buf[1] = data[cur * 4];
234 writer.write_all(&buf)?;
235 cur = beg_run;
236 }
237
238 while cur < beg_run {
239 let nonrun_count = 128.min(beg_run - cur);
240 buf[0] = nonrun_count as u8;
241 writer.write_u8(buf[0]);
242 for i in 0..nonrun_count {
243 writer.write_u8_err(data[(cur + i) * 4])?;
244 }
245
246 cur += nonrun_count;
247 }
248
249 if run_count >= MIN_RLE {
250 buf[0] = (128 + run_count) as u8;
251 buf[1] = data[beg_run * 4];
252 writer.write_all(&buf)?;
253 cur += run_count;
254 }
255 }
256 Ok(())
257}
258
259fn float_to_rgbe(rgb: &[f32; 3], rgbe: &mut [u8; 4]) {
260 let v = rgb.iter().fold(f32::MIN, |x, y| x.max(*y));
261
262 if v > 1e-32 {
263 let old_v = v;
264 let (mut v, e) = frexp(v);
265 v = v * 256. / old_v;
266 rgbe[0] = (rgb[0] * v).clamp(0.0, 255.0) as u8;
267 rgbe[1] = (rgb[1] * v).clamp(0.0, 255.0) as u8;
268 rgbe[2] = (rgb[2] * v).clamp(0.0, 255.0) as u8;
269
270 rgbe[3] = (e.wrapping_add(128)) as u8;
271 } else {
272 rgbe.fill(0);
273 }
274}
275
276#[rustfmt::skip]
277pub fn abs(num: f32) -> f32
278{
279 f32::from_bits(num.to_bits() & (i32::MAX as u32))
283}
284
285#[rustfmt::skip]
287pub fn signum(num: f32) -> f32
288{
289 if num.is_nan() { f32::NAN } else if num.is_infinite()
290 {
291 if num.is_sign_positive() { 0.0 } else { -0.0 }
292 } else if num > 0.0 { 1.0 } else { -1.0 }
293}
294
295fn floor(num: f32) -> f32 {
296 if num.is_nan() || num.is_infinite() {
297 return num;
299 }
300 let n = num as u64;
301 let d = n as f32;
302
303 if d == num || num >= 0.0 {
304 d
305 } else {
306 d - 1.0
307 }
308}
309
310#[allow(clippy::cast_precision_loss, clippy::cast_sign_loss)]
313fn fast_log2(x: f32) -> f32 {
314 let vx = x.to_bits();
321 let mx = (vx & 0x007F_FFFF) | 0x3f00_0000;
322 let mx_f = f32::from_bits(mx);
323
324 let mut y = vx as f32;
325 y *= 1.192_092_9e-7;
327
328 y - 124.225_52 - 1.498_030_3 * mx_f - 1.725_88 / (0.352_088_72 + mx_f)
329}
330
331fn frexp(s: f32) -> (f32, i32) {
333 if 0.0 == s {
335 (s, 0)
336 } else {
337 let lg = fast_log2(abs(s));
338 let lg_floor = floor(lg);
339 let x = (lg - lg_floor - 1.0).exp2();
344 let exp = lg_floor + 1.0;
345 (signum(s) * x, exp as i32)
346 }
347}