jxl_oxide_cli/commands/decode.rs
1use std::path::PathBuf;
2
3use clap::Parser;
4use jxl_oxide::{CropInfo, EnumColourEncoding};
5
6#[derive(Debug, Parser)]
7#[non_exhaustive]
8pub struct DecodeArgs {
9 /// Output file
10 #[arg(short, long)]
11 pub output: Option<PathBuf>,
12 /// Output ICC file
13 #[arg(long)]
14 pub icc_output: Option<PathBuf>,
15 /// Input file
16 pub input: PathBuf,
17 /// (unstable) Region to render, in format of 'width height left top'
18 #[arg(long, value_parser = parse_crop_info)]
19 pub crop: Option<CropInfo>,
20 /// (unstable) Approximate memory limit, in bytes
21 #[arg(long, default_value_t = 0)]
22 pub approx_memory_limit: usize,
23 /// Format to output
24 #[arg(value_enum, short = 'f', long)]
25 pub output_format: Option<OutputFormat>,
26 #[allow(clippy::doc_overindented_list_items, reason = "verbatim doc comment")]
27 /// (unstable) Target colorspace specification
28 ///
29 /// Specification string consists of (optional) preset and a sequence of parameters delimited by commas.
30 ///
31 /// Parameters have a syntax of `name=value`. Possible parameter names:
32 /// - type: Color space type. Possible values:
33 /// - rgb
34 /// - gray
35 /// - xyb
36 /// - gamut: Color gamut. Invalid if type is Gray or XYB. Possible values:
37 /// - srgb
38 /// - p3
39 /// - bt2100
40 /// - wp: White point. Invalid if type is XYB. Possible values:
41 /// - d65
42 /// - d50
43 /// - dci
44 /// - e
45 /// - tf: Transfer function. Invalid if type is XYB. Possible values:
46 /// - srgb
47 /// - bt709
48 /// - dci
49 /// - pq
50 /// - hlg
51 /// - linear
52 /// - (gamma value)
53 /// - intent: Rendering intent. Possible values:
54 /// - relative
55 /// - perceptual
56 /// - saturation
57 /// - absolute
58 ///
59 /// Presets define a set of parameters commonly used together. Possible presets:
60 /// - srgb: type=rgb,gamut=srgb,wp=d65,tf=srgb,intent=relative
61 /// - display_p3: type=rgb,gamut=p3,wp=d65,tf=srgb,intent=relative
62 /// - rec2020: type=rgb,gamut=bt2100,wp=d65,tf=bt709,intent=relative
63 /// - rec2100: type=rgb,gamut=bt2100,wp=d65,intent=relative
64 /// Transfer function is not set for this preset; one should be provided, e.g. rec2100,tf=pq
65 #[arg(long, value_parser = super::parse_color_encoding, verbatim_doc_comment)]
66 pub target_colorspace: Option<EnumColourEncoding>,
67 /// (unstable) Path to target ICC profile
68 #[arg(long)]
69 pub target_icc: Option<PathBuf>,
70 /// Number of parallelism to use
71 #[cfg_attr(feature = "rayon", arg(short = 'j', long))]
72 #[cfg_attr(not(feature = "rayon"), arg(skip))]
73 pub num_threads: Option<usize>,
74 /// Number of repeated decoding, used for benchmarking
75 #[arg(long, value_parser = clap::value_parser!(u32).range(1..))]
76 pub num_reps: Option<u32>,
77 /// External color management system to use for ICC profiles.
78 ///
79 /// External CMS will handle ICC profiles jxl-oxide cannot handle.
80 #[arg(long)]
81 pub cms: Option<Cms>,
82 /// (unstable) Force 32-bit Modular buffers when decoding.
83 #[arg(long)]
84 pub force_wide_buffers: bool,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
88pub enum OutputFormat {
89 /// PNG, respects bit depth information.
90 Png,
91 /// PNG, always 8-bit.
92 Png8,
93 /// PNG, always 16-bit.
94 Png16,
95 /// JPEG bitstream reconstruction.
96 #[value(name = "jpeg", alias("jpg"))]
97 JpegReconstruct,
98 /// Numpy, used for conformance test.
99 Npy,
100}
101
102#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
103pub enum Cms {
104 /// Little CMS 2.
105 #[default]
106 Lcms2,
107 /// moxcms crate.
108 Moxcms,
109}
110
111fn parse_crop_info(s: &str) -> Result<CropInfo, std::num::ParseIntError> {
112 let s = s.trim();
113 let mut it = s.split_whitespace().map(|s| s.parse::<u32>());
114 let Some(w) = it.next().transpose()? else {
115 return Ok(CropInfo {
116 width: 0,
117 height: 0,
118 left: 0,
119 top: 0,
120 });
121 };
122 let Some(h) = it.next().transpose()? else {
123 return Ok(CropInfo {
124 width: w,
125 height: w,
126 left: 0,
127 top: 0,
128 });
129 };
130 let Some(x) = it.next().transpose()? else {
131 return Ok(CropInfo {
132 width: w,
133 height: h,
134 left: 0,
135 top: 0,
136 });
137 };
138 let Some(y) = it.next().transpose()? else {
139 return Ok(CropInfo {
140 width: w,
141 height: w,
142 left: h,
143 top: x,
144 });
145 };
146 Ok(CropInfo {
147 width: w,
148 height: h,
149 left: x,
150 top: y,
151 })
152}