Krolyxon
Commit 4654f72

Branches

Tags

Refs →

remagesrcdecodejxl.rs

67 lines · 2.3 KB · Rust

1//! JPEG XL through `jxl-oxide`, rendered to sRGB (moxcms handles ICC-encoded files), with
2//! animation.
3
4use std::path::Path;
5use std::time::Duration;
6
7use anyhow::anyhow;
8use jxl_oxide::{EnumColourEncoding, JxlImage, Moxcms, RenderingIntent};
9
10use super::color;
11use super::{DecodedImage, Frame, MAX_FRAMES_BYTES, Options};
12
13pub fn decode(path: &Path, opts: &Options) -> anyhow::Result<DecodedImage> {
14 let mut image = JxlImage::builder().open(path).map_err(|e| anyhow!("{e}"))?;
15 if opts.color {
16 image.set_cms(Moxcms);
17 image.request_color_encoding(EnumColourEncoding::srgb(RenderingIntent::Relative));
18 }
19 // Seconds per tick of a frame's duration.
20 let tick = image
21 .image_header()
22 .metadata
23 .animation
24 .as_ref()
25 .filter(|a| a.tps_numerator > 0)
26 .map(|a| a.tps_denominator as f64 / a.tps_numerator as f64);
27 let count = image.num_loaded_keyframes();
28 let mut frames = Vec::new();
29 let mut size = [0, 0];
30 let mut bytes = 0;
31 for i in 0..count {
32 let render = image.render_frame(i).map_err(|e| anyhow!("{e}"))?;
33 let mut stream = render.stream();
34 let channels = stream.channels() as usize;
35 size = [stream.width(), stream.height()];
36 let mut buf = vec![0u8; size[0] as usize * size[1] as usize * channels];
37 stream.write_to_buffer(&mut buf);
38 // Extra channels (CMYK black, spot colours) beyond RGBA are dropped.
39 let pixels = if channels > 4 {
40 buf.chunks_exact(channels)
41 .flat_map(|p| [p[0], p[1], p[2], p[3]])
42 .collect()
43 } else {
44 color::expand(&buf, channels)
45 };
46 let delay = tick.map_or(Duration::ZERO, |t| {
47 Duration::from_secs_f64(render.duration() as f64 * t)
48 });
49 bytes += pixels.len();
50 frames.push(Frame { pixels, delay });
51 if bytes > MAX_FRAMES_BYTES {
52 log::warn!("animation truncated at {} frames", frames.len());
53 break;
54 }
55 }
56 if frames.is_empty() {
57 return Err(anyhow!("no frames"));
58 }
59 Ok(DecodedImage {
60 size,
61 animated: tick.is_some() && frames.len() > 1,
62 frames,
63 format: "JXL",
64 source_size: size,
65 refine: None,
66 })
67}