feat: start work on jxl support

This commit is contained in:
electria 2026-07-02 15:57:29 -07:00
commit 770bdc76d3
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
4 changed files with 135 additions and 22 deletions

111
Cargo.lock generated
View file

@ -422,6 +422,31 @@ dependencies = [
"piper",
]
[[package]]
name = "bon"
version = "3.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561"
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",
]
[[package]]
name = "built"
version = "0.8.1"
@ -454,6 +479,12 @@ dependencies = [
"syn",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
@ -778,6 +809,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 = "dirs"
version = "6.0.0"
@ -1608,6 +1673,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"
@ -1649,6 +1720,7 @@ dependencies = [
"dirs",
"iced",
"image",
"jpegxl-rs",
"rfd",
]
@ -1756,6 +1828,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"
@ -2758,6 +2853,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"
@ -3425,6 +3530,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"

View file

@ -8,3 +8,4 @@ iced = { version = "0.14.0", features = [ "image" ] }
dirs = "6.0.0"
image = "0.25.10"
rfd = "0.17.2"
jpegxl-rs = "0.14.0"

View file

@ -36,8 +36,10 @@
commonArgs = {
# all that's needed for artifacts and checks
nativeBuildInputs = with pkgs; [
pkg-config
];
buildInputs = with pkgs; [
libjxl
];
src = ./.;
@ -64,6 +66,7 @@
"image/gif"
"image/webp"
"image/avif"
"image/jxl"
"image/tiff"
"image/bmp"
"image/vnd.microsoft.icon" # .ico

View file

@ -1,4 +1,4 @@
use std::{env, ffi::OsStr, fs, path::Path};
use std::{env, ffi::OsStr, fs, io::Read, path::Path};
use iced::{
Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, color, event,
@ -6,9 +6,10 @@ use iced::{
theme, widget, window,
};
use image::{
DynamicImage, EncodableLayout, ImageDecoder, ImageReader, RgbaImage, codecs::webp::WebPEncoder,
imageops,
DynamicImage, EncodableLayout, ImageDecoder, ImageReader, RgbImage, RgbaImage,
codecs::webp::WebPEncoder, imageops,
};
use jpegxl_rs::decode::JxlDecoder;
use rfd::FileDialog;
#[derive(Clone, Debug)]
@ -168,8 +169,8 @@ impl State {
.add_filter(
"image",
&[
"png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "avif", "bmp", "exr", "ff", "gif",
"hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp",
"png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "avif", "jxl", "bmp", "exr", "ff",
"gif", "hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp",
],
)
.pick_file()
@ -189,24 +190,21 @@ impl State {
return Some("no path to save provided".into());
};
match fs::File::create(&path) {
Err(e) => return Some(format!("failed to create file: {e}")),
Ok(file) => {
let maybe_encoder = match path.extension().map(OsStr::to_string_lossy).as_deref() {
Some("webp") => Some(WebPEncoder::new_lossless(file)),
let mut file = match fs::File::create(&path) {
Ok(f) => f,
Err(e) => return Some(format!("failed to create file '{}': {e}", path.display())),
};
None | Some(_) => None,
};
let result = if let Some(encoder) = maybe_encoder {
image.write_with_encoder(encoder)
} else {
image.save(path)
};
if let Err(e) = result {
return Some(format!("failed to save image: {e}"));
}
match path.extension().map(OsStr::to_string_lossy).as_deref() {
Some("jxl") => {
jpegxl_rs::encoder_builder().build().unwrap().encode(
RgbImage::from(DynamicImage::from(*image)).as_bytes(),
image.width(),
image.height(),
);
}
None | Some(_) => {
image.save(path);
}
};