From 53ed0f8a36be39aeeff8956bc45bfa7784aa068c Mon Sep 17 00:00:00 2001 From: electria Date: Wed, 29 Jul 2026 15:58:08 -0700 Subject: [PATCH 1/2] feat: switch to pure-rust jxl decoder this makes the logic drastically simpler; as it comes with image crate integration. however, images can no longer be saved as JXL with this version (as jxl-oxide is decoder-only) --- Cargo.lock | 301 ++++++++++++++++++++++++++++++++++----------------- Cargo.toml | 6 +- src/main.rs | 2 + src/utils.rs | 147 +++++++------------------ 4 files changed, 247 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20e11eb..8cf4097 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,21 @@ dependencies = [ "equator", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "android-activity" version = "0.6.1" @@ -445,28 +460,13 @@ dependencies = [ ] [[package]] -name = "bon" -version = "3.9.3" +name = "brotli-decompressor" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ - "bon-macros", - "rustversion", -] - -[[package]] -name = "bon-macros" -version = "3.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" -dependencies = [ - "darling", - "ident_case", - "prettyplease", - "proc-macro2", - "quote", - "rustversion", - "syn", + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] @@ -847,40 +847,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "dav1d" version = "0.11.1" @@ -1757,12 +1723,6 @@ dependencies = [ "winit", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "image" version = "0.25.10" @@ -1806,7 +1766,7 @@ dependencies = [ "dirs", "iced", "image", - "jpegxl-rs", + "jxl-oxide", "rfd", ] @@ -1914,29 +1874,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpegxl-rs" -version = "0.14.0+libjxl-0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7d16fe73fb6d2e16b986392b716409c6ae6fabd35769d66bea391b016cdbc9" -dependencies = [ - "bon", - "byteorder", - "half", - "image", - "jpegxl-sys", - "thiserror 2.0.18", -] - -[[package]] -name = "jpegxl-sys" -version = "0.12.1+libjxl-0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00cb4f7ffb45ee4327e9ec6ca1a732f3abef470ee008f7d4be3afb414075cd7d" -dependencies = [ - "pkg-config", -] - [[package]] name = "js-sys" version = "0.3.103" @@ -1948,6 +1885,186 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jxl-bitstream" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b480e752277e29eb4054f69546887a9b84656fe78c08f54ba5850ced98a378fe" +dependencies = [ + "tracing", +] + +[[package]] +name = "jxl-coding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd972bcd125e776f1eb241ac50e39f956095a1c2770c64736c968f8946bd9a3c" +dependencies = [ + "jxl-bitstream", + "tracing", +] + +[[package]] +name = "jxl-color" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f316b1358c1711755b3ee8e8cb5c4a1dad12e796233088a7a513440782de80b2" +dependencies = [ + "jxl-bitstream", + "jxl-coding", + "jxl-grid", + "jxl-image", + "jxl-oxide-common", + "jxl-threadpool", + "tracing", +] + +[[package]] +name = "jxl-frame" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d967c6fd669c7c01060b5022d8835fa82fd46b06ffc98b549f17600a097c2b3" +dependencies = [ + "jxl-bitstream", + "jxl-coding", + "jxl-grid", + "jxl-image", + "jxl-modular", + "jxl-oxide-common", + "jxl-threadpool", + "jxl-vardct", + "tracing", +] + +[[package]] +name = "jxl-grid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01671307879a033bfa52e6e8784b941aca770b3f3a7d33830b455b6844f793fb" +dependencies = [ + "tracing", +] + +[[package]] +name = "jxl-image" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f752d62577c702a94dbbce4045caf08cb58639e8a4d56464b40ecf33ffe565" +dependencies = [ + "jxl-bitstream", + "jxl-grid", + "jxl-oxide-common", + "tracing", +] + +[[package]] +name = "jxl-jbr" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35d032bcec660647828527ff42c6f5776d2fd44b8357f9f6d9ac6dc07218e46" +dependencies = [ + "brotli-decompressor", + "jxl-bitstream", + "jxl-frame", + "jxl-grid", + "jxl-image", + "jxl-modular", + "jxl-oxide-common", + "jxl-threadpool", + "jxl-vardct", + "tracing", +] + +[[package]] +name = "jxl-modular" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2f045b24c738dd91d482be385512b512721ae08a671bd4b27bf1c47f215235" +dependencies = [ + "jxl-bitstream", + "jxl-coding", + "jxl-grid", + "jxl-oxide-common", + "jxl-threadpool", + "tracing", +] + +[[package]] +name = "jxl-oxide" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36c662923f47586880211f3bc7c0d83fb3a9b410d278c7bde93450748abeef3" +dependencies = [ + "brotli-decompressor", + "bytemuck", + "image", + "jxl-bitstream", + "jxl-color", + "jxl-frame", + "jxl-grid", + "jxl-image", + "jxl-jbr", + "jxl-oxide-common", + "jxl-render", + "jxl-threadpool", + "tracing", +] + +[[package]] +name = "jxl-oxide-common" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62394c5021b3a9e7e0dbb2d639d555d019090c9946c39f6d3b09d390db4157b" +dependencies = [ + "jxl-bitstream", +] + +[[package]] +name = "jxl-render" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34386bfdb6a19b5a30cc9beb4d475d537422c31ae8c39bb69640fcce3fcaf19" +dependencies = [ + "bytemuck", + "jxl-bitstream", + "jxl-coding", + "jxl-color", + "jxl-frame", + "jxl-grid", + "jxl-image", + "jxl-modular", + "jxl-oxide-common", + "jxl-threadpool", + "jxl-vardct", + "tracing", +] + +[[package]] +name = "jxl-threadpool" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f15eb830aa77a7f21148d72e153562a26bfe570139bd4922eab1908dd499d3" +dependencies = [ + "rayon", + "rayon-core", + "tracing", +] + +[[package]] +name = "jxl-vardct" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce72a18c6d3a47172ab6c479be2bdb56f22066b5d7092663f03b4490820b4511" +dependencies = [ + "jxl-bitstream", + "jxl-coding", + "jxl-grid", + "jxl-modular", + "jxl-oxide-common", + "jxl-threadpool", + "tracing", +] + [[package]] name = "kamadak-exif" version = "0.6.1" @@ -2953,16 +3070,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -3639,12 +3746,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "svg_fmt" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 519eeb1..bbe6fcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,12 @@ license = "AGPL-3.0-or-later" iced = { version = "0.14.0", features = [ "image" ] } dirs = "6.0.0" rfd = "0.17.2" -jpegxl-rs = "0.14.0" [dependencies.image] version = "0.25.10" features = [ "avif-native" ] + +[dependencies.jxl-oxide] +version = "0.12.6" +# I really wish the image crate was named better +features = [ "image" ] diff --git a/src/main.rs b/src/main.rs index 3d8618d..18d35a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -175,6 +175,8 @@ impl State { } fn main() -> Result<(), iced::Error> { + jxl_oxide::integration::register_image_decoding_hook(); + iced::application(State::new, State::update, State::view) .subscription(State::subscription) .title(State::title) diff --git a/src/utils.rs b/src/utils.rs index 961a27d..1477a4e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,91 +1,50 @@ -use std::{ - ffi::OsStr, - fs, - io::Write, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; use image::{DynamicImage, ImageDecoder, ImageReader, RgbaImage}; -use jpegxl_rs::image::ToDynamic; use rfd::FileDialog; pub fn load_image(path: impl AsRef) -> Result { let path = path.as_ref(); - let decoded_image = match path.extension().map(OsStr::to_string_lossy).as_deref() { - Some("jxl") => { - let data = match std::fs::read(path) { - Ok(d) => d, - Err(e) => { - return Err(format!( - "failed to read data from '{}': {e}", - path.display() - )); - } - }; - - match jpegxl_rs::decoder_builder() - .build() - .unwrap() - .decode_to_image(&data) - { - Ok(Some(decoded_image)) => decoded_image, - Ok(None) => { - return Err(format!( - "failed to convert jxl '{}' to DynamicImage", - path.display() - )); - } - Err(e) => { - return Err(format!("failed to decode jxl '{}': {e}", path.display())); - } - } - } - - _ => { - let reader = match ImageReader::open(path) { - Ok(r) => r, - Err(e) => { - return Err(format!( - "failed to create reader for '{}': {e}", - path.display() - )); - } - }; - - let mut decoder = match reader.into_decoder() { - Ok(d) => d, - Err(e) => { - return Err(format!( - "failed to create decoder for '{}': {e}", - path.display() - )); - } - }; - - let oreintation = match decoder.orientation() { - Ok(o) => o, - Err(e) => { - return Err(format!( - "failed to get oreintation of '{}': {e}", - path.display() - )); - } - }; - - let mut decoded_image = match DynamicImage::from_decoder(decoder) { - Ok(i) => i, - Err(e) => { - return Err(format!("failed to decode image '{}': {e}", path.display())); - } - }; - - decoded_image.apply_orientation(oreintation); - - decoded_image + let reader = match ImageReader::open(path) { + Ok(r) => r, + Err(e) => { + return Err(format!( + "failed to create reader for '{}': {e}", + path.display() + )); } }; + let mut decoder = match reader.into_decoder() { + Ok(d) => d, + Err(e) => { + return Err(format!( + "failed to create decoder for '{}': {e}", + path.display() + )); + } + }; + + let oreintation = match decoder.orientation() { + Ok(o) => o, + Err(e) => { + return Err(format!( + "failed to get oreintation of '{}': {e}", + path.display() + )); + } + }; + + let mut decoded_image = match DynamicImage::from_decoder(decoder) { + Ok(i) => i, + Err(e) => { + return Err(format!("failed to decode image '{}': {e}", path.display())); + } + }; + + decoded_image.apply_orientation(oreintation); + Ok(decoded_image.into_rgba8()) } pub fn pick_image() -> Result { @@ -114,36 +73,8 @@ pub fn save_image(image: Option<&RgbaImage>) -> Result<(), String> { return Err("no path to save provided".into()); }; - let mut file = match fs::File::create(&path) { - Ok(f) => f, - Err(e) => return Err(format!("failed to create file '{}': {e}", path.display())), - }; - - match path.extension().map(OsStr::to_string_lossy).as_deref() { - Some("jxl") => { - let mut encoder = jpegxl_rs::encoder_builder() - .speed(jpegxl_rs::encode::EncoderSpeed::Glacier) - .lossless(true) - .build() - .unwrap(); - let rgb_image = DynamicImage::from(image.to_owned()).into_rgb8(); - let jxl = - match encoder.encode::(&rgb_image, rgb_image.width(), rgb_image.height()) { - Ok(j) => j, - Err(e) => { - return Err(format!("failed to encode jxl '{}': {e}", path.display())); - } - }; - - if let Err(e) = file.write(&jxl.data) { - return Err(format!("failed to write jxl to '{}': {e}", path.display())); - }; - } - _ => { - if let Err(e) = image.save(&path) { - return Err(format!("failed to save '{}': {e}", path.display())); - }; - } + if let Err(e) = image.save(&path) { + return Err(format!("failed to save '{}': {e}", path.display())); }; Ok(()) From 98c317adf513862b78d461bda59334a4b9cc81d7 Mon Sep 17 00:00:00 2001 From: electria Date: Wed, 29 Jul 2026 16:01:19 -0700 Subject: [PATCH 2/2] style: avoid toml curly braces else it causes messy diffs and is generally less writable and readable --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bbe6fcb..487f499 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,13 @@ edition = "2024" license = "AGPL-3.0-or-later" [dependencies] -iced = { version = "0.14.0", features = [ "image" ] } dirs = "6.0.0" rfd = "0.17.2" +[dependencies.iced] +version = "0.14.0" +features = [ "image" ] + [dependencies.image] version = "0.25.10" features = [ "avif-native" ]