From 168ef57e44a9a9e4f645ddcbcdbe3742cf49ad1e Mon Sep 17 00:00:00 2001 From: Wonwoo Choi Date: Mon, 28 Aug 2023 22:19:57 +0900 Subject: [PATCH] jxl-oxide: Merge JxlRenderer into JxlImage --- crates/jxl-oxide-cli/src/bin/jxl-dec.rs | 7 +- crates/jxl-oxide-cli/src/bin/jxl-info.rs | 5 +- crates/jxl-oxide/src/bin/generate-fixture.rs | 3 +- crates/jxl-oxide/src/lib.rs | 134 ++++++------------- crates/jxl-oxide/tests/conformance.rs | 9 +- crates/jxl-oxide/tests/decode.rs | 3 +- 6 files changed, 49 insertions(+), 112 deletions(-) diff --git a/crates/jxl-oxide-cli/src/bin/jxl-dec.rs b/crates/jxl-oxide-cli/src/bin/jxl-dec.rs index 306a002d..e97f6d12 100644 --- a/crates/jxl-oxide-cli/src/bin/jxl-dec.rs +++ b/crates/jxl-oxide-cli/src/bin/jxl-dec.rs @@ -158,12 +158,11 @@ fn main() { let decode_start = std::time::Instant::now(); let mut keyframes = Vec::new(); - let mut renderer = image.renderer(); if args.output_format == OutputFormat::Npy { - renderer.set_render_spot_colour(false); + image.set_render_spot_colour(false); } loop { - let result = renderer.render_next_frame_cropped(crop).expect("rendering frames failed"); + let result = image.render_next_frame_cropped(crop).expect("rendering frames failed"); match result { jxl_oxide::RenderResult::Done(frame) => keyframes.push(frame), jxl_oxide::RenderResult::NeedMoreData => panic!("Unexpected end of file"), @@ -177,7 +176,7 @@ fn main() { if let Some(output) = &args.output { tracing::debug!(output_format = format_args!("{:?}", args.output_format)); - let pixel_format = renderer.pixel_format(); + let pixel_format = image.pixel_format(); let output = std::fs::File::create(output).expect("failed to open output file"); match args.output_format { OutputFormat::Png => { diff --git a/crates/jxl-oxide-cli/src/bin/jxl-info.rs b/crates/jxl-oxide-cli/src/bin/jxl-info.rs index 508eed59..1c9b3bbf 100644 --- a/crates/jxl-oxide-cli/src/bin/jxl-info.rs +++ b/crates/jxl-oxide-cli/src/bin/jxl-info.rs @@ -102,13 +102,12 @@ fn main() { } let animated = image_meta.animation.is_some(); - let mut renderer = image.renderer(); loop { - let result = renderer.load_next_frame().expect("rendering frames failed"); + let result = image.load_next_frame().expect("loading frames failed"); let frame_header = match result { jxl_oxide::LoadResult::Done(idx) => { println!("Frame #{idx}"); - renderer.frame_header(idx).unwrap() + image.frame_header(idx).unwrap() } jxl_oxide::LoadResult::NeedMoreData => panic!("Unexpected end of file"), jxl_oxide::LoadResult::NoMoreFrames => break, diff --git a/crates/jxl-oxide/src/bin/generate-fixture.rs b/crates/jxl-oxide/src/bin/generate-fixture.rs index a304eb1e..6467f293 100644 --- a/crates/jxl-oxide/src/bin/generate-fixture.rs +++ b/crates/jxl-oxide/src/bin/generate-fixture.rs @@ -14,9 +14,8 @@ fn main() { header[8..12].copy_from_slice(&channels.to_le_bytes()); stdout.write_all(&header).unwrap(); - let mut renderer = image.renderer(); loop { - let result = renderer.render_next_frame().unwrap(); + let result = image.render_next_frame().unwrap(); match result { jxl_oxide::RenderResult::Done(frame) => { stdout.write_all(&[0]).unwrap(); diff --git a/crates/jxl-oxide/src/lib.rs b/crates/jxl-oxide/src/lib.rs index 55e86923..04b17436 100644 --- a/crates/jxl-oxide/src/lib.rs +++ b/crates/jxl-oxide/src/lib.rs @@ -26,7 +26,8 @@ //! ``` //! //! `JxlImage` parses the image header and embedded ICC profile (if there's any). Use -//! [`JxlImage::renderer`] to start rendering the image. You might need to use +//! [`JxlImage::render_next_frame`], or [`JxlImage::load_next_frame`] followed by +//! [`JxlImage::render_frame`] to render the image. You might need to use //! [`JxlRenderer::rendered_icc`] to do color management correctly. //! //! ```no_run @@ -37,9 +38,8 @@ //! # fn wait_for_data() {} //! # fn main() -> jxl_oxide::Result<()> { //! # let mut image = JxlImage::open("input.jxl").unwrap(); -//! let mut renderer = image.renderer(); //! loop { -//! let result = renderer.render_next_frame()?; +//! let result = image.render_next_frame()?; //! match result { //! RenderResult::Done(render) => { //! present_image(render); @@ -84,6 +84,9 @@ pub struct JxlImage { bitstream: Bitstream>, image_header: Arc, embedded_icc: Option>, + ctx: RenderContext, + render_spot_colour: bool, + end_of_image: bool, } impl JxlImage { @@ -108,48 +111,16 @@ impl JxlImage { bitstream.skip_to_bookmark(bookmark)?; } + let render_spot_colour = !image_header.metadata.grayscale(); + Ok(Self { bitstream, - image_header, + image_header: image_header.clone(), embedded_icc, - }) - } - - /// Returns the image header. - #[inline] - pub fn image_header(&self) -> &ImageHeader { - &self.image_header - } - - /// Returns the embedded ICC profile. - #[inline] - pub fn embedded_icc(&self) -> Option<&[u8]> { - self.embedded_icc.as_deref() - } - - /// Returns the ICC profile that describes rendered images. - /// - /// - If the image is XYB encoded, and the ICC profile is embedded, then the profile describes - /// linear sRGB or linear grayscale colorspace. - /// - Else, if the ICC profile is embedded, then the embedded profile is returned. - /// - Else, the profile describes the color encoding signalled in the image header. - pub fn rendered_icc(&self) -> Vec { - create_rendered_icc(&self.image_header.metadata, self.embedded_icc.as_deref()) - } - - /// Starts rendering the image. - #[inline] - pub fn renderer(&mut self) -> JxlRenderer<'_, R> { - let ctx = RenderContext::new(self.image_header.clone()); - JxlRenderer { - bitstream: &mut self.bitstream, - image_header: self.image_header.clone(), - embedded_icc: self.embedded_icc.as_deref(), - ctx, - render_spot_colour: !self.image_header.metadata.grayscale(), - crop_region: None, + ctx: RenderContext::new(image_header), + render_spot_colour, end_of_image: false, - } + }) } } @@ -162,65 +133,17 @@ impl JxlImage { } } -/// Renderer for the initialized JPEG XL image. -#[derive(Debug)] -pub struct JxlRenderer<'img, R> { - bitstream: &'img mut Bitstream>, - image_header: Arc, - embedded_icc: Option<&'img [u8]>, - ctx: RenderContext, - render_spot_colour: bool, - crop_region: Option, - end_of_image: bool, -} - -impl<'img, R: Read> JxlRenderer<'img, R> { +impl JxlImage { /// Returns the image header. #[inline] pub fn image_header(&self) -> &ImageHeader { &self.image_header } - /// Sets the cropping region of the image. - #[inline] - pub fn set_crop_region(&mut self, crop_region: Option) -> &mut Self { - self.crop_region = crop_region; - self - } - - /// Returns the cropping region of the image. - #[inline] - pub fn crop_region(&self) -> Option { - self.crop_region - } - - #[inline] - #[allow(unused)] - fn crop_region_flattened(&self) -> Option<(u32, u32, u32, u32)> { - self.crop_region.map(|info| (info.left, info.top, info.width, info.height)) - } - - /// 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() { - tracing::warn!("Spot colour channels are not rendered on grayscale images"); - return self; - } - self.render_spot_colour = render_spot_colour; - self - } - - /// Returns whether the spot color channels will be rendered. - #[inline] - pub fn render_spot_colour(&self) -> bool { - self.render_spot_colour - } - /// Returns the embedded ICC profile. #[inline] - pub fn embedded_icc(&self) -> Option<&'img [u8]> { - self.embedded_icc + pub fn embedded_icc(&self) -> Option<&[u8]> { + self.embedded_icc.as_deref() } /// Returns the ICC profile that describes rendered images. @@ -230,7 +153,7 @@ impl<'img, R: Read> JxlRenderer<'img, R> { /// - Else, if the ICC profile is embedded, then the embedded profile is returned. /// - Else, the profile describes the color encoding signalled in the image header. pub fn rendered_icc(&self) -> Vec { - create_rendered_icc(&self.image_header.metadata, self.embedded_icc) + create_rendered_icc(&self.image_header.metadata, self.embedded_icc.as_deref()) } /// Returns the pixel format of the rendered image. @@ -257,6 +180,25 @@ impl<'img, R: Read> JxlRenderer<'img, R> { } } + /// 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() { + tracing::warn!("Spot colour channels are not rendered on grayscale images"); + return self; + } + self.render_spot_colour = render_spot_colour; + self + } + + /// Returns whether the spot color channels will be rendered. + #[inline] + pub fn render_spot_colour(&self) -> bool { + self.render_spot_colour + } +} + +impl JxlImage { /// Loads the next keyframe, and returns the result with the keyframe index. /// /// Unlike [`render_next_frame`][Self::render_next_frame], this method does not render the @@ -266,7 +208,7 @@ impl<'img, R: Read> JxlRenderer<'img, R> { return Ok(LoadResult::NoMoreFrames); } - self.ctx.load_until_keyframe(self.bitstream)?; + self.ctx.load_until_keyframe(&mut self.bitstream)?; let keyframe_index = self.ctx.loaded_keyframes() - 1; self.end_of_image = self.frame_header(keyframe_index).unwrap().is_last; @@ -285,7 +227,7 @@ impl<'img, R: Read> JxlRenderer<'img, R> { self.ctx.loaded_keyframes() } - /// Renders the given keyframe. + /// Renders the given keyframe with optional cropping region. pub fn render_frame_cropped(&mut self, keyframe_index: usize, image_region: Option) -> Result { let mut grids = self.ctx.render_keyframe(keyframe_index, image_region.map(From::from))?; let mut grids = grids.take_buffer(); @@ -330,7 +272,7 @@ impl<'img, R: Read> JxlRenderer<'img, R> { self.render_frame_cropped(keyframe_index, None) } - /// Loads and renders the next keyframe. + /// Loads and renders the next keyframe with optional cropping region. pub fn render_next_frame_cropped(&mut self, image_region: Option) -> Result { let load_result = self.load_next_frame()?; match load_result { diff --git a/crates/jxl-oxide/tests/conformance.rs b/crates/jxl-oxide/tests/conformance.rs index 7bae9437..5cf2d63a 100644 --- a/crates/jxl-oxide/tests/conformance.rs +++ b/crates/jxl-oxide/tests/conformance.rs @@ -75,14 +75,13 @@ fn run_test( ) { let debug = std::env::var("JXL_OXIDE_DEBUG").is_ok(); - let mut renderer = image.renderer(); - renderer.set_render_spot_colour(false); + image.set_render_spot_colour(false); let transform = target_icc.map(|target_icc| { - let source_profile = Profile::new_icc(&renderer.rendered_icc()).expect("failed to parse ICC profile"); + let source_profile = Profile::new_icc(&image.rendered_icc()).expect("failed to parse ICC profile"); let target_profile = Profile::new_icc(&target_icc).expect("failed to parse ICC profile"); - if renderer.image_header().metadata.grayscale() { + if image.image_header().metadata.grayscale() { LcmsTransform::Grayscale(Transform::new( &source_profile, lcms2::PixelFormat::GRAY_FLT, @@ -103,7 +102,7 @@ fn run_test( let mut num_keyframes = 0usize; loop { - let result = renderer.render_next_frame().expect("failed to render frame"); + let result = image.render_next_frame().expect("failed to render frame"); let render = match result { jxl_oxide::RenderResult::Done(render) => render, jxl_oxide::RenderResult::NeedMoreData => panic!("unexpected end of file"), diff --git a/crates/jxl-oxide/tests/decode.rs b/crates/jxl-oxide/tests/decode.rs index c8bac1d4..4b66210b 100644 --- a/crates/jxl-oxide/tests/decode.rs +++ b/crates/jxl-oxide/tests/decode.rs @@ -26,10 +26,9 @@ fn decode(data: &[u8], mut expected: R) { let fixture_header = FixtureHeader::from_bytes(header); let mut image = jxl_oxide::JxlImage::from_reader(std::io::Cursor::new(data)).unwrap(); - let mut renderer = image.renderer(); loop { - let result = renderer.render_next_frame().unwrap(); + let result = image.render_next_frame().unwrap(); match result { jxl_oxide::RenderResult::Done(frame) => { let mut marker = 0u8;