use crate::{api::JxlOutputBuffer, headers::Orientation, image::Rect, util::ShiftRightCeil};
#[derive(Debug)]
pub struct SaveStageBufferInfo {
pub downsample: (u8, u8),
pub orientation: Orientation,
pub byte_size: usize,
pub after_extend: bool,
}
pub struct BufferSplitter<'a, 'b> {
buffers: &'a mut [Option<JxlOutputBuffer<'b>>],
requested_rects: Vec<Rect>,
}
impl<'a, 'b> BufferSplitter<'a, 'b> {
pub fn new(bufs: &'a mut [Option<JxlOutputBuffer<'b>>]) -> Self {
Self {
buffers: bufs,
requested_rects: vec![],
}
}
pub(crate) fn get_local_buffers(
&mut self,
save_buffer_info: &[Option<SaveStageBufferInfo>],
rect: Rect,
outside_current_frame: bool,
frame_size: (usize, usize),
full_image_size: (usize, usize),
frame_origin: (isize, isize),
) -> Vec<Option<JxlOutputBuffer<'_>>> {
self.requested_rects.push(rect);
let mut local_buffers = vec![];
let buffers = &mut *self.buffers;
local_buffers.reserve(buffers.len());
for _ in 0..buffers.len() {
local_buffers.push(None::<JxlOutputBuffer>);
}
let rect = if !outside_current_frame {
rect.clip(frame_size)
} else {
rect
};
for (i, (info, buf)) in save_buffer_info.iter().zip(buffers.iter_mut()).enumerate() {
let Some(bi) = info else {
continue;
};
let Some(buf) = buf.as_mut() else {
continue;
};
if outside_current_frame && !bi.after_extend {
continue;
}
let mut channel_rect = rect.downsample(bi.downsample);
if !outside_current_frame {
let frame_size = (
frame_size.0.shrc(bi.downsample.0),
frame_size.1.shrc(bi.downsample.1),
);
channel_rect = channel_rect.clip(frame_size);
if bi.after_extend {
let origin = (
rect.origin.0 as isize + frame_origin.0,
rect.origin.1 as isize + frame_origin.1,
);
let end = (
origin.0 + rect.size.0 as isize,
origin.1 + rect.size.1 as isize,
);
let origin = (origin.0.max(0) as usize, origin.1.max(0) as usize);
let end = (
end.0.min(full_image_size.0 as isize).max(0) as usize,
end.1.min(full_image_size.1 as isize).max(0) as usize,
);
channel_rect = Rect {
origin,
size: (
end.0.saturating_sub(origin.0),
end.1.saturating_sub(origin.1),
),
};
}
}
if channel_rect.size.0 == 0 || channel_rect.size.1 == 0 {
continue;
}
let channel_rect = bi.orientation.display_rect(channel_rect, full_image_size);
let channel_rect = channel_rect.to_byte_rect_sz(bi.byte_size);
local_buffers[i] = Some(buf.rect(channel_rect));
}
local_buffers
}
pub fn into_changed_regions(self) -> Vec<Rect> {
self.requested_rects
}
#[cfg(any(test, feature = "threads"))]
pub fn get_full_buffers(&mut self) -> &mut [Option<JxlOutputBuffer<'b>>] {
&mut *self.buffers
}
}
#[cfg(feature = "threads")]
pub(crate) fn compute_channel_rect(
bi: &SaveStageBufferInfo,
rect: Rect,
frame_size: (usize, usize),
full_image_size: (usize, usize),
frame_origin: (isize, isize),
) -> Option<Rect> {
let mut channel_rect = rect.downsample(bi.downsample);
let ds_frame_size = (
frame_size.0.shrc(bi.downsample.0),
frame_size.1.shrc(bi.downsample.1),
);
channel_rect = channel_rect.clip(ds_frame_size);
if bi.after_extend {
let origin = (
rect.origin.0 as isize + frame_origin.0,
rect.origin.1 as isize + frame_origin.1,
);
let end = (
origin.0 + rect.size.0 as isize,
origin.1 + rect.size.1 as isize,
);
let origin = (origin.0.max(0) as usize, origin.1.max(0) as usize);
let end = (
end.0.min(full_image_size.0 as isize).max(0) as usize,
end.1.min(full_image_size.1 as isize).max(0) as usize,
);
channel_rect = Rect {
origin,
size: (
end.0.saturating_sub(origin.0),
end.1.saturating_sub(origin.1),
),
};
}
if channel_rect.size.0 == 0 || channel_rect.size.1 == 0 {
return None;
}
let channel_rect = bi.orientation.display_rect(channel_rect, full_image_size);
Some(channel_rect.to_byte_rect_sz(bi.byte_size))
}
#[cfg(feature = "threads")]
pub(crate) struct OwnedLocalBuffer {
pub(crate) data: Vec<u8>,
pub(crate) bytes_per_row: usize,
pub(crate) num_rows: usize,
pub(crate) channel_rect: Rect,
pub(crate) buffer_index: usize,
}
#[cfg(feature = "threads")]
#[allow(dead_code)] pub(crate) struct WorkItemOutput {
pub(crate) buffers: Vec<OwnedLocalBuffer>,
}
#[cfg(feature = "threads")]
pub(crate) fn compute_local_buffer_layouts(
save_buffer_info: &[Option<SaveStageBufferInfo>],
num_buffer_slots: usize,
rect: Rect,
frame_size: (usize, usize),
full_image_size: (usize, usize),
frame_origin: (isize, isize),
) -> Vec<(usize, usize, usize, Rect)> {
let rect = rect.clip(frame_size);
let mut layouts = Vec::new();
for (i, info) in save_buffer_info.iter().enumerate() {
let Some(bi) = info else {
continue;
};
if i >= num_buffer_slots {
continue;
}
let Some(channel_rect) =
compute_channel_rect(bi, rect, frame_size, full_image_size, frame_origin)
else {
continue;
};
let bytes_per_row = channel_rect.size.0;
let num_rows = channel_rect.size.1;
if bytes_per_row == 0 || num_rows == 0 {
continue;
}
layouts.push((i, bytes_per_row, num_rows, channel_rect));
}
layouts
}
#[cfg(feature = "threads")]
pub(crate) fn copy_back_local_buffers(
owned_buffers: &[OwnedLocalBuffer],
output: &mut [Option<JxlOutputBuffer<'_>>],
) {
for olb in owned_buffers {
let Some(buf) = output[olb.buffer_index].as_mut() else {
continue;
};
let channel_rect = olb.channel_rect;
let mut target = buf.rect(channel_rect);
for row in 0..olb.num_rows {
let src_start = row * olb.bytes_per_row;
let src_end = src_start + olb.bytes_per_row;
let dst_row = target.row_mut(row);
dst_row[..olb.bytes_per_row].copy_from_slice(&olb.data[src_start..src_end]);
}
}
}