jxl_oxide/
fb.rs

1use jxl_image::BitDepth;
2use jxl_render::{ImageBuffer, Region};
3use private::Sealed;
4
5/// Frame buffer representing a decoded image.
6#[derive(Debug, Clone)]
7pub struct FrameBuffer {
8    width: usize,
9    height: usize,
10    channels: usize,
11    buf: Vec<f32>,
12}
13
14impl FrameBuffer {
15    /// Creates a new framebuffer with given dimension.
16    ///
17    /// Note that framebuffer allocations are not tracked.
18    pub fn new(width: usize, height: usize, channels: usize) -> Self {
19        Self {
20            width,
21            height,
22            channels,
23            buf: vec![0.0f32; width * height * channels],
24        }
25    }
26
27    /// For internal use only.
28    #[doc(hidden)]
29    pub fn from_grids(
30        grids: &[&ImageBuffer],
31        bit_depth: &[BitDepth],
32        grid_regions: &[Region],
33        copy_region: Region,
34        orientation: u32,
35    ) -> Self {
36        let channels = grids.len();
37        if channels == 0 {
38            panic!("framebuffer should have channels");
39        }
40        if !(1..=8).contains(&orientation) {
41            panic!("Invalid orientation {orientation}");
42        }
43
44        let Region {
45            left,
46            top,
47            width,
48            height,
49        } = copy_region;
50        let width = width as usize;
51        let height = height as usize;
52
53        let (outw, outh) = match orientation {
54            1..=4 => (width, height),
55            5..=8 => (height, width),
56            _ => unreachable!(),
57        };
58        let mut out = Self::new(outw, outh, channels);
59        let buf = out.buf_mut();
60        for y in 0..height {
61            for x in 0..width {
62                for (c, (g, region)) in grids.iter().zip(grid_regions).enumerate() {
63                    let (outx, outy) = match orientation {
64                        1 => (x, y),
65                        2 => (width - x - 1, y),
66                        3 => (width - x - 1, height - y - 1),
67                        4 => (x, height - y - 1),
68                        5 => (y, x),
69                        6 => (height - y - 1, x),
70                        7 => (height - y - 1, width - x - 1),
71                        8 => (y, width - x - 1),
72                        _ => unreachable!(),
73                    };
74                    let idx = c + (outx + outy * outw) * channels;
75
76                    let base_x = (left - region.left) as isize;
77                    let base_y = (top - region.top) as isize;
78                    let Some(x) = x.checked_add_signed(base_x) else {
79                        buf[idx] = 0.0;
80                        continue;
81                    };
82                    let Some(y) = y.checked_add_signed(base_y) else {
83                        buf[idx] = 0.0;
84                        continue;
85                    };
86                    if x >= region.width as usize || y >= region.height as usize {
87                        buf[idx] = 0.0;
88                        continue;
89                    }
90
91                    buf[idx] = match g {
92                        ImageBuffer::F32(g) => g.get(x, y).copied().unwrap_or(0.0),
93                        ImageBuffer::I32(g) => {
94                            bit_depth[c].parse_integer_sample(g.get(x, y).copied().unwrap_or(0))
95                        }
96                        ImageBuffer::I16(g) => bit_depth[c]
97                            .parse_integer_sample(g.get(x, y).copied().unwrap_or(0) as i32),
98                    };
99                }
100            }
101        }
102
103        out
104    }
105
106    /// Returns the width of the frame buffer.
107    #[inline]
108    pub fn width(&self) -> usize {
109        self.width
110    }
111
112    /// Returns the height of the frame buffer.
113    #[inline]
114    pub fn height(&self) -> usize {
115        self.height
116    }
117
118    /// Returns the number of channels of the frame buffer.
119    #[inline]
120    pub fn channels(&self) -> usize {
121        self.channels
122    }
123
124    /// Returns the contents of frame buffer.
125    ///
126    /// The buffer has length of `width * height * channels`, where `n * channels + c`-th sample
127    /// belongs to the `c`-th channel.
128    #[inline]
129    pub fn buf(&self) -> &[f32] {
130        &self.buf
131    }
132
133    /// Returns the mutable reference to frame buffer.
134    ///
135    /// The buffer has length of `width * height * channels`, where `n * channels + c`-th sample
136    /// belongs to the `c`-th channel.
137    #[inline]
138    pub fn buf_mut(&mut self) -> &mut [f32] {
139        &mut self.buf
140    }
141
142    /// Returns the contents of frame buffer, grouped by pixels.
143    ///
144    /// # Panics
145    /// Panics if `N != self.channels()`.
146    #[inline]
147    pub fn buf_grouped<const N: usize>(&self) -> &[[f32; N]] {
148        let grouped_len = self.width * self.height;
149        assert_eq!(self.buf.len(), grouped_len * N);
150        // SAFETY: Arrays have size of size_of::<T> * N, alignment of T.
151        // Buffer length is checked above.
152        unsafe { std::slice::from_raw_parts(self.buf.as_ptr() as *const [f32; N], grouped_len) }
153    }
154
155    /// Returns the mutable reference to frame buffer, grouped by pixels.
156    ///
157    /// # Panics
158    /// Panics if `N != self.channels()`.
159    #[inline]
160    pub fn buf_grouped_mut<const N: usize>(&mut self) -> &mut [[f32; N]] {
161        let grouped_len = self.width * self.height;
162        assert_eq!(self.buf.len(), grouped_len * N);
163        // SAFETY: Arrays have size of size_of::<T> * N, alignment of T.
164        // Buffer length is checked above.
165        unsafe {
166            std::slice::from_raw_parts_mut(self.buf.as_mut_ptr() as *mut [f32; N], grouped_len)
167        }
168    }
169}
170
171/// Image stream that writes to borrowed buffer.
172pub struct ImageStream<'r> {
173    orientation: u32,
174    width: u32,
175    height: u32,
176    grids: Vec<&'r ImageBuffer>,
177    start_offset_xy: Vec<(i32, i32)>,
178    bit_depth: Vec<BitDepth>,
179    spot_colors: Vec<ImageStreamSpotColor<'r>>,
180    y: u32,
181    x: u32,
182    c: u32,
183}
184
185impl<'r> ImageStream<'r> {
186    pub(crate) fn from_render(render: &'r crate::Render, skip_alpha: bool) -> Self {
187        use jxl_image::ExtraChannelType;
188
189        let orientation = render.orientation;
190        assert!((1..=8).contains(&orientation));
191        let Region {
192            left,
193            top,
194            mut width,
195            mut height,
196        } = render.target_frame_region;
197        if orientation >= 5 {
198            std::mem::swap(&mut width, &mut height);
199        }
200
201        let fb = render.image.buffer();
202        let color_channels = render.image.color_channels();
203        let regions_and_shifts = render.image.regions_and_shifts();
204
205        let mut grids: Vec<_> = render.color_channels().iter().collect();
206        let mut bit_depth = vec![render.color_bit_depth; grids.len()];
207
208        let mut start_offset_xy = Vec::new();
209        for (region, _) in &regions_and_shifts[..color_channels] {
210            start_offset_xy.push((left - region.left, top - region.top));
211        }
212
213        // Find black
214        if render.is_cmyk {
215            for (ec_idx, (ec, (region, _))) in render
216                .extra_channels
217                .iter()
218                .zip(&regions_and_shifts[color_channels..])
219                .enumerate()
220            {
221                if ec.is_black() {
222                    grids.push(&fb[color_channels + ec_idx]);
223                    bit_depth.push(ec.bit_depth);
224                    start_offset_xy.push((left - region.left, top - region.top));
225                    break;
226                }
227            }
228        }
229
230        // Find alpha
231        if !skip_alpha {
232            for (ec_idx, (ec, (region, _))) in render
233                .extra_channels
234                .iter()
235                .zip(&regions_and_shifts[color_channels..])
236                .enumerate()
237            {
238                if ec.is_alpha() {
239                    grids.push(&fb[color_channels + ec_idx]);
240                    bit_depth.push(ec.bit_depth);
241                    start_offset_xy.push((left - region.left, top - region.top));
242                    break;
243                }
244            }
245        }
246
247        let mut spot_colors = Vec::new();
248        if render.render_spot_color && color_channels == 3 {
249            for (ec_idx, (ec, (region, _))) in render
250                .extra_channels
251                .iter()
252                .zip(&regions_and_shifts[color_channels..])
253                .enumerate()
254            {
255                if let ExtraChannelType::SpotColour {
256                    red,
257                    green,
258                    blue,
259                    solidity,
260                } = ec.ty
261                {
262                    let grid = &fb[color_channels + ec_idx];
263                    let xy = (left - region.left, top - region.top);
264                    spot_colors.push(ImageStreamSpotColor {
265                        grid,
266                        start_offset_xy: xy,
267                        bit_depth: ec.bit_depth,
268                        rgb: (red, green, blue),
269                        solidity,
270                    });
271                }
272            }
273        }
274
275        ImageStream {
276            orientation,
277            width,
278            height,
279            grids,
280            bit_depth,
281            start_offset_xy,
282            spot_colors,
283            y: 0,
284            x: 0,
285            c: 0,
286        }
287    }
288}
289
290impl ImageStream<'_> {
291    /// Returns width of the image.
292    #[inline]
293    pub fn width(&self) -> u32 {
294        self.width
295    }
296
297    /// Returns height of the image.
298    #[inline]
299    pub fn height(&self) -> u32 {
300        self.height
301    }
302
303    /// Returns the number of channels of the image.
304    #[inline]
305    pub fn channels(&self) -> u32 {
306        self.grids.len() as u32
307    }
308
309    /// Writes next samples to the buffer, returning how many samples are written.
310    pub fn write_to_buffer<Sample: FrameBufferSample>(&mut self, buf: &mut [Sample]) -> usize {
311        let channels = self.grids.len() as u32;
312        let mut buf_it = buf.iter_mut();
313        let mut count = 0usize;
314        'outer: while self.y < self.height {
315            while self.x < self.width {
316                while self.c < channels {
317                    let Some(v) = buf_it.next() else {
318                        break 'outer;
319                    };
320                    let (start_x, start_y) = self.start_offset_xy[self.c as usize];
321                    let (orig_x, orig_y) = self.to_original_coord(self.x, self.y);
322                    let (Some(x), Some(y)) = (
323                        orig_x.checked_add_signed(start_x),
324                        orig_y.checked_add_signed(start_y),
325                    ) else {
326                        *v = Sample::default();
327                        count += 1;
328                        self.c += 1;
329                        continue;
330                    };
331                    let x = x as usize;
332                    let y = y as usize;
333                    let grid = &self.grids[self.c as usize];
334                    let bit_depth = self.bit_depth[self.c as usize];
335
336                    if self.c >= 3 || self.spot_colors.is_empty() {
337                        v.copy_from_grid(grid, x, y, bit_depth);
338                    } else {
339                        let mut tmp_sample = 0f32;
340                        tmp_sample.copy_from_grid(grid, x, y, bit_depth);
341
342                        for spot in &self.spot_colors {
343                            let ImageStreamSpotColor {
344                                grid,
345                                start_offset_xy: (start_x, start_y),
346                                bit_depth,
347                                rgb: (r, g, b),
348                                solidity,
349                            } = *spot;
350                            let color = [r, g, b][self.c as usize];
351                            let xy = (
352                                orig_x.checked_add_signed(start_x),
353                                orig_y.checked_add_signed(start_y),
354                            );
355                            let mix = if let (Some(x), Some(y)) = xy {
356                                let x = x as usize;
357                                let y = y as usize;
358                                let mut val = 0f32;
359                                val.copy_from_grid(grid, x, y, bit_depth);
360                                val * solidity
361                            } else {
362                                0.0
363                            };
364
365                            tmp_sample = color * mix + tmp_sample * (1.0 - mix);
366                        }
367
368                        v.copy_from_f32(tmp_sample);
369                    }
370
371                    count += 1;
372                    self.c += 1;
373                }
374                self.c = 0;
375                self.x += 1;
376            }
377            self.x = 0;
378            self.y += 1;
379        }
380        count
381    }
382
383    #[inline]
384    fn to_original_coord(&self, x: u32, y: u32) -> (u32, u32) {
385        let width = self.width;
386        let height = self.height;
387        match self.orientation {
388            1 => (x, y),
389            2 => (width - x - 1, y),
390            3 => (width - x - 1, height - y - 1),
391            4 => (x, height - y - 1),
392            5 => (y, x),
393            6 => (y, width - x - 1),
394            7 => (height - y - 1, width - x - 1),
395            8 => (height - y - 1, x),
396            _ => unreachable!(),
397        }
398    }
399}
400
401struct ImageStreamSpotColor<'r> {
402    grid: &'r ImageBuffer,
403    start_offset_xy: (i32, i32),
404    bit_depth: BitDepth,
405    rgb: (f32, f32, f32),
406    solidity: f32,
407}
408
409/// Marker trait for supported output sample types.
410pub trait FrameBufferSample: private::Sealed {}
411
412/// Output as 32-bit float samples, with nominal range of `[0, 1]`.
413impl FrameBufferSample for f32 {}
414
415/// Output as 16-bit unsigned integer samples.
416impl FrameBufferSample for u16 {}
417
418/// Output as 8-bit unsigned integer samples.
419impl FrameBufferSample for u8 {}
420
421mod private {
422    use jxl_image::BitDepth;
423    use jxl_render::ImageBuffer;
424
425    #[cfg(not(feature = "image"))]
426    pub trait Sealed: Sized + Default {
427        fn copy_from_grid(&mut self, grid: &ImageBuffer, x: usize, y: usize, bit_depth: BitDepth);
428        fn copy_from_f32(&mut self, val: f32);
429    }
430
431    #[cfg(feature = "image")]
432    pub trait Sealed: Sized + Default + bytemuck::NoUninit + bytemuck::AnyBitPattern {
433        fn copy_from_grid(&mut self, grid: &ImageBuffer, x: usize, y: usize, bit_depth: BitDepth);
434        fn copy_from_f32(&mut self, val: f32);
435    }
436
437    impl Sealed for f32 {
438        #[inline]
439        fn copy_from_grid(&mut self, grid: &ImageBuffer, x: usize, y: usize, bit_depth: BitDepth) {
440            *self = match grid {
441                ImageBuffer::F32(g) => g.get(x, y).copied().unwrap_or(0.0),
442                ImageBuffer::I32(g) => {
443                    bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0))
444                }
445                ImageBuffer::I16(g) => {
446                    bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0) as i32)
447                }
448            };
449        }
450
451        #[inline]
452        fn copy_from_f32(&mut self, val: f32) {
453            *self = val;
454        }
455    }
456
457    impl Sealed for u16 {
458        #[inline]
459        fn copy_from_grid(&mut self, grid: &ImageBuffer, x: usize, y: usize, bit_depth: BitDepth) {
460            if matches!(
461                bit_depth,
462                BitDepth::IntegerSample {
463                    bits_per_sample: 16
464                }
465            ) {
466                *self = match grid {
467                    ImageBuffer::F32(g) => (g.get(x, y).copied().unwrap_or(0.0) * 65535.0 + 0.5)
468                        .clamp(0.0, 65535.0) as u16,
469                    ImageBuffer::I32(g) => g.get(x, y).copied().unwrap_or(0).clamp(0, 65535) as u16,
470                    ImageBuffer::I16(g) => g.get(x, y).copied().unwrap_or(0).max(0) as u16,
471                };
472            } else {
473                let flt = match grid {
474                    ImageBuffer::F32(g) => g.get(x, y).copied().unwrap_or(0.0),
475                    ImageBuffer::I32(g) => {
476                        bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0))
477                    }
478                    ImageBuffer::I16(g) => {
479                        bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0) as i32)
480                    }
481                };
482                self.copy_from_f32(flt);
483            }
484        }
485
486        #[inline]
487        fn copy_from_f32(&mut self, val: f32) {
488            *self = (val * 65535.0 + 0.5).clamp(0.0, 65535.0) as u16;
489        }
490    }
491
492    impl Sealed for u8 {
493        #[inline]
494        fn copy_from_grid(&mut self, grid: &ImageBuffer, x: usize, y: usize, bit_depth: BitDepth) {
495            if matches!(bit_depth, BitDepth::IntegerSample { bits_per_sample: 8 }) {
496                *self = match grid {
497                    ImageBuffer::F32(g) => {
498                        (g.get(x, y).copied().unwrap_or(0.0) * 255.0 + 0.5).clamp(0.0, 255.0) as u8
499                    }
500                    ImageBuffer::I32(g) => g.get(x, y).copied().unwrap_or(0).clamp(0, 255) as u8,
501                    ImageBuffer::I16(g) => g.get(x, y).copied().unwrap_or(0).clamp(0, 255) as u8,
502                };
503            } else {
504                let flt = match grid {
505                    ImageBuffer::F32(g) => g.get(x, y).copied().unwrap_or(0.0),
506                    ImageBuffer::I32(g) => {
507                        bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0))
508                    }
509                    ImageBuffer::I16(g) => {
510                        bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0) as i32)
511                    }
512                };
513                self.copy_from_f32(flt);
514            }
515        }
516
517        #[inline]
518        fn copy_from_f32(&mut self, val: f32) {
519            *self = (val * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
520        }
521    }
522}