Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
bba0ec5682 |
|||
|
8627ec026f |
|||
|
9b9c04c470 |
|||
|
30b0662731 |
|||
|
770bdc76d3 |
|||
|
02e470b317 |
|||
|
831def32a4 |
|||
|
53f78d9588 |
4 changed files with 232 additions and 46 deletions
111
Cargo.lock
generated
111
Cargo.lock
generated
|
|
@ -422,6 +422,31 @@ dependencies = [
|
||||||
"piper",
|
"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]]
|
[[package]]
|
||||||
name = "built"
|
name = "built"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
|
@ -454,6 +479,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder-lite"
|
name = "byteorder-lite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -778,6 +809,40 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
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]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
|
|
@ -1608,6 +1673,12 @@ dependencies = [
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.10"
|
version = "0.25.10"
|
||||||
|
|
@ -1649,6 +1720,7 @@ dependencies = [
|
||||||
"dirs",
|
"dirs",
|
||||||
"iced",
|
"iced",
|
||||||
"image",
|
"image",
|
||||||
|
"jpegxl-rs",
|
||||||
"rfd",
|
"rfd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1756,6 +1828,29 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.103"
|
version = "0.3.103"
|
||||||
|
|
@ -2758,6 +2853,16 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
|
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]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "3.5.0"
|
version = "3.5.0"
|
||||||
|
|
@ -3425,6 +3530,12 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svg_fmt"
|
name = "svg_fmt"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,4 @@ iced = { version = "0.14.0", features = [ "image" ] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
image = "0.25.10"
|
image = "0.25.10"
|
||||||
rfd = "0.17.2"
|
rfd = "0.17.2"
|
||||||
|
jpegxl-rs = "0.14.0"
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,10 @@
|
||||||
commonArgs = {
|
commonArgs = {
|
||||||
# all that's needed for artifacts and checks
|
# all that's needed for artifacts and checks
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
];
|
];
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
|
libjxl
|
||||||
];
|
];
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
@ -64,6 +66,7 @@
|
||||||
"image/gif"
|
"image/gif"
|
||||||
"image/webp"
|
"image/webp"
|
||||||
"image/avif"
|
"image/avif"
|
||||||
|
"image/jxl"
|
||||||
"image/tiff"
|
"image/tiff"
|
||||||
"image/bmp"
|
"image/bmp"
|
||||||
"image/vnd.microsoft.icon" # .ico
|
"image/vnd.microsoft.icon" # .ico
|
||||||
|
|
|
||||||
163
src/main.rs
163
src/main.rs
|
|
@ -1,11 +1,12 @@
|
||||||
use std::{env, ffi::OsStr, fs};
|
use std::{env, ffi::OsStr, fs, io::Write, path::Path};
|
||||||
|
|
||||||
use iced::{
|
use iced::{
|
||||||
Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, color, event,
|
Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, color, event,
|
||||||
keyboard::{self, Key, key},
|
keyboard::{self, Key, key},
|
||||||
theme, widget, window,
|
theme, widget, window,
|
||||||
};
|
};
|
||||||
use image::{EncodableLayout, ImageReader, RgbaImage, codecs::webp::WebPEncoder, imageops};
|
use image::{DynamicImage, EncodableLayout, ImageDecoder, ImageReader, RgbaImage, imageops};
|
||||||
|
use jpegxl_rs::image::ToDynamic;
|
||||||
use rfd::FileDialog;
|
use rfd::FileDialog;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -15,7 +16,7 @@ enum Message {
|
||||||
Event(iced::Event),
|
Event(iced::Event),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct State {
|
struct State {
|
||||||
image: Option<RgbaImage>,
|
image: Option<RgbaImage>,
|
||||||
image_display: Option<widget::image::Allocation>,
|
image_display: Option<widget::image::Allocation>,
|
||||||
|
|
@ -24,20 +25,9 @@ struct State {
|
||||||
}
|
}
|
||||||
impl State {
|
impl State {
|
||||||
fn new() -> (Self, Task<Message>) {
|
fn new() -> (Self, Task<Message>) {
|
||||||
let state = State {
|
let mut state = State::default();
|
||||||
image_display: None,
|
|
||||||
image: env::args().nth(1).and_then(|path| {
|
|
||||||
Some(
|
|
||||||
ImageReader::open(path)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap()
|
|
||||||
.into_rgba8(),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
|
|
||||||
error: None,
|
state.error = env::args().nth(1).and_then(|path| state.load_image(path));
|
||||||
};
|
|
||||||
let allocate_image = state.allocate_image();
|
let allocate_image = state.allocate_image();
|
||||||
|
|
||||||
(state, allocate_image)
|
(state, allocate_image)
|
||||||
|
|
@ -124,13 +114,92 @@ impl State {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_image(&mut self, path: impl AsRef<Path>) -> Option<String> {
|
||||||
|
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 Some(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 Some(format!(
|
||||||
|
"failed to convert jxl '{}' to DynamicImage",
|
||||||
|
path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(e) => return Some(format!("faled to decode '{}' {e}", path.display())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
let reader = match ImageReader::open(&path) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
return Some(format!(
|
||||||
|
"failed to create reader for '{}': {e}",
|
||||||
|
path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut decoder = match reader.into_decoder() {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
return Some(format!(
|
||||||
|
"failed to create decoder for '{}': {e}",
|
||||||
|
path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let oreintation = match decoder.orientation() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
return Some(format!(
|
||||||
|
"failed to get oreintation of '{}': {e}",
|
||||||
|
path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut decoded_image = match DynamicImage::from_decoder(decoder) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
return Some(format!("failed to decode image '{}': {e}", path.display()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
decoded_image.apply_orientation(oreintation);
|
||||||
|
|
||||||
|
decoded_image
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.image = Some(decoded_image.into_rgba8());
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
fn open_image(&mut self) -> Option<String> {
|
fn open_image(&mut self) -> Option<String> {
|
||||||
let Some(path) = FileDialog::new()
|
let Some(path) = FileDialog::new()
|
||||||
.add_filter(
|
.add_filter(
|
||||||
"image",
|
"image",
|
||||||
&[
|
&[
|
||||||
"png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "avif", "bmp", "exr", "ff", "gif",
|
"png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "avif", "jxl", "bmp", "exr", "ff",
|
||||||
"hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp",
|
"gif", "hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.pick_file()
|
.pick_file()
|
||||||
|
|
@ -138,19 +207,7 @@ impl State {
|
||||||
return Some("no path to open provided".into());
|
return Some("no path to open provided".into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let reader = match ImageReader::open(path) {
|
self.load_image(path)
|
||||||
Ok(r) => r,
|
|
||||||
Err(e) => return Some(format!("failed to create reader: {e}")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let decoded_image = match reader.decode() {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(e) => return Some(format!("failed to decode image: {e}")),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.image = Some(decoded_image.into_rgba8());
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_image(&self) -> Option<String> {
|
fn save_image(&self) -> Option<String> {
|
||||||
|
|
@ -162,24 +219,38 @@ impl State {
|
||||||
return Some("no path to save provided".into());
|
return Some("no path to save provided".into());
|
||||||
};
|
};
|
||||||
|
|
||||||
match fs::File::create(&path) {
|
let mut file = match fs::File::create(&path) {
|
||||||
Err(e) => return Some(format!("failed to create file: {e}")),
|
Ok(f) => f,
|
||||||
Ok(file) => {
|
Err(e) => return Some(format!("failed to create file '{}': {e}", path.display())),
|
||||||
let maybe_encoder = match path.extension().map(OsStr::to_string_lossy).as_deref() {
|
|
||||||
Some("webp") => Some(WebPEncoder::new_lossless(file)),
|
|
||||||
|
|
||||||
None | Some(_) => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = if let Some(encoder) = maybe_encoder {
|
match path.extension().map(OsStr::to_string_lossy).as_deref() {
|
||||||
image.write_with_encoder(encoder)
|
Some("jxl") => {
|
||||||
} else {
|
let mut encoder = jpegxl_rs::encoder_builder()
|
||||||
image.save(path)
|
.speed(jpegxl_rs::encode::EncoderSpeed::Glacier)
|
||||||
};
|
.lossless(true)
|
||||||
|
.build()
|
||||||
if let Err(e) = result {
|
.unwrap();
|
||||||
return Some(format!("failed to save image: {e}"));
|
let rgb_image = DynamicImage::from(image.to_owned()).into_rgb8();
|
||||||
|
let jxl = match encoder.encode::<u8, u8>(
|
||||||
|
&rgb_image,
|
||||||
|
rgb_image.width(),
|
||||||
|
rgb_image.height(),
|
||||||
|
) {
|
||||||
|
Ok(j) => j,
|
||||||
|
Err(e) => {
|
||||||
|
return Some(format!("failed to encode jxl '{}': {e}", path.display()));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = file.write(&jxl.data) {
|
||||||
|
return Some(format!("failed to write jxl to '{}': {e}", path.display()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Err(e) = image.save(&path) {
|
||||||
|
return Some(format!("failed to save '{}': {e}", path.display()));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue