diff --git a/Cargo.lock b/Cargo.lock index 8cf4097..20e11eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,21 +55,6 @@ 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" @@ -460,13 +445,28 @@ dependencies = [ ] [[package]] -name = "brotli-decompressor" -version = "5.0.3" +name = "bon" +version = "3.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "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", ] [[package]] @@ -847,6 +847,40 @@ 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" @@ -1723,6 +1757,12 @@ 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" @@ -1766,7 +1806,7 @@ dependencies = [ "dirs", "iced", "image", - "jxl-oxide", + "jpegxl-rs", "rfd", ] @@ -1874,6 +1914,29 @@ 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" @@ -1885,186 +1948,6 @@ 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" @@ -3070,6 +2953,16 @@ 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" @@ -3746,6 +3639,12 @@ 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 487f499..c4f9f6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "AGPL-3.0-or-later" [dependencies] dirs = "6.0.0" rfd = "0.17.2" +jpegxl-rs = "0.14.0" [dependencies.iced] version = "0.14.0" @@ -15,8 +16,3 @@ features = [ "image" ] [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/flake.nix b/flake.nix index 72d435c..ddb2efc 100644 --- a/flake.nix +++ b/flake.nix @@ -39,6 +39,7 @@ pkg-config ]; buildInputs = with pkgs; [ + libjxl dav1d ]; diff --git a/src/main.rs b/src/main.rs index 18d35a0..3d8618d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -175,8 +175,6 @@ 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 1477a4e..961a27d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,50 +1,91 @@ -use std::path::{Path, PathBuf}; +use std::{ + ffi::OsStr, + fs, + io::Write, + 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 reader = match ImageReader::open(path) { - Ok(r) => r, - Err(e) => { - return Err(format!( - "failed to create reader for '{}': {e}", - path.display() - )); + 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 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 { @@ -73,8 +114,36 @@ pub fn save_image(image: Option<&RgbaImage>) -> Result<(), String> { return Err("no path to save provided".into()); }; - if let Err(e) = image.save(&path) { - return Err(format!("failed to save '{}': {e}", path.display())); + 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())); + }; + } }; Ok(())