1use jxl_image::BitDepth;
2use jxl_render::{ImageBuffer, Region};
3use private::Sealed;
4
5#[derive(Debug, Clone)]
7pub struct FrameBuffer {
8 width: usize,
9 height: usize,
10 channels: usize,
11 buf: Vec<f32>,
12}
13
14impl FrameBuffer {
15 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 #[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 #[inline]
108 pub fn width(&self) -> usize {
109 self.width
110 }
111
112 #[inline]
114 pub fn height(&self) -> usize {
115 self.height
116 }
117
118 #[inline]
120 pub fn channels(&self) -> usize {
121 self.channels
122 }
123
124 #[inline]
129 pub fn buf(&self) -> &[f32] {
130 &self.buf
131 }
132
133 #[inline]
138 pub fn buf_mut(&mut self) -> &mut [f32] {
139 &mut self.buf
140 }
141
142 #[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 unsafe { std::slice::from_raw_parts(self.buf.as_ptr() as *const [f32; N], grouped_len) }
153 }
154
155 #[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 unsafe {
166 std::slice::from_raw_parts_mut(self.buf.as_mut_ptr() as *mut [f32; N], grouped_len)
167 }
168 }
169}
170
171pub 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 ®ions_and_shifts[..color_channels] {
210 start_offset_xy.push((left - region.left, top - region.top));
211 }
212
213 if render.is_cmyk {
215 for (ec_idx, (ec, (region, _))) in render
216 .extra_channels
217 .iter()
218 .zip(®ions_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 if !skip_alpha {
232 for (ec_idx, (ec, (region, _))) in render
233 .extra_channels
234 .iter()
235 .zip(®ions_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(®ions_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 #[inline]
293 pub fn width(&self) -> u32 {
294 self.width
295 }
296
297 #[inline]
299 pub fn height(&self) -> u32 {
300 self.height
301 }
302
303 #[inline]
305 pub fn channels(&self) -> u32 {
306 self.grids.len() as u32
307 }
308
309 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
409pub trait FrameBufferSample: private::Sealed {}
411
412impl FrameBufferSample for f32 {}
414
415impl FrameBufferSample for u16 {}
417
418impl 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}