From 50507577788a0636e00d2967d751836d7c583c6b Mon Sep 17 00:00:00 2001 From: Wonwoo Choi Date: Sat, 31 Aug 2024 16:16:52 +0900 Subject: [PATCH 1/4] Less buffer copies --- crates/jxl-color/src/convert.rs | 16 +++ crates/jxl-oxide-cli/src/decode.rs | 2 +- crates/jxl-oxide/src/lib.rs | 202 ++++++++++++----------------- crates/jxl-render/src/blend.rs | 18 +-- crates/jxl-render/src/image.rs | 73 +++++++---- crates/jxl-render/src/lib.rs | 190 ++++++++++++--------------- crates/jxl-render/src/render.rs | 4 +- crates/jxl-render/src/state.rs | 22 +--- 8 files changed, 247 insertions(+), 280 deletions(-) diff --git a/crates/jxl-color/src/convert.rs b/crates/jxl-color/src/convert.rs index f98ed3f7..d6c510c4 100644 --- a/crates/jxl-color/src/convert.rs +++ b/crates/jxl-color/src/convert.rs @@ -524,6 +524,22 @@ impl ColorTransform { self.ops.is_empty() } + #[inline] + pub fn input_channels(&self) -> usize { + self.begin_channels + } + + #[inline] + pub fn output_channels(&self) -> usize { + let mut channels = self.begin_channels; + for op in &self.ops { + if let Some(x) = op.outputs() { + channels = x; + } + } + channels + } + /// Performs the prepared color transformation on the samples. /// /// Returns the number of final channels after transformation. diff --git a/crates/jxl-oxide-cli/src/decode.rs b/crates/jxl-oxide-cli/src/decode.rs index 2c66d127..11e641f9 100644 --- a/crates/jxl-oxide-cli/src/decode.rs +++ b/crates/jxl-oxide-cli/src/decode.rs @@ -379,7 +379,7 @@ fn write_npy( ) -> std::io::Result<()> { let channels = { let first_frame = keyframes.first().unwrap(); - first_frame.color_channels().len() + first_frame.extra_channels().len() + first_frame.color_channels().len() + first_frame.extra_channels().0.len() }; let mut output = std::io::BufWriter::new(output); diff --git a/crates/jxl-oxide/src/lib.rs b/crates/jxl-oxide/src/lib.rs index 9ca63388..26daf88b 100644 --- a/crates/jxl-oxide/src/lib.rs +++ b/crates/jxl-oxide/src/lib.rs @@ -150,6 +150,7 @@ use jxl_bitstream::Name; use jxl_bitstream::{Bitstream, Bundle}; use jxl_frame::FrameContext; use jxl_render::ImageBuffer; +use jxl_render::ImageWithRegion; use jxl_render::Region; use jxl_render::{IndexedFrame, RenderContext}; @@ -719,11 +720,7 @@ impl JxlImage { /// Renders the given keyframe with optional cropping region. pub fn render_frame_cropped(&self, keyframe_index: usize) -> Result { - let mut image = self.ctx.render_keyframe(keyframe_index)?; - let grids = image.take_buffer(); - let regions = image.regions_and_shifts(); - let (color_channels, extra_channels) = self.process_render(grids)?; - let (color_regions, extra_regions) = regions.split_at(color_channels.len()); + let image = self.ctx.render_keyframe(keyframe_index)?; let image_region = self .ctx @@ -738,11 +735,9 @@ impl JxlImage { name: frame_header.name.clone(), duration: frame_header.duration, orientation: self.image_header.metadata.orientation, - color_channels, - extra_channels, + image, + extra_channels: self.convert_ec_info(), target_frame_region, - color_regions: color_regions.iter().map(|&(region, _)| region).collect(), - extra_regions: extra_regions.iter().map(|&(region, _)| region).collect(), color_bit_depth: self.image_header.metadata.bit_depth, }; Ok(result) @@ -755,16 +750,11 @@ impl JxlImage { /// Renders the currently loading keyframe with optional cropping region. pub fn render_loading_frame_cropped(&mut self) -> Result { - let (frame, mut image) = self.ctx.render_loading_keyframe()?; + let (frame, image) = self.ctx.render_loading_keyframe()?; let frame_header = frame.header(); let name = frame_header.name.clone(); let duration = frame_header.duration; - let grids = image.take_buffer(); - let regions = image.regions_and_shifts(); - let (color_channels, extra_channels) = self.process_render(grids)?; - let (color_regions, extra_regions) = regions.split_at(color_channels.len()); - let image_region = self .ctx .image_region() @@ -778,55 +768,25 @@ impl JxlImage { name, duration, orientation: self.image_header.metadata.orientation, - color_channels, - extra_channels, + image, + extra_channels: self.convert_ec_info(), target_frame_region, - color_regions: color_regions.iter().map(|&(region, _)| region).collect(), - extra_regions: extra_regions.iter().map(|&(region, _)| region).collect(), color_bit_depth: self.image_header.metadata.bit_depth, }; Ok(result) } - fn process_render( - &self, - mut grids: Vec, - ) -> Result<(Vec, Vec)> { - let _guard = tracing::trace_span!("Process rendered image").entered(); - - let pixel_format = self.pixel_format(); - let color_channels = if pixel_format.is_grayscale() { 1 } else { 3 }; - let mut color_channels: Vec<_> = grids.drain(..color_channels).collect(); - let mut extra_channels: Vec<_> = grids - .into_iter() - .zip(&self.image_header.metadata.ec_info) - .map(|(grid, ec_info)| ExtraChannel { + fn convert_ec_info(&self) -> Vec { + self.image_header + .metadata + .ec_info + .iter() + .map(|ec_info| ExtraChannel { ty: ec_info.ty, name: ec_info.name.clone(), bit_depth: ec_info.bit_depth, - grid, }) - .filter(|x| !x.is_black() || pixel_format.has_black()) // filter black channel - .collect(); - - let has_spot_colour = extra_channels.iter().any(|ec| ec.is_spot_colour()); - if self.render_spot_colour && color_channels.len() == 3 && has_spot_colour { - let bit_depth = self.image_header.metadata.bit_depth; - let [a, b, c] = &mut *color_channels else { - unreachable!() - }; - let a = a.convert_to_float_modular(bit_depth)?; - let b = b.convert_to_float_modular(bit_depth)?; - let c = c.convert_to_float_modular(bit_depth)?; - for ec in &mut extra_channels { - if ec.is_spot_colour() { - let ec_grid = ec.grid.convert_to_float_modular(ec.bit_depth)?; - jxl_render::render_spot_color([a, b, c], ec_grid, &ec.ty)?; - } - } - } - - Ok((color_channels, extra_channels)) + .collect() } } @@ -925,11 +885,9 @@ pub struct Render { name: Name, duration: u32, orientation: u32, - color_channels: Vec, + image: Arc, extra_channels: Vec, target_frame_region: Region, - color_regions: Vec, - extra_regions: Vec, color_bit_depth: BitDepth, } @@ -963,23 +921,40 @@ impl Render { /// Extra channels other than black and alpha are not included. #[inline] pub fn image(&self) -> FrameBuffer { - let mut fb: Vec<_> = self.color_channels.iter().collect(); + let buffers = self.image.buffer(); + let regions_and_shifts = self.image.regions_and_shifts(); + let color_channels = self.image.color_channels(); + + let mut fb: Vec<_> = buffers[..color_channels].iter().collect(); let mut bit_depth = vec![self.color_bit_depth; fb.len()]; - let mut regions = self.color_regions.clone(); + let mut regions: Vec<_> = regions_and_shifts[..color_channels] + .iter() + .map(|(region, _)| *region) + .collect(); // Find black - for (ec, ®ion) in self.extra_channels.iter().zip(&self.extra_regions) { + for (ec_idx, (ec, &(region, _))) in self + .extra_channels + .iter() + .zip(regions_and_shifts) + .enumerate() + { if ec.is_black() { - fb.push(&ec.grid); + fb.push(&buffers[color_channels + ec_idx]); bit_depth.push(ec.bit_depth); regions.push(region); break; } } // Find alpha - for (ec, ®ion) in self.extra_channels.iter().zip(&self.extra_regions) { + for (ec_idx, (ec, &(region, _))) in self + .extra_channels + .iter() + .zip(regions_and_shifts) + .enumerate() + { if ec.is_alpha() { - fb.push(&ec.grid); + fb.push(&buffers[color_channels + ec_idx]); bit_depth.push(ec.bit_depth); regions.push(region); break; @@ -1000,14 +975,17 @@ impl Render { /// All extra channels are included. #[inline] pub fn image_all_channels(&self) -> FrameBuffer { - let mut fb: Vec<_> = self.color_channels.iter().collect(); - let mut bit_depth = vec![self.color_bit_depth; fb.len()]; + let fb: Vec<_> = self.image.buffer().iter().collect(); + let mut bit_depth = vec![self.color_bit_depth; self.image.color_channels()]; for ec in &self.extra_channels { - fb.push(&ec.grid); bit_depth.push(ec.bit_depth); } - let mut regions = self.color_regions.clone(); - regions.extend_from_slice(&self.extra_regions); + let regions: Vec<_> = self + .image + .regions_and_shifts() + .iter() + .map(|(region, _)| *region) + .collect(); FrameBuffer::from_grids( &fb, @@ -1022,17 +1000,20 @@ impl Render { /// /// All extra channels are included. pub fn image_planar(&self) -> Vec { - self.color_channels + let grids = self.image.buffer(); + let bit_depth_it = std::iter::repeat(self.color_bit_depth) + .take(self.image.color_channels()) + .chain(self.extra_channels.iter().map(|ec| ec.bit_depth)); + let region_it = self + .image + .regions_and_shifts() .iter() - .zip(&self.color_regions) - .map(|(g, ®ion)| (g, self.color_bit_depth, region)) - .chain( - self.extra_channels - .iter() - .zip(&self.extra_regions) - .map(|(x, ®ion)| (&x.grid, x.bit_depth, region)), - ) - .map(|(x, bit_depth, region)| { + .map(|(region, _)| *region); + + bit_depth_it + .zip(region_it) + .zip(grids) + .map(|((bit_depth, region), x)| { FrameBuffer::from_grids( &[x], &[bit_depth], @@ -1049,32 +1030,17 @@ impl Render { /// Orientation is not applied. #[inline] pub fn color_channels(&self) -> &[ImageBuffer] { - &self.color_channels - } - - /// Returns the mutable slice to the color channels. - /// - /// Orientation is not applied. - #[inline] - pub fn color_channels_mut(&mut self) -> &mut [ImageBuffer] { - &mut self.color_channels + let color_channels = self.image.color_channels(); + &self.image.buffer()[..color_channels] } /// Returns the extra channels, potentially including alpha and black channels. /// /// Orientation is not applied. #[inline] - pub fn extra_channels(&self) -> &[ExtraChannel] { - &self.extra_channels - } - - /// Returns the mutable slice to the extra channels, potentially including alpha and black - /// channels. - /// - /// Orientation is not applied. - #[inline] - pub fn extra_channels_mut(&mut self) -> &mut [ExtraChannel] { - &mut self.extra_channels + pub fn extra_channels(&self) -> (&[ExtraChannel], &[ImageBuffer]) { + let color_channels = self.image.color_channels(); + (&self.extra_channels, &self.image.buffer()[color_channels..]) } } @@ -1095,27 +1061,42 @@ impl Render { if orientation >= 5 { std::mem::swap(&mut width, &mut height); } - let mut grids: Vec<_> = self.color_channels.iter().collect(); + + let fb = self.image.buffer(); + let color_channels = self.image.color_channels(); + let regions_and_shifts = self.image.regions_and_shifts(); + + let mut grids: Vec<_> = self.color_channels().iter().collect(); let mut bit_depth = vec![self.color_bit_depth; grids.len()]; let mut start_offset_xy = Vec::new(); - for region in &self.color_regions { + for (region, _) in ®ions_and_shifts[..color_channels] { start_offset_xy.push((left - region.left, top - region.top)); } // Find black - for (ec, region) in self.extra_channels.iter().zip(&self.extra_regions) { + for (ec_idx, (ec, (region, _))) in self + .extra_channels + .iter() + .zip(®ions_and_shifts[color_channels..]) + .enumerate() + { if ec.is_black() { - grids.push(&ec.grid); + grids.push(&fb[color_channels + ec_idx]); bit_depth.push(ec.bit_depth); start_offset_xy.push((left - region.left, top - region.top)); break; } } // Find alpha - for (ec, region) in self.extra_channels.iter().zip(&self.extra_regions) { + for (ec_idx, (ec, (region, _))) in self + .extra_channels + .iter() + .zip(®ions_and_shifts[color_channels..]) + .enumerate() + { if ec.is_alpha() { - grids.push(&ec.grid); + grids.push(&fb[color_channels + ec_idx]); bit_depth.push(ec.bit_depth); start_offset_xy.push((left - region.left, top - region.top)); break; @@ -1238,7 +1219,6 @@ pub struct ExtraChannel { ty: ExtraChannelType, name: Name, bit_depth: BitDepth, - grid: ImageBuffer, } impl ExtraChannel { @@ -1254,18 +1234,6 @@ impl ExtraChannel { &self.name } - /// Returns the sample grid of the channel. - #[inline] - pub fn grid(&self) -> &ImageBuffer { - &self.grid - } - - /// Returns the mutable sample grid of the channel. - #[inline] - pub fn grid_mut(&mut self) -> &mut ImageBuffer { - &mut self.grid - } - /// Returns `true` if the channel is a black channel of CMYK image. #[inline] pub fn is_black(&self) -> bool { diff --git a/crates/jxl-render/src/blend.rs b/crates/jxl-render/src/blend.rs index 47e99ed3..49dd0a3a 100644 --- a/crates/jxl-render/src/blend.rs +++ b/crates/jxl-render/src/blend.rs @@ -248,8 +248,8 @@ pub(crate) fn blend( can_overwrite = false; } - let base_grid = Arc::clone(&grid.image).run_with_image()?; - let mut base_grid = base_grid.blend(Some(output_image_region), pool)?; + let base_grid_render = Arc::clone(&grid.image).run_with_image()?; + let base_grid = base_grid_render.blend(Some(output_image_region), pool)?; assert_eq!(base_grid.color_channels(), color_channels); if base_grid.regions_and_shifts()[idx].0.is_empty() { @@ -267,8 +267,12 @@ pub(crate) fn blend( output_image_region.translate(-base_frame_header.x0, -base_frame_header.y0); target_grid = if can_overwrite { - let buffer = &mut base_grid.buffer_mut()[idx]; - std::mem::replace(buffer, ImageBuffer::F32(AlignedGrid::empty())) + if let Some(mut image) = base_grid_render.try_take_blended() { + let buffer = &mut image.buffer_mut()[idx]; + std::mem::replace(buffer, ImageBuffer::F32(AlignedGrid::empty())) + } else { + base_grid.buffer()[idx].try_clone()? + } } else { base_grid.buffer()[idx].try_clone()? }; @@ -413,7 +417,7 @@ pub(crate) fn blend( pub fn patch( image_header: &ImageHeader, base_grid: &mut ImageWithRegion, - patch_ref_grid: &mut ImageWithRegion, + patch_ref_grid: &ImageWithRegion, patch_ref: &PatchRef, ) -> Result<()> { use jxl_frame::data::PatchBlendMode; @@ -474,8 +478,6 @@ pub fn patch( ) .then_some(blending_info.alpha_channel as usize); - patch_ref_grid.buffer_mut()[idx].convert_to_float_modular(bit_depth)?; - let base_alpha; let new_alpha; let premultiplied; @@ -498,8 +500,6 @@ pub fn patch( (&mut r[0], &l[alpha_idx + color_channels]) }; base_alpha = Some(alpha.as_float().unwrap().as_subgrid()); - patch_ref_grid.buffer_mut()[alpha_idx + color_channels] - .convert_to_float_modular(alpha_bit_depth)?; new_alpha = Some( patch_ref_grid.buffer()[alpha_idx + color_channels] .as_float() diff --git a/crates/jxl-render/src/image.rs b/crates/jxl-render/src/image.rs index 0ecb9497..a8f71756 100644 --- a/crates/jxl-render/src/image.rs +++ b/crates/jxl-render/src/image.rs @@ -593,7 +593,7 @@ impl ImageWithRegion { } #[inline] - pub(crate) fn color_channels(&self) -> usize { + pub fn color_channels(&self) -> usize { self.color_channels } @@ -686,11 +686,11 @@ impl RenderedImage { } impl RenderedImage { - pub(crate) fn blend<'r>( - &'r self, + pub(crate) fn blend( + &self, oriented_image_region: Option, pool: &JxlThreadPool, - ) -> Result> { + ) -> Result> { let image_header = self.image.frame.image_header(); let frame_header = self.image.frame.header(); let image_region = self.image.image_region; @@ -712,47 +712,64 @@ impl RenderedImage { }; let mut grid_lock = self.image.render.lock().unwrap(); - let grid = grid_lock.as_grid_mut().unwrap(); - if grid.blend_done { - return Ok(BlendResult(grid_lock)); + if let FrameRender::Blended(image) = &*grid_lock { + return Ok(Arc::clone(image)); + } + + let render = std::mem::replace(&mut *grid_lock, FrameRender::ErrTaken); + let FrameRender::Done(mut grid) = render else { + panic!(); + }; + + if frame_header.can_reference() { + let bit_depth_it = std::iter::repeat(image_header.metadata.bit_depth) + .take(grid.color_channels) + .chain(image_header.metadata.ec_info.iter().map(|ec| ec.bit_depth)); + for (buffer, bit_depth) in grid.buffer.iter_mut().zip(bit_depth_it) { + buffer.convert_to_float_modular(bit_depth)?; + } } if !frame_header.frame_type.is_normal_frame() || frame_header.resets_canvas { grid.blend_done = true; - return Ok(BlendResult(grid_lock)); + let image = Arc::new(grid); + *grid_lock = FrameRender::Blended(Arc::clone(&image)); + return Ok(image); } if !grid.ct_done() { - util::convert_color_for_record(image_header, frame_header.do_ycbcr, grid, pool)?; + util::convert_color_for_record(image_header, frame_header.do_ycbcr, &mut grid, pool)?; } - let out = crate::blend::blend( + let image = crate::blend::blend( image_header, self.image.refs.clone(), &self.image.frame, - grid, + &mut grid, frame_region, pool, )?; - *grid = out; - Ok(BlendResult(grid_lock)) - } -} - -pub struct BlendResult<'r, S: Sample>(std::sync::MutexGuard<'r, FrameRender>); -impl std::ops::Deref for BlendResult<'_, S> { - type Target = ImageWithRegion; - - #[inline] - fn deref(&self) -> &Self::Target { - self.0.as_grid().unwrap() + let image = Arc::new(image); + *grid_lock = FrameRender::Blended(Arc::clone(&image)); + Ok(image) } -} -impl std::ops::DerefMut for BlendResult<'_, S> { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.as_grid_mut().unwrap() + pub(crate) fn try_take_blended(&self) -> Option { + let mut grid_lock = self.image.render.lock().unwrap(); + match std::mem::take(&mut *grid_lock) { + FrameRender::Blended(image) => { + let cloned_image = Arc::clone(&image); + let image_inner = Arc::into_inner(cloned_image); + if image_inner.is_none() { + *grid_lock = FrameRender::Blended(image); + } + image_inner + } + render => { + *grid_lock = render; + None + } + } } } diff --git a/crates/jxl-render/src/lib.rs b/crates/jxl-render/src/lib.rs index 42f4dc50..ddf9dd62 100644 --- a/crates/jxl-render/src/lib.rs +++ b/crates/jxl-render/src/lib.rs @@ -540,17 +540,15 @@ impl RenderContext { } } - fn render_by_index(&self, index: usize) -> Result { + fn render_by_index(&self, index: usize) -> Result> { if self.narrow_modular() { Arc::clone(&self.renders_narrow[index]) .run_with_image()? - .blend(None, &self.pool)? - .try_clone() + .blend(None, &self.pool) } else { Arc::clone(&self.renders_wide[index]) .run_with_image()? - .blend(None, &self.pool)? - .try_clone() + .blend(None, &self.pool) } } @@ -558,26 +556,25 @@ impl RenderContext { /// /// The keyframe should be loaded in prior to rendering, with one of the loading methods. #[inline] - pub fn render(&mut self) -> Result { + pub fn render(&mut self) -> Result> { self.render_keyframe(0) } /// Renders the keyframe. /// /// The keyframe should be loaded in prior to rendering, with one of the loading methods. - pub fn render_keyframe(&self, keyframe_idx: usize) -> Result { + pub fn render_keyframe(&self, keyframe_idx: usize) -> Result> { let idx = *self .keyframes .get(keyframe_idx) .ok_or(Error::IncompleteFrame)?; - let mut grid = self.render_by_index(idx)?; + let grid = self.render_by_index(idx)?; let frame = &*self.frames[idx]; - self.postprocess_keyframe(frame, &mut grid)?; - Ok(grid) + self.postprocess_keyframe(frame, grid) } - pub fn render_loading_keyframe(&mut self) -> Result<(&IndexedFrame, ImageWithRegion)> { + pub fn render_loading_keyframe(&mut self) -> Result<(&IndexedFrame, Arc)> { let mut current_frame_grid = None; if self.loading_frame().is_some() { let ret = self.render_loading_frame(); @@ -588,9 +585,9 @@ impl RenderContext { } } - let (frame, mut grid) = if let Some(grid) = current_frame_grid { + let (frame, grid) = if let Some(grid) = current_frame_grid { let frame = self.loading_frame().unwrap(); - (frame, grid) + (frame, Arc::new(grid)) } else if let Some(idx) = self.keyframe_in_progress { let grid = self.render_by_index(idx)?; let frame = &*self.frames[idx]; @@ -599,7 +596,7 @@ impl RenderContext { return Err(Error::IncompleteFrame); }; - self.postprocess_keyframe(frame, &mut grid)?; + let grid = self.postprocess_keyframe(frame, grid)?; Ok((frame, grid)) } @@ -769,110 +766,95 @@ impl RenderContext { } } - fn postprocess_keyframe(&self, frame: &IndexedFrame, grid: &mut ImageWithRegion) -> Result<()> { + fn postprocess_keyframe( + &self, + frame: &IndexedFrame, + grid: Arc, + ) -> Result> { let frame_header = frame.header(); - - /* - let oriented_image_region = util::apply_orientation_to_image_region( - &self.image_header, - self.requested_image_region, - ); - let frame_region = oriented_image_region.translate(-frame_header.x0, -frame_header.y0); - - if grid.region() != frame_region { - let mut new_grid = ImageWithRegion::from_region_and_tracker( - grid.channels(), - frame_region, - grid.ct_done(), - self.tracker.as_ref(), - )?; - for (ch, g) in new_grid.buffer_mut().iter_mut().enumerate() { - grid.clone_region_channel(frame_region, ch, g); - } - *grid = new_grid; - } - */ - let metadata = self.metadata(); - tracing::trace_span!("Transform to requested color encoding").in_scope( - || -> Result<()> { - let header_color_encoding = &metadata.colour_encoding; - let frame_color_encoding = if !grid.ct_done() && metadata.xyb_encoded { - ColorEncodingWithProfile::new(EnumColourEncoding::xyb( - jxl_color::RenderingIntent::Perceptual, - )) - } else if let ColourEncoding::Enum(encoding) = header_color_encoding { - ColorEncodingWithProfile::new(encoding.clone()) - } else { - ColorEncodingWithProfile::with_icc(&self.embedded_icc)? - }; - tracing::trace!(?frame_color_encoding); - tracing::trace!(requested_color_encoding = ?self.requested_color_encoding); - tracing::trace!(do_ycbcr = frame_header.do_ycbcr); + tracing::trace_span!("Transform to requested color encoding").in_scope(|| -> Result<_> { + let header_color_encoding = &metadata.colour_encoding; + let frame_color_encoding = if !grid.ct_done() && metadata.xyb_encoded { + ColorEncodingWithProfile::new(EnumColourEncoding::xyb( + jxl_color::RenderingIntent::Perceptual, + )) + } else if let ColourEncoding::Enum(encoding) = header_color_encoding { + ColorEncodingWithProfile::new(encoding.clone()) + } else { + ColorEncodingWithProfile::with_icc(&self.embedded_icc)? + }; + tracing::trace!(?frame_color_encoding); + tracing::trace!(requested_color_encoding = ?self.requested_color_encoding); + tracing::trace!(do_ycbcr = frame_header.do_ycbcr); - if !grid.ct_done() && frame_header.do_ycbcr { - grid.convert_modular_color(self.image_header.metadata.bit_depth)?; - jxl_color::ycbcr_to_rgb(grid.as_color_floats_mut()); - } - if grid.ct_done() { - return Ok(()); - } + if grid.ct_done() { + return Ok(grid); + } - let mut transform = jxl_color::ColorTransform::builder(); - transform.set_srgb_icc(!self.cms.supports_linear_tf()); - let transform = transform.build( - &frame_color_encoding, - &self.requested_color_encoding, - &metadata.opsin_inverse_matrix, - &metadata.tone_mapping, - )?; - if transform.is_noop() { - grid.set_ct_done(true); - return Ok(()); - } + let mut transform = jxl_color::ColorTransform::builder(); + transform.set_srgb_icc(!self.cms.supports_linear_tf()); + let transform = transform.build( + &frame_color_encoding, + &self.requested_color_encoding, + &metadata.opsin_inverse_matrix, + &metadata.tone_mapping, + )?; + if transform.is_noop() && !frame_header.do_ycbcr { + return Ok(grid); + } - let encoded_color_channels = frame_header.encoded_color_channels(); - if encoded_color_channels < 3 { - grid.clone_gray()?; - } + let mut grid = grid.try_clone()?; + if !grid.ct_done() && frame_header.do_ycbcr { grid.convert_modular_color(self.image_header.metadata.bit_depth)?; - let (color_channels, extra_channels) = grid.buffer_mut().split_at_mut(3); - let mut channels = Vec::new(); - for grid in color_channels { - channels.push(grid.as_float_mut().unwrap().buf_mut()); - } + jxl_color::ycbcr_to_rgb(grid.as_color_floats_mut()); + } + if transform.is_noop() { + let output_channels = transform.output_channels(); + grid.remove_color_channels(output_channels); + return Ok(Arc::new(grid)); + } - let mut has_black = false; - for (grid, ec_info) in extra_channels.iter_mut().zip(&metadata.ec_info) { - if ec_info.is_black() { - channels.push(grid.convert_to_float_modular(ec_info.bit_depth)?.buf_mut()); - has_black = true; - break; - } - } + let encoded_color_channels = frame_header.encoded_color_channels(); + if encoded_color_channels < 3 { + grid.clone_gray()?; + } - if has_black { - // 0 means full ink; invert samples - for grid in channels.iter_mut() { - for v in &mut **grid { - *v = 1.0 - *v; - } - } + grid.convert_modular_color(self.image_header.metadata.bit_depth)?; + let (color_channels, extra_channels) = grid.buffer_mut().split_at_mut(3); + let mut channels = Vec::new(); + for grid in color_channels { + channels.push(grid.as_float_mut().unwrap().buf_mut()); + } + + let mut has_black = false; + for (grid, ec_info) in extra_channels.iter_mut().zip(&metadata.ec_info) { + if ec_info.is_black() { + channels.push(grid.convert_to_float_modular(ec_info.bit_depth)?.buf_mut()); + has_black = true; + break; } + } - let output_channels = - transform.run_with_threads(&mut channels, &*self.cms, &self.pool)?; - if output_channels < 3 { - grid.remove_color_channels(output_channels); + if has_black { + // 0 means full ink; invert samples + for grid in channels.iter_mut() { + for v in &mut **grid { + *v = 1.0 - *v; + } } - grid.set_ct_done(true); - Ok(()) - }, - )?; + } - Ok(()) + let output_channels = + transform.run_with_threads(&mut channels, &*self.cms, &self.pool)?; + if output_channels < 3 { + grid.remove_color_channels(output_channels); + } + grid.set_ct_done(true); + Ok(Arc::new(grid)) + }) } } diff --git a/crates/jxl-render/src/render.rs b/crates/jxl-render/src/render.rs index 7abbdfc4..1edb9fa3 100644 --- a/crates/jxl-render/src/render.rs +++ b/crates/jxl-render/src/render.rs @@ -188,8 +188,8 @@ fn render_features( let oriented_image_region = Region::with_size(ref_header.width, ref_header.height) .translate(ref_header.x0, ref_header.y0); let ref_grid_image = std::sync::Arc::clone(&ref_grid.image).run_with_image()?; - let mut ref_grid_image = ref_grid_image.blend(Some(oriented_image_region), pool)?; - blend::patch(image_header, grid, &mut ref_grid_image, patch)?; + let ref_grid_image = ref_grid_image.blend(Some(oriented_image_region), pool)?; + blend::patch(image_header, grid, &ref_grid_image, patch)?; } } diff --git a/crates/jxl-render/src/state.rs b/crates/jxl-render/src/state.rs index 2cf09cb0..1e97a263 100644 --- a/crates/jxl-render/src/state.rs +++ b/crates/jxl-render/src/state.rs @@ -50,6 +50,7 @@ pub enum FrameRender { Rendering, InProgress(Box>), Done(ImageWithRegion), + Blended(Arc), Err(crate::Error), ErrTaken, } @@ -61,30 +62,13 @@ impl std::fmt::Debug for FrameRender { Self::Rendering => write!(f, "Rendering"), Self::InProgress(_) => write!(f, "InProgress(_)"), Self::Done(_) => write!(f, "Done(_)"), + Self::Blended(_) => write!(f, "Blended(_)"), Self::Err(e) => f.debug_tuple("Err").field(e).finish(), Self::ErrTaken => write!(f, "ErrTaken"), } } } -impl FrameRender { - pub(crate) fn as_grid(&self) -> Option<&ImageWithRegion> { - if let Self::Done(grid) = self { - Some(grid) - } else { - None - } - } - - pub(crate) fn as_grid_mut(&mut self) -> Option<&mut ImageWithRegion> { - if let Self::Done(grid) = self { - Some(grid) - } else { - None - } - } -} - pub struct FrameRenderHandle { pub(crate) frame: Arc, pub(crate) image_region: Region, @@ -223,7 +207,7 @@ impl FrameRenderHandle { tracing::trace!(index = self.frame.idx, "Waiting..."); render_ref = self.condvar.wait(render_ref).unwrap(); } - FrameRender::Done(_) => { + FrameRender::Done(_) | FrameRender::Blended(_) => { *render_ref = render; return Ok(render_ref); } From 206f8a68dd8afd7eb076f5a4c0dc8205eaf93d29 Mon Sep 17 00:00:00 2001 From: Wonwoo Choi Date: Sun, 1 Sep 2024 16:38:20 +0900 Subject: [PATCH 2/4] Render spot colors in ImageStream --- crates/jxl-oxide-cli/src/decode.rs | 2 +- crates/jxl-oxide/src/fb.rs | 241 ++++++++++++++++++++++++++ crates/jxl-oxide/src/lib.rs | 182 ++----------------- crates/jxl-oxide/tests/conformance.rs | 2 +- 4 files changed, 256 insertions(+), 171 deletions(-) diff --git a/crates/jxl-oxide-cli/src/decode.rs b/crates/jxl-oxide-cli/src/decode.rs index 11e641f9..5a9222ec 100644 --- a/crates/jxl-oxide-cli/src/decode.rs +++ b/crates/jxl-oxide-cli/src/decode.rs @@ -99,7 +99,7 @@ pub fn handle_decode(args: DecodeArgs) -> Result<()> { let mps = total_pixels as f64 / 1e6; if args.output_format == OutputFormat::Npy { - image.set_render_spot_colour(false); + image.set_render_spot_color(false); } let keyframes = if let Some(num_reps @ 2..) = args.num_reps { diff --git a/crates/jxl-oxide/src/fb.rs b/crates/jxl-oxide/src/fb.rs index 955d100f..2892a7da 100644 --- a/crates/jxl-oxide/src/fb.rs +++ b/crates/jxl-oxide/src/fb.rs @@ -166,3 +166,244 @@ impl FrameBuffer { } } } + +/// Image stream that writes to borrowed buffer. +pub struct ImageStream<'r> { + orientation: u32, + width: u32, + height: u32, + grids: Vec<&'r ImageBuffer>, + start_offset_xy: Vec<(i32, i32)>, + bit_depth: Vec, + spot_colors: Vec>, + y: u32, + x: u32, + c: u32, +} + +impl<'r> ImageStream<'r> { + pub(crate) fn from_render(render: &'r crate::Render) -> Self { + use jxl_image::ExtraChannelType; + + let orientation = render.orientation; + assert!((1..=8).contains(&orientation)); + let Region { + left, + top, + mut width, + mut height, + } = render.target_frame_region; + if orientation >= 5 { + std::mem::swap(&mut width, &mut height); + } + + let fb = render.image.buffer(); + let color_channels = render.image.color_channels(); + let regions_and_shifts = render.image.regions_and_shifts(); + + let mut grids: Vec<_> = render.color_channels().iter().collect(); + let mut bit_depth = vec![render.color_bit_depth; grids.len()]; + + let mut start_offset_xy = Vec::new(); + for (region, _) in ®ions_and_shifts[..color_channels] { + start_offset_xy.push((left - region.left, top - region.top)); + } + + // Find black + for (ec_idx, (ec, (region, _))) in render + .extra_channels + .iter() + .zip(®ions_and_shifts[color_channels..]) + .enumerate() + { + if ec.is_black() { + grids.push(&fb[color_channels + ec_idx]); + bit_depth.push(ec.bit_depth); + start_offset_xy.push((left - region.left, top - region.top)); + break; + } + } + // Find alpha + for (ec_idx, (ec, (region, _))) in render + .extra_channels + .iter() + .zip(®ions_and_shifts[color_channels..]) + .enumerate() + { + if ec.is_alpha() { + grids.push(&fb[color_channels + ec_idx]); + bit_depth.push(ec.bit_depth); + start_offset_xy.push((left - region.left, top - region.top)); + break; + } + } + + let mut spot_colors = Vec::new(); + if render.render_spot_color && color_channels == 3 { + for (ec_idx, (ec, (region, _))) in render + .extra_channels + .iter() + .zip(®ions_and_shifts[color_channels..]) + .enumerate() + { + if let ExtraChannelType::SpotColour { + red, + green, + blue, + solidity, + } = ec.ty + { + let grid = &fb[color_channels + ec_idx]; + let xy = (left - region.left, top - region.top); + spot_colors.push(ImageStreamSpotColor { + grid, + start_offset_xy: xy, + bit_depth: ec.bit_depth, + rgb: (red, green, blue), + solidity, + }); + } + } + } + + ImageStream { + orientation, + width, + height, + grids, + bit_depth, + start_offset_xy, + spot_colors, + y: 0, + x: 0, + c: 0, + } + } +} + +impl ImageStream<'_> { + /// Returns width of the image. + #[inline] + pub fn width(&self) -> u32 { + self.width + } + + /// Returns height of the image. + #[inline] + pub fn height(&self) -> u32 { + self.height + } + + /// Returns the number of channels of the image. + #[inline] + pub fn channels(&self) -> u32 { + self.grids.len() as u32 + } + + /// Writes next samples to the buffer, returning how many samples are written. + pub fn write_to_buffer(&mut self, buf: &mut [f32]) -> usize { + let channels = self.grids.len() as u32; + let mut buf_it = buf.iter_mut(); + let mut count = 0usize; + 'outer: while self.y < self.height { + while self.x < self.width { + while self.c < channels { + let Some(v) = buf_it.next() else { + break 'outer; + }; + let (start_x, start_y) = self.start_offset_xy[self.c as usize]; + let (orig_x, orig_y) = self.to_original_coord(self.x, self.y); + let (Some(x), Some(y)) = ( + orig_x.checked_add_signed(start_x), + orig_y.checked_add_signed(start_y), + ) else { + *v = 0.0; + count += 1; + self.c += 1; + continue; + }; + let x = x as usize; + let y = y as usize; + let grid = &self.grids[self.c as usize]; + let bit_depth = self.bit_depth[self.c as usize]; + *v = match grid { + ImageBuffer::F32(g) => g.get(x, y).copied().unwrap_or(0.0), + ImageBuffer::I32(g) => { + bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0)) + } + ImageBuffer::I16(g) => { + bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0) as i32) + } + }; + + if self.c < 3 { + for spot in &self.spot_colors { + let ImageStreamSpotColor { + grid, + start_offset_xy: (start_x, start_y), + bit_depth, + rgb: (r, g, b), + solidity, + } = *spot; + let color = [r, g, b][self.c as usize]; + let xy = ( + orig_x.checked_add_signed(start_x), + orig_y.checked_add_signed(start_y), + ); + let mix = if let (Some(x), Some(y)) = xy { + let x = x as usize; + let y = y as usize; + let val = match grid { + ImageBuffer::F32(g) => g.get(x, y).copied().unwrap_or(0.0), + ImageBuffer::I32(g) => bit_depth + .parse_integer_sample(g.get(x, y).copied().unwrap_or(0)), + ImageBuffer::I16(g) => bit_depth.parse_integer_sample( + g.get(x, y).copied().unwrap_or(0) as i32, + ), + }; + val * solidity + } else { + 0.0 + }; + + *v = color * mix + *v * (1.0 - mix); + } + } + + count += 1; + self.c += 1; + } + self.c = 0; + self.x += 1; + } + self.x = 0; + self.y += 1; + } + count + } + + #[inline] + fn to_original_coord(&self, x: u32, y: u32) -> (u32, u32) { + let width = self.width; + let height = self.height; + match self.orientation { + 1 => (x, y), + 2 => (width - x - 1, y), + 3 => (width - x - 1, height - y - 1), + 4 => (x, height - y - 1), + 5 => (y, x), + 6 => (y, width - x - 1), + 7 => (height - y - 1, width - x - 1), + 8 => (height - y - 1, x), + _ => unreachable!(), + } + } +} + +struct ImageStreamSpotColor<'r> { + grid: &'r ImageBuffer, + start_offset_xy: (i32, i32), + bit_depth: BitDepth, + rgb: (f32, f32, f32), + solidity: f32, +} diff --git a/crates/jxl-oxide/src/lib.rs b/crates/jxl-oxide/src/lib.rs index 26daf88b..595d4fbe 100644 --- a/crates/jxl-oxide/src/lib.rs +++ b/crates/jxl-oxide/src/lib.rs @@ -172,7 +172,7 @@ mod lcms2; #[cfg(feature = "lcms2")] pub use self::lcms2::Lcms2; -pub use fb::FrameBuffer; +pub use fb::{FrameBuffer, ImageStream}; pub type Result = std::result::Result>; @@ -386,7 +386,7 @@ impl UninitializedJxlImage { let bytes_read = bitstream.num_read_bits() / 8 + skip_bytes; self.buffer.drain(..bytes_read); - let render_spot_colour = !image_header.metadata.grayscale(); + let render_spot_color = !image_header.metadata.grayscale(); let mut builder = RenderContext::builder().pool(self.pool.clone()); if let Some(icc) = embedded_icc { @@ -405,7 +405,7 @@ impl UninitializedJxlImage { reader: self.reader, image_header, ctx, - render_spot_colour, + render_spot_color, end_of_image: false, buffer: Vec::new(), buffer_offset: bytes_read, @@ -433,7 +433,7 @@ pub struct JxlImage { reader: ContainerDetectingReader, image_header: Arc, ctx: RenderContext, - render_spot_colour: bool, + render_spot_color: bool, end_of_image: bool, buffer: Vec, buffer_offset: usize, @@ -641,18 +641,18 @@ impl JxlImage { /// Returns whether the spot color channels will be rendered. #[inline] - pub fn render_spot_colour(&self) -> bool { - self.render_spot_colour + pub fn render_spot_color(&self) -> bool { + self.render_spot_color } /// Sets whether the spot colour channels will be rendered. #[inline] - pub fn set_render_spot_colour(&mut self, render_spot_colour: bool) -> &mut Self { - if render_spot_colour && self.image_header.metadata.grayscale() { + pub fn set_render_spot_color(&mut self, render_spot_color: bool) -> &mut Self { + if render_spot_color && self.image_header.metadata.grayscale() { tracing::warn!("Spot colour channels are not rendered on grayscale images"); return self; } - self.render_spot_colour = render_spot_colour; + self.render_spot_color = render_spot_color; self } @@ -739,6 +739,7 @@ impl JxlImage { extra_channels: self.convert_ec_info(), target_frame_region, color_bit_depth: self.image_header.metadata.bit_depth, + render_spot_color: self.render_spot_color, }; Ok(result) } @@ -772,6 +773,7 @@ impl JxlImage { extra_channels: self.convert_ec_info(), target_frame_region, color_bit_depth: self.image_header.metadata.bit_depth, + render_spot_color: self.render_spot_color, }; Ok(result) } @@ -889,6 +891,7 @@ pub struct Render { extra_channels: Vec, target_frame_region: Region, color_bit_depth: BitDepth, + render_spot_color: bool, } impl Render { @@ -1050,166 +1053,7 @@ impl Render { /// The stream will include black and alpha channels, if exists, in addition to color channels. /// Orientation is applied. pub fn stream(&self) -> ImageStream { - let orientation = self.orientation; - assert!((1..=8).contains(&orientation)); - let Region { - left, - top, - mut width, - mut height, - } = self.target_frame_region; - if orientation >= 5 { - std::mem::swap(&mut width, &mut height); - } - - let fb = self.image.buffer(); - let color_channels = self.image.color_channels(); - let regions_and_shifts = self.image.regions_and_shifts(); - - let mut grids: Vec<_> = self.color_channels().iter().collect(); - let mut bit_depth = vec![self.color_bit_depth; grids.len()]; - - let mut start_offset_xy = Vec::new(); - for (region, _) in ®ions_and_shifts[..color_channels] { - start_offset_xy.push((left - region.left, top - region.top)); - } - - // Find black - for (ec_idx, (ec, (region, _))) in self - .extra_channels - .iter() - .zip(®ions_and_shifts[color_channels..]) - .enumerate() - { - if ec.is_black() { - grids.push(&fb[color_channels + ec_idx]); - bit_depth.push(ec.bit_depth); - start_offset_xy.push((left - region.left, top - region.top)); - break; - } - } - // Find alpha - for (ec_idx, (ec, (region, _))) in self - .extra_channels - .iter() - .zip(®ions_and_shifts[color_channels..]) - .enumerate() - { - if ec.is_alpha() { - grids.push(&fb[color_channels + ec_idx]); - bit_depth.push(ec.bit_depth); - start_offset_xy.push((left - region.left, top - region.top)); - break; - } - } - - ImageStream { - orientation, - width, - height, - grids, - bit_depth, - start_offset_xy, - y: 0, - x: 0, - c: 0, - } - } -} - -/// Image stream that writes to borrowed buffer. -pub struct ImageStream<'r> { - orientation: u32, - width: u32, - height: u32, - grids: Vec<&'r ImageBuffer>, - start_offset_xy: Vec<(i32, i32)>, - bit_depth: Vec, - y: u32, - x: u32, - c: u32, -} - -impl ImageStream<'_> { - /// Returns width of the image. - #[inline] - pub fn width(&self) -> u32 { - self.width - } - - /// Returns height of the image. - #[inline] - pub fn height(&self) -> u32 { - self.height - } - - /// Returns the number of channels of the image. - #[inline] - pub fn channels(&self) -> u32 { - self.grids.len() as u32 - } - - /// Writes next samples to the buffer, returning how many samples are written. - pub fn write_to_buffer(&mut self, buf: &mut [f32]) -> usize { - let channels = self.grids.len() as u32; - let mut buf_it = buf.iter_mut(); - let mut count = 0usize; - 'outer: while self.y < self.height { - while self.x < self.width { - while self.c < channels { - let Some(v) = buf_it.next() else { - break 'outer; - }; - let (start_x, start_y) = self.start_offset_xy[self.c as usize]; - let (x, y) = self.to_original_coord(self.x, self.y); - let (Some(x), Some(y)) = - (x.checked_add_signed(start_x), y.checked_add_signed(start_y)) - else { - *v = 0.0; - count += 1; - self.c += 1; - continue; - }; - let x = x as usize; - let y = y as usize; - let grid = &self.grids[self.c as usize]; - let bit_depth = self.bit_depth[self.c as usize]; - *v = match grid { - ImageBuffer::F32(g) => g.get(x, y).copied().unwrap_or(0.0), - ImageBuffer::I32(g) => { - bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0)) - } - ImageBuffer::I16(g) => { - bit_depth.parse_integer_sample(g.get(x, y).copied().unwrap_or(0) as i32) - } - }; - count += 1; - self.c += 1; - } - self.c = 0; - self.x += 1; - } - self.x = 0; - self.y += 1; - } - count - } - - #[inline] - fn to_original_coord(&self, x: u32, y: u32) -> (u32, u32) { - let width = self.width; - let height = self.height; - match self.orientation { - 1 => (x, y), - 2 => (width - x - 1, y), - 3 => (width - x - 1, height - y - 1), - 4 => (x, height - y - 1), - 5 => (y, x), - 6 => (y, width - x - 1), - 7 => (height - y - 1, width - x - 1), - 8 => (height - y - 1, x), - _ => unreachable!(), - } + ImageStream::from_render(self) } } diff --git a/crates/jxl-oxide/tests/conformance.rs b/crates/jxl-oxide/tests/conformance.rs index 8c5bad6b..a690f66c 100644 --- a/crates/jxl-oxide/tests/conformance.rs +++ b/crates/jxl-oxide/tests/conformance.rs @@ -60,7 +60,7 @@ fn run_test( ) { let debug = std::env::var("JXL_OXIDE_DEBUG").is_ok(); - image.set_render_spot_colour(false); + image.set_render_spot_color(false); if let Some(target_icc) = target_icc { image.request_icc(&target_icc).unwrap(); From 638fc062932bd6f0042c1d5821e5b1ac9cea97f4 Mon Sep 17 00:00:00 2001 From: Wonwoo Choi Date: Sun, 1 Sep 2024 17:03:29 +0900 Subject: [PATCH 3/4] jxl-oxide: Remove `Render::image` --- crates/jxl-oxide-wasm/src/lib.rs | 10 ++++- crates/jxl-oxide/src/lib.rs | 69 ++++---------------------------- 2 files changed, 15 insertions(+), 64 deletions(-) diff --git a/crates/jxl-oxide-wasm/src/lib.rs b/crates/jxl-oxide-wasm/src/lib.rs index 0a130464..d83e7b6b 100644 --- a/crates/jxl-oxide-wasm/src/lib.rs +++ b/crates/jxl-oxide-wasm/src/lib.rs @@ -254,10 +254,16 @@ impl RenderResult { #[wasm_bindgen(js_name = encodeToPng)] pub fn into_png(self) -> Result, String> { let image = self.image; + let mut stream = image.stream(); + let mut fb = jxl_oxide::FrameBuffer::new( + stream.width() as usize, + stream.height() as usize, + stream.channels() as usize, + ); + stream.write_to_buffer(fb.buf_mut()); - let fb = image.image(); let mut out = Vec::new(); - let mut encoder = png::Encoder::new(&mut out, fb.width() as u32, fb.height() as u32); + let mut encoder = png::Encoder::new(&mut out, stream.width(), stream.height()); let color = match self.pixfmt { PixelFormat::Gray => png::ColorType::Grayscale, PixelFormat::Graya => png::ColorType::GrayscaleAlpha, diff --git a/crates/jxl-oxide/src/lib.rs b/crates/jxl-oxide/src/lib.rs index 595d4fbe..060be01a 100644 --- a/crates/jxl-oxide/src/lib.rs +++ b/crates/jxl-oxide/src/lib.rs @@ -919,63 +919,18 @@ impl Render { self.orientation } - /// Creates a buffer with interleaved channels, with orientation applied. + /// Creates a stream that writes to borrowed buffer. /// - /// Extra channels other than black and alpha are not included. - #[inline] - pub fn image(&self) -> FrameBuffer { - let buffers = self.image.buffer(); - let regions_and_shifts = self.image.regions_and_shifts(); - let color_channels = self.image.color_channels(); - - let mut fb: Vec<_> = buffers[..color_channels].iter().collect(); - let mut bit_depth = vec![self.color_bit_depth; fb.len()]; - let mut regions: Vec<_> = regions_and_shifts[..color_channels] - .iter() - .map(|(region, _)| *region) - .collect(); - - // Find black - for (ec_idx, (ec, &(region, _))) in self - .extra_channels - .iter() - .zip(regions_and_shifts) - .enumerate() - { - if ec.is_black() { - fb.push(&buffers[color_channels + ec_idx]); - bit_depth.push(ec.bit_depth); - regions.push(region); - break; - } - } - // Find alpha - for (ec_idx, (ec, &(region, _))) in self - .extra_channels - .iter() - .zip(regions_and_shifts) - .enumerate() - { - if ec.is_alpha() { - fb.push(&buffers[color_channels + ec_idx]); - bit_depth.push(ec.bit_depth); - regions.push(region); - break; - } - } - - FrameBuffer::from_grids( - &fb, - &bit_depth, - ®ions, - self.target_frame_region, - self.orientation, - ) + /// The stream will include black and alpha channels, if exists, in addition to color channels. + /// Orientation is applied. + pub fn stream(&self) -> ImageStream { + ImageStream::from_render(self) } /// Creates a buffer with interleaved channels, with orientation applied. /// - /// All extra channels are included. + /// All extra channels are included. Use [`stream`](Render::stream) if only color, black and + /// alpha channels are needed. #[inline] pub fn image_all_channels(&self) -> FrameBuffer { let fb: Vec<_> = self.image.buffer().iter().collect(); @@ -1047,16 +1002,6 @@ impl Render { } } -impl Render { - /// Creates a stream that writes to borrowed buffer. - /// - /// The stream will include black and alpha channels, if exists, in addition to color channels. - /// Orientation is applied. - pub fn stream(&self) -> ImageStream { - ImageStream::from_render(self) - } -} - /// Extra channel of the image. #[derive(Debug)] pub struct ExtraChannel { From a19122a0d7b74d07abab566ee91683dedad2feee Mon Sep 17 00:00:00 2001 From: Wonwoo Choi Date: Sun, 1 Sep 2024 18:39:18 +0900 Subject: [PATCH 4/4] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d052b185..13a286a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Removed +- `jxl-oxide`: Remove `Render::image` (#334). Use `Render::stream` instead. + ### Fixed - `jxl-modular`: Fix incorrect color with complex inverse palette (#312).