Skip to content

Commit

Permalink
jxl-oxide: Merge JxlRenderer into JxlImage
Browse files Browse the repository at this point in the history
  • Loading branch information
tirr-c committed Aug 28, 2023
1 parent ca458b8 commit 168ef57
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 112 deletions.
7 changes: 3 additions & 4 deletions crates/jxl-oxide-cli/src/bin/jxl-dec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Check warning on line 162 in crates/jxl-oxide-cli/src/bin/jxl-dec.rs

View check run for this annotation

Codecov / codecov/patch

crates/jxl-oxide-cli/src/bin/jxl-dec.rs#L162

Added line #L162 was not covered by tests
}
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");

Check warning on line 165 in crates/jxl-oxide-cli/src/bin/jxl-dec.rs

View check run for this annotation

Codecov / codecov/patch

crates/jxl-oxide-cli/src/bin/jxl-dec.rs#L165

Added line #L165 was not covered by tests
match result {
jxl_oxide::RenderResult::Done(frame) => keyframes.push(frame),
jxl_oxide::RenderResult::NeedMoreData => panic!("Unexpected end of file"),
Expand All @@ -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();

Check warning on line 179 in crates/jxl-oxide-cli/src/bin/jxl-dec.rs

View check run for this annotation

Codecov / codecov/patch

crates/jxl-oxide-cli/src/bin/jxl-dec.rs#L179

Added line #L179 was not covered by tests
let output = std::fs::File::create(output).expect("failed to open output file");
match args.output_format {
OutputFormat::Png => {
Expand Down
5 changes: 2 additions & 3 deletions crates/jxl-oxide-cli/src/bin/jxl-info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Check warning on line 106 in crates/jxl-oxide-cli/src/bin/jxl-info.rs

View check run for this annotation

Codecov / codecov/patch

crates/jxl-oxide-cli/src/bin/jxl-info.rs#L106

Added line #L106 was not covered by tests
let frame_header = match result {
jxl_oxide::LoadResult::Done(idx) => {
println!("Frame #{idx}");
renderer.frame_header(idx).unwrap()
image.frame_header(idx).unwrap()

Check warning on line 110 in crates/jxl-oxide-cli/src/bin/jxl-info.rs

View check run for this annotation

Codecov / codecov/patch

crates/jxl-oxide-cli/src/bin/jxl-info.rs#L110

Added line #L110 was not covered by tests
}
jxl_oxide::LoadResult::NeedMoreData => panic!("Unexpected end of file"),
jxl_oxide::LoadResult::NoMoreFrames => break,
Expand Down
3 changes: 1 addition & 2 deletions crates/jxl-oxide/src/bin/generate-fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
134 changes: 38 additions & 96 deletions crates/jxl-oxide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -84,6 +84,9 @@ pub struct JxlImage<R> {
bitstream: Bitstream<ContainerDetectingReader<R>>,
image_header: Arc<ImageHeader>,
embedded_icc: Option<Vec<u8>>,
ctx: RenderContext,
render_spot_colour: bool,
end_of_image: bool,
}

impl<R: Read> JxlImage<R> {
Expand All @@ -108,48 +111,16 @@ impl<R: Read> JxlImage<R> {
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<u8> {
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,
}
})
}
}

Expand All @@ -162,65 +133,17 @@ impl JxlImage<File> {
}
}

/// Renderer for the initialized JPEG XL image.
#[derive(Debug)]
pub struct JxlRenderer<'img, R> {
bitstream: &'img mut Bitstream<ContainerDetectingReader<R>>,
image_header: Arc<ImageHeader>,
embedded_icc: Option<&'img [u8]>,
ctx: RenderContext,
render_spot_colour: bool,
crop_region: Option<CropInfo>,
end_of_image: bool,
}

impl<'img, R: Read> JxlRenderer<'img, R> {
impl<R> JxlImage<R> {
/// 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<CropInfo>) -> &mut Self {
self.crop_region = crop_region;
self
}

/// Returns the cropping region of the image.
#[inline]
pub fn crop_region(&self) -> Option<CropInfo> {
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()

Check warning on line 146 in crates/jxl-oxide/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/jxl-oxide/src/lib.rs#L145-L146

Added lines #L145 - L146 were not covered by tests
}

/// Returns the ICC profile that describes rendered images.
Expand All @@ -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<u8> {
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.
Expand All @@ -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;

Check warning on line 188 in crates/jxl-oxide/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/jxl-oxide/src/lib.rs#L188

Added line #L188 was not covered by tests
}
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<R: Read> JxlImage<R> {
/// 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
Expand All @@ -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;
Expand All @@ -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<CropInfo>) -> Result<Render> {
let mut grids = self.ctx.render_keyframe(keyframe_index, image_region.map(From::from))?;
let mut grids = grids.take_buffer();
Expand Down Expand Up @@ -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<CropInfo>) -> Result<RenderResult> {
let load_result = self.load_next_frame()?;
match load_result {
Expand Down
9 changes: 4 additions & 5 deletions crates/jxl-oxide/tests/conformance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,13 @@ fn run_test<R: std::io::Read>(
) {
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,
Expand All @@ -103,7 +102,7 @@ fn run_test<R: std::io::Read>(

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"),
Expand Down
3 changes: 1 addition & 2 deletions crates/jxl-oxide/tests/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ fn decode<R: Read>(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;
Expand Down

0 comments on commit 168ef57

Please sign in to comment.