From e8761f8fcf673d2335223b94e8b606db6e0dee92 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 11:01:51 -0700 Subject: [PATCH 01/22] try to give ownership of the image to the Handle this doesn't work without copying, because RgbaImage is always an owned type as such, it is likely not worth it --- src/main.rs | 13 +++---------- src/utils.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3e65e8d..9ac79ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,6 @@ enum Message { #[derive(Clone, Debug, Default)] struct State { - image: Option, image_display: Option, image_filter: widget::image::FilterMethod, @@ -168,8 +167,7 @@ impl State { if image.dimensions() < (100, 100) { self.image_filter = widget::image::FilterMethod::Nearest; } - self.image = Some(image); - self.allocate_image() + self.allocate_image(image) }) } fn pick_and_load_image(&mut self) -> Result, String> { @@ -180,16 +178,11 @@ impl State { self.error = utils::save_image(self.image.as_ref()).err(); } - fn allocate_image(&self) -> Task { - let Some(image) = self.image.as_ref() else { - eprintln!("no image to allocate"); - return Task::none(); - }; - + fn allocate_image(&self, image: RgbaImage) -> Task { widget::image::allocate(widget::image::Handle::from_rgba( image.width(), image.height(), - unsafe { std::mem::transmute::<&[u8], &'static [u8]>(image.as_bytes()) }, + image.into_vec(), )) .map(Message::ImageDisplayReady) } diff --git a/src/utils.rs b/src/utils.rs index dafd1f2..c59c7e7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,18 @@ use iced::widget; use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; use rfd::FileDialog; +pub fn image_from_handle(handle: widget::image::Handle) -> RgbaImage { + match handle { + widget::image::Handle::Rgba { + id, + width, + height, + pixels, + } => RgbaImage::from_raw(width, height, pixels.as_ref()).unwrap(), + _ => panic!("handle should always hold rgba data"), + } +} + pub fn load_image(path: impl AsRef) -> Result { _load_image(path).map_err(|e| e.to_string()) } From 26c17d5cf9bf6ba47e03b212e3cf1db2233d34c0 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 11:14:05 -0700 Subject: [PATCH 02/22] docs: add safety note --- src/main.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main.rs b/src/main.rs index 3e65e8d..5ad2bf3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -189,6 +189,18 @@ impl State { widget::image::allocate(widget::image::Handle::from_rgba( image.width(), image.height(), + // SAFETY: this is a sort of race condition; + // will cause panics with large enough images, + // when they are edited consecutively. + // + // this can be reproduced by holding down 'i' with a 10k by 10k px image, + // which will invert the colors rapidly and eventually crash the app. + // + // however, that is not such a problem in this case, + // and I don't know a different way of doing this that doesn't copy + // (performance loss, which is can be pretty big) + // or likely cause flickering instead in such cases + // (like using Handle over Allocation to immediately drop the last one) unsafe { std::mem::transmute::<&[u8], &'static [u8]>(image.as_bytes()) }, )) .map(Message::ImageDisplayReady) From 754dd6126d0937819482a89e5ec2185d36ff239d Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 11:21:37 -0700 Subject: [PATCH 03/22] fix: wrong dbus output (?) --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 40636f4..d7fa343 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ dlDeps = with pkgs; [ # libdbus, for rfd - dbus + dbus.lib # needed for both x11 and wayland libxkbcommon From 94c905db386991fd1de615d43cc4da1ae78df86e Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 14:19:53 -0700 Subject: [PATCH 04/22] build: seperate linux-specific deps --- flake.nix | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/flake.nix b/flake.nix index d7fa343..5a57299 100644 --- a/flake.nix +++ b/flake.nix @@ -19,22 +19,25 @@ cargoToml = fromTOML (builtins.readFile ./Cargo.toml); name = cargoToml.package.name; - dlDeps = with pkgs; [ - # libdbus, for rfd - dbus.lib + dlDeps = + with pkgs; + [ + # needed for both x11 and wayland + libxkbcommon + libGL - # needed for both x11 and wayland - libxkbcommon - libGL - vulkan-loader + libx11 + libxcursor + libxi + libxcb + ] + ++ lib.optionals stdenv.hostPlatform.isLinux [ + # libdbus, for rfd + dbus.lib - wayland - - libx11 - libxcursor - libxi - libxcb - ]; + vulkan-loader + wayland + ]; commonArgs = { # all that's needed for artifacts and checks From 9d4e24e8099a2cffa6df0c1c87c724c629f65838 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 14:36:36 -0700 Subject: [PATCH 05/22] refactor: apply clippy lints cargo clippy --no-deps -- --deny clippy::nursery --deny clippy::pedantic (does not quite pass due to unused `&self` in State::subscription) I am considering ignoring events when the file picker is opened, but that isn't a very nice solution (should just make it async atp) --- src/main.rs | 95 ++++++++++++++++++++++++++-------------------------- src/utils.rs | 8 ++--- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5ad2bf3..c951b31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ struct State { } impl State { fn new() -> (Self, Task) { - let mut state = State::default(); + let mut state = Self::default(); if let Some(path) = env::args().nth(1) { match state.load_image(path) { @@ -43,22 +43,25 @@ impl State { fn view(&self) -> Element<'_, Message, Theme, Renderer> { let mut main = Vec::new(); - main.push(if let Some(allocation) = self.image_display.as_ref() { - widget::image::viewer(allocation.handle().clone()) - .filter_method(self.image_filter) - .max_scale(50.) - .min_scale(1.) - .width(Length::Fill) - .height(Length::Fill) - .into() - } else { - widget::container(widget::text(include_str!("usage.txt"))) - .height(Length::Fill) - .width(Length::Fill) - .align_x(Alignment::Center) - .align_y(Alignment::Center) - .into() - }); + main.push(self.image_display.as_ref().map_or_else( + || { + widget::container(widget::text(include_str!("usage.txt"))) + .height(Length::Fill) + .width(Length::Fill) + .align_x(Alignment::Center) + .align_y(Alignment::Center) + .into() + }, + |allocation| { + widget::image::viewer(allocation.handle().clone()) + .filter_method(self.image_filter) + .max_scale(50.) + .min_scale(1.) + .width(Length::Fill) + .height(Length::Fill) + .into() + }, + )); if let Some(error) = self.error.as_ref() { main.push(widget::text(error).style(widget::text::danger).into()); @@ -84,9 +87,8 @@ impl State { Key::Named(key::Named::Tab) => { if modifiers.shift() { return widget::operation::focus_previous(); - } else { - return widget::operation::focus_next(); } + return widget::operation::focus_next(); } Key::Character("o") => match self.pick_and_load_image() { @@ -97,26 +99,22 @@ impl State { Err(e) => self.error = Some(e), }, - Key::Character("r") => { - match self.image.as_ref() { - None => self.error = Some("no image to rotate".into()), - Some(image) => { - self.error = None; - self.image = Some(imageops::rotate90(image)); - return self.allocate_image(); - } - }; - } - Key::Character("i") => { - match self.image.as_mut() { - None => self.error = Some("no image to invert".into()), - Some(image) => { - self.error = None; - imageops::invert(image); - return self.allocate_image(); - } - }; - } + Key::Character("r") => match self.image.as_ref() { + None => self.error = Some("no image to rotate".into()), + Some(image) => { + self.error = None; + self.image = Some(imageops::rotate90(image)); + return self.allocate_image(); + } + }, + Key::Character("i") => match self.image.as_mut() { + None => self.error = Some("no image to invert".into()), + Some(image) => { + self.error = None; + imageops::invert(image); + return self.allocate_image(); + } + }, Key::Character("f") => { self.image_filter = match self.image_filter { widget::image::FilterMethod::Linear => widget::image::FilterMethod::Nearest, @@ -160,8 +158,7 @@ impl State { "{} {}x{}", path.as_ref() .file_name() - .map(OsStr::to_string_lossy) - .unwrap_or(Cow::Borrowed("[no file]")), + .map_or(Cow::Borrowed("[no file]"), OsStr::to_string_lossy), image.width(), image.height(), )); @@ -210,13 +207,15 @@ impl State { event::listen().map(Message::Event) } fn title(&self) -> String { - match self.info.as_ref() { - Some(info) => format!( - "imagey {info} {}", - utils::string_from_filter_type(self.image_filter), - ), - None => "imagey".into(), - } + self.info.as_ref().map_or_else( + || "imagey".into(), + |info| { + format!( + "imagey {info} {}", + utils::string_from_filter_type(self.image_filter), + ) + }, + ) } } diff --git a/src/utils.rs b/src/utils.rs index dafd1f2..cc523c4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,9 +5,9 @@ use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; use rfd::FileDialog; pub fn load_image(path: impl AsRef) -> Result { - _load_image(path).map_err(|e| e.to_string()) + load_image_impl(path).map_err(|e| e.to_string()) } -fn _load_image(path: impl AsRef) -> ImageResult { +fn load_image_impl(path: impl AsRef) -> ImageResult { let mut decoder = ImageReader::open(path)? .with_guessed_format()? .into_decoder()?; @@ -48,12 +48,12 @@ pub fn save_image(image: Option<&RgbaImage>) -> Result<(), String> { if let Err(e) = image.save(&path) { return Err(e.to_string()); - }; + } Ok(()) } -pub fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static str { +pub const fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static str { match f { widget::image::FilterMethod::Linear => "bilinear", widget::image::FilterMethod::Nearest => "nearest neighbor", From 18de039e77575f2b0233bb9957f940a9a8dbd5a5 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 15:11:41 -0700 Subject: [PATCH 06/22] feat: make pickers async --- src/main.rs | 49 +++++++++++++++++++++++++++++++++---------------- src/utils.rs | 23 ++++++++--------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index c951b31..ba75666 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,9 @@ -use std::{borrow::Cow, env, ffi::OsStr, path::Path}; +use std::{ + borrow::Cow, + env, + ffi::OsStr, + path::{Path, PathBuf}, +}; use iced::{ Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event, @@ -12,7 +17,9 @@ mod utils; #[derive(Clone, Debug)] enum Message { + ImagePicked(Result), ImageDisplayReady(Result), + SavePathPicked(Result), Event(iced::Event), } @@ -74,9 +81,26 @@ impl State { } fn update(&mut self, message: Message) -> Task { match message { + Message::ImagePicked(result) => match result.and_then(|path| self.load_image(path)) { + Err(e) => self.error = Some(e), + Ok(task) => { + self.error = None; + return task; + } + }, Message::ImageDisplayReady(result) => { self.image_display = Some(result.unwrap()); } + Message::SavePathPicked(result) => { + self.error = result + .and_then(|path| { + self.image.as_ref().map_or_else( + || Err("no image to save".into()), + |image| image.save(path).map_err(|e| e.to_string()), + ) + }) + .err(); + } Message::Event(iced::Event::Keyboard(keyboard::Event::KeyPressed { key, @@ -91,13 +115,9 @@ impl State { return widget::operation::focus_next(); } - Key::Character("o") => match self.pick_and_load_image() { - Ok(task) => { - self.error = None; - return task; - } - Err(e) => self.error = Some(e), - }, + Key::Character("o") => { + return Task::perform(utils::pick_image(), Message::ImagePicked); + } Key::Character("r") => match self.image.as_ref() { None => self.error = Some("no image to rotate".into()), @@ -126,7 +146,11 @@ impl State { } Key::Character("s") => { - self.save_image(); + if self.image.is_some() { + self.error = None; + return Task::perform(utils::pick_save_path(), Message::SavePathPicked); + } + self.error = Some("no image to save".into()); } Key::Character("q") => return window::latest().and_then(window::close), @@ -169,13 +193,6 @@ impl State { self.allocate_image() }) } - fn pick_and_load_image(&mut self) -> Result, String> { - utils::pick_image().and_then(|path| self.load_image(path)) - } - - fn save_image(&mut self) { - self.error = utils::save_image(self.image.as_ref()).err(); - } fn allocate_image(&self) -> Task { let Some(image) = self.image.as_ref() else { diff --git a/src/utils.rs b/src/utils.rs index cc523c4..fc1841d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use iced::widget; use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; -use rfd::FileDialog; +use rfd::AsyncFileDialog; pub fn load_image(path: impl AsRef) -> Result { load_image_impl(path).map_err(|e| e.to_string()) @@ -20,8 +20,8 @@ fn load_image_impl(path: impl AsRef) -> ImageResult { Ok(decoded_image.into_rgba8()) } -pub fn pick_image() -> Result { - let Some(path) = FileDialog::new() +pub async fn pick_image() -> Result { + let Some(filehandle) = AsyncFileDialog::new() .add_filter( "image", &[ @@ -30,27 +30,20 @@ pub fn pick_image() -> Result { ], ) .pick_file() + .await else { return Err("no path to open provided".into()); }; - Ok(path) + Ok(filehandle.path().to_owned()) } -pub fn save_image(image: Option<&RgbaImage>) -> Result<(), String> { - let Some(image) = image else { - return Err("no image to save".into()); - }; - - let Some(path) = FileDialog::new().save_file() else { +pub async fn pick_save_path() -> Result { + let Some(filehandle) = AsyncFileDialog::new().save_file().await else { return Err("no path to save provided".into()); }; - if let Err(e) = image.save(&path) { - return Err(e.to_string()); - } - - Ok(()) + Ok(filehandle.path().to_owned()) } pub const fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static str { From 6fa0e29eae2fe13e7c140447b1def1ee87828f8c Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 17:23:29 -0700 Subject: [PATCH 07/22] feat: display file size --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/main.rs | 9 ++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2664b09..4c05ae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1905,6 +1905,7 @@ dependencies = [ "image", "jxl-oxide", "rfd", + "size", "zip", ] @@ -3787,6 +3788,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "size" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" + [[package]] name = "skrifa" version = "0.37.0" diff --git a/Cargo.toml b/Cargo.toml index 9015f09..b830559 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" +size = "0.5.0" zip = "8.6.0" [dependencies.iced] diff --git a/src/main.rs b/src/main.rs index ba75666..2acbbfa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::{ borrow::Cow, env, ffi::OsStr, + os::unix::fs::MetadataExt, path::{Path, PathBuf}, }; @@ -11,6 +12,7 @@ use iced::{ theme, widget, window, }; use image::{EncodableLayout, RgbaImage, imageops}; +use size::Size; mod kra; mod utils; @@ -179,10 +181,15 @@ impl State { fn load_image(&mut self, path: impl AsRef) -> Result, String> { utils::load_image(&path).map(|image| { self.info = Some(format!( - "{} {}x{}", + "{} {} {}x{}", path.as_ref() .file_name() .map_or(Cow::Borrowed("[no file]"), OsStr::to_string_lossy), + path.as_ref() + .metadata() + .map_or(Cow::Borrowed("[no size]"), |m| Cow::Owned( + Size::from_bytes(m.size()).to_string() + )), image.width(), image.height(), )); From 808a8bf31d789f5af8ca505c717f5bc648bc466d Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 17:31:34 -0700 Subject: [PATCH 08/22] feat: put the bars together this made sense now that I made error messages shorter, and figured out how to properly align things in a row. looks good! might even consider keeping the infobar on by default --- src/main.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2acbbfa..b89efc0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,9 +50,7 @@ impl State { (state, Task::none()) } fn view(&self) -> Element<'_, Message, Theme, Renderer> { - let mut main = Vec::new(); - - main.push(self.image_display.as_ref().map_or_else( + let main = self.image_display.as_ref().map_or_else( || { widget::container(widget::text(include_str!("usage.txt"))) .height(Length::Fill) @@ -70,16 +68,27 @@ impl State { .height(Length::Fill) .into() }, - )); + ); + let mut bar = Vec::new(); if let Some(error) = self.error.as_ref() { - main.push(widget::text(error).style(widget::text::danger).into()); + bar.push( + widget::container(widget::text(error).style(widget::text::danger)) + .align_x(Alignment::Start) + .width(Length::Fill) + .into(), + ); } if self.infobar_shown && self.info.is_some() { - main.push(widget::text(self.title()).into()); + bar.push( + widget::container(widget::text(self.title())) + .align_x(Alignment::End) + .width(Length::Fill) + .into(), + ); } - widget::column(main).into() + widget::column([main, widget::row(bar).into()]).into() } fn update(&mut self, message: Message) -> Task { match message { From 1f909904c2479543dbb72ae70a714c2f36aac6c7 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 17:47:12 -0700 Subject: [PATCH 09/22] fix: print error on failed to read metadata this should be very rare, so no need to display it or abort --- src/main.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index b89efc0..46cc96a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,11 +194,13 @@ impl State { path.as_ref() .file_name() .map_or(Cow::Borrowed("[no file]"), OsStr::to_string_lossy), - path.as_ref() - .metadata() - .map_or(Cow::Borrowed("[no size]"), |m| Cow::Owned( - Size::from_bytes(m.size()).to_string() - )), + path.as_ref().metadata().map_or_else( + |e| { + eprintln!("failed to read metadata: {e}"); + Cow::Borrowed("[no size]") + }, + |m| Cow::Owned(Size::from_bytes(m.size()).to_string()) + ), image.width(), image.height(), )); From cb5e1d837a65684b437087a0319a41d67f7d024b Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 19:36:58 -0700 Subject: [PATCH 10/22] feat: image copy and paste --- Cargo.lock | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +++ src/main.rs | 51 ++++++++++++++++++++++++++++++++- src/usage.txt | 3 ++ src/utils.rs | 21 +++++++++++++- 5 files changed, 155 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c05ae7..d8f99d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,27 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -1226,6 +1247,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -1900,6 +1927,7 @@ dependencies = [ name = "imagey" version = "0.1.0" dependencies = [ + "arboard", "dirs", "iced", "image", @@ -3065,6 +3093,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -3131,6 +3169,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -4253,6 +4302,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +dependencies = [ + "memchr", + "nom", + "petgraph", +] + [[package]] name = "ttf-parser" version = "0.25.1" @@ -5116,6 +5176,24 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wl-clipboard-rs" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix 1.1.4", + "thiserror 2.0.18", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "x11-dl" version = "2.21.0" diff --git a/Cargo.toml b/Cargo.toml index b830559..382a20f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,7 @@ features = [ "avif-native" ] version = "0.12.6" # I really wish the image crate was named better features = [ "image" ] + +[dependencies.arboard] +version = "3.6.1" +features = [ "wayland-data-control" ] diff --git a/src/main.rs b/src/main.rs index 46cc96a..2c16d88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use std::{ path::{Path, PathBuf}, }; +use arboard::Clipboard; use iced::{ Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event, keyboard::{self, Key, key}, @@ -26,7 +27,7 @@ enum Message { Event(iced::Event), } -#[derive(Clone, Debug, Default)] +#[derive(Default)] struct State { image: Option, image_display: Option, @@ -35,6 +36,8 @@ struct State { error: Option, info: Option, infobar_shown: bool, + + clipboard: Option, } impl State { fn new() -> (Self, Task) { @@ -166,6 +169,52 @@ impl State { Key::Character("q") => return window::latest().and_then(window::close), + Key::Character("y") | Key::Character("c") => { + if let Some(image) = self.image.as_ref() { + match match self.clipboard.take() { + Some(c) => Ok(c), + None => Clipboard::new(), + } { + Ok(mut clipboard) => { + self.error = clipboard + .set_image(utils::arboard_from_rgbaimage(image)) + .map_err(|e| e.to_string()) + .err(); + self.clipboard = Some(clipboard); + } + Err(e) => self.error = Some(e.to_string()), + } + } else { + self.error = Some("no image to yank".into()); + } + } + Key::Character("p") | Key::Character("v") => { + match match self.clipboard.take() { + Some(c) => Ok(c), + None => Clipboard::new(), + } { + Ok(mut clipboard) => { + match clipboard.get_image().map(utils::rgbaimage_from_arboard) { + Ok(image) => { + self.error = None; + self.clipboard = Some(clipboard); + + self.info = + Some(format!("{}x{}", image.width(), image.height(),)); + if image.dimensions() < (100, 100) { + self.image_filter = widget::image::FilterMethod::Nearest; + } + self.image = Some(image); + return self.allocate_image(); + } + Err(e) => self.error = Some(e.to_string()), + }; + self.clipboard = Some(clipboard); + } + Err(e) => self.error = Some(e.to_string()), + } + } + // ignore unused keys _ => {} }, diff --git a/src/usage.txt b/src/usage.txt index 1d4c6c1..9df1bc2 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -5,3 +5,6 @@ 'b' to toggle the bar 's' to save the image 'q' to quit + +'y' or 'c' to yank +'p' or 'v' to put diff --git a/src/utils.rs b/src/utils.rs index fc1841d..474c3b3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; use iced::widget; use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; @@ -52,3 +55,19 @@ pub const fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static widget::image::FilterMethod::Nearest => "nearest neighbor", } } + +pub fn arboard_from_rgbaimage<'a>(image: &'a RgbaImage) -> arboard::ImageData<'a> { + arboard::ImageData { + width: image.width() as usize, + height: image.height() as usize, + bytes: Cow::Borrowed(image.as_ref()), + } +} +pub fn rgbaimage_from_arboard(image: arboard::ImageData) -> RgbaImage { + RgbaImage::from_raw( + image.width as u32, + image.height as u32, + image.bytes.into_owned(), + ) + .unwrap() +} From d3bc84769eca56e1caa4629ea7ae4582fcfb6d26 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 20:17:25 -0700 Subject: [PATCH 11/22] refactor: improve clipboard put code style --- src/main.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2c16d88..013c128 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,22 +194,19 @@ impl State { None => Clipboard::new(), } { Ok(mut clipboard) => { - match clipboard.get_image().map(utils::rgbaimage_from_arboard) { - Ok(image) => { - self.error = None; - self.clipboard = Some(clipboard); + let result = clipboard.get_image().map(utils::rgbaimage_from_arboard); - self.info = - Some(format!("{}x{}", image.width(), image.height(),)); - if image.dimensions() < (100, 100) { - self.image_filter = widget::image::FilterMethod::Nearest; - } - self.image = Some(image); - return self.allocate_image(); - } - Err(e) => self.error = Some(e.to_string()), - }; + self.error = result.as_ref().map_err(|e| e.to_string()).err(); self.clipboard = Some(clipboard); + + if let Ok(image) = result { + self.info = Some(format!("{}x{}", image.width(), image.height(),)); + if image.dimensions() < (100, 100) { + self.image_filter = widget::image::FilterMethod::Nearest; + } + self.image = Some(image); + return self.allocate_image(); + } } Err(e) => self.error = Some(e.to_string()), } From e57c1c9322bf0620514838e7203a87eef657a13b Mon Sep 17 00:00:00 2001 From: electria Date: Sat, 1 Aug 2026 10:22:41 -0700 Subject: [PATCH 12/22] fix: jxls being rotated twice --- src/utils.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 474c3b3..ae1dbb1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ use std::{ borrow::Cow, + ffi::OsStr, path::{Path, PathBuf}, }; @@ -11,7 +12,7 @@ pub fn load_image(path: impl AsRef) -> Result { load_image_impl(path).map_err(|e| e.to_string()) } fn load_image_impl(path: impl AsRef) -> ImageResult { - let mut decoder = ImageReader::open(path)? + let mut decoder = ImageReader::open(&path)? .with_guessed_format()? .into_decoder()?; @@ -19,7 +20,17 @@ fn load_image_impl(path: impl AsRef) -> ImageResult { let mut decoded_image = DynamicImage::from_decoder(decoder)?; - decoded_image.apply_orientation(oreintation); + // the condition is a workaround to not rotate JXL images twice; + // since they are already rotated by the decoder + // (while jpegs for instance aren't) + if !path + .as_ref() + .extension() + .map(OsStr::to_string_lossy) + .is_some_and(|s| s == "jxl") + { + decoded_image.apply_orientation(oreintation); + } Ok(decoded_image.into_rgba8()) } From db457fc4e7af44b8333c75a1e25516fdcd06529e Mon Sep 17 00:00:00 2001 From: electria Date: Mon, 3 Aug 2026 11:00:15 -0700 Subject: [PATCH 13/22] docs: add readme --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..545d7ef --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# imagey + +image viewer and maybe editor; inspired by mpv's simplicity + +name is subject to change, suggestions welcome :) + +## usage + +```sh +# try it out! +nix run git+https://git.federated.nexus/electria/imagey + +# install it imperatively +nix profile install git+https://git.federated.nexus/electria/imagey +``` + +keybinds are shown on startup if you didn't start it with an image + +## known issues + +### JXL + +1. encoding is not implemented (jxl-oxide is decoding-only) +2. oreintation is incorrect in some cases + + if the image has metadata oreintation AND + + it's a jxl without the right extension OR + it's a jpeg with the jxl extension + + this is due to the JXL decoder automatically rotating the image, + while the JPEG decoder (for instance) requires the extra step. + + my workaround is to check the path of the input file, + not changing the oreintation if it has the jxl extension; + causing these caveats for cases where the extension is wrong. + +### clipboard + +1. large images (eg photos) don't seem to copy on linux/wayland, + (despite set_image not returning any error) From 603678ba343278ee7ecf126bc28de7232b55911c Mon Sep 17 00:00:00 2001 From: electria Date: Mon, 3 Aug 2026 23:30:21 -0700 Subject: [PATCH 14/22] refactor: move kra to crate in the interest of reusing that code in a gallery app --- Cargo.lock | 13 ++++++++++++- Cargo.toml | 1 + src/kra.rs | 40 ---------------------------------------- src/main.rs | 3 +-- 4 files changed, 14 insertions(+), 43 deletions(-) delete mode 100644 src/kra.rs diff --git a/Cargo.lock b/Cargo.lock index d8f99d0..b164cb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1932,6 +1932,7 @@ dependencies = [ "iced", "image", "jxl-oxide", + "kra-image-integration", "rfd", "size", "zip", @@ -2267,6 +2268,16 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kra-image-integration" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab1db0e6caced9f4b26057c10ea190c447a8be4fcac03afd7213e35168ad700" +dependencies = [ + "image", + "zip", +] + [[package]] name = "kurbo" version = "0.10.4" @@ -3100,7 +3111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 382a20f..96d9fb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ license = "AGPL-3.0-or-later" [dependencies] dirs = "6.0.0" +kra-image-integration = "0.1.0" rfd = "0.17.2" size = "0.5.0" zip = "8.6.0" diff --git a/src/kra.rs b/src/kra.rs deleted file mode 100644 index 3642370..0000000 --- a/src/kra.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::{ - ffi::OsString, - io::{self, Read}, -}; - -use image::{ - ImageDecoder, ImageError, ImageReader, ImageResult, - error::DecodingError, - hooks::{self, GenericReader}, -}; -use zip::ZipArchive; - -pub fn register() -> bool { - hooks::register_decoding_hook(OsString::from("kra"), Box::new(hook)) -} - -fn hook<'a>(reader: GenericReader<'a>) -> ImageResult> { - let mut zip = ZipArchive::new(reader).map_err(to_image_error)?; - - let mut reader = zip.by_name("mergedimage.png").map_err(to_image_error)?; - - // reading it all and wrapping it with a Cursor - // is the only way I know to give it Seek - // (which is required by ImageReader) - let mut buf = Vec::new(); - reader.read_to_end(&mut buf)?; - - let image_reader = ImageReader::with_format(io::Cursor::new(buf), image::ImageFormat::Png); - - Ok(Box::new(image_reader.into_decoder()?)) -} - -fn to_image_error( - e: impl Into>, -) -> ImageError { - ImageError::Decoding(DecodingError::new( - image::error::ImageFormatHint::PathExtension("kra".into()), - e, - )) -} diff --git a/src/main.rs b/src/main.rs index 013c128..c676e44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,6 @@ use iced::{ use image::{EncodableLayout, RgbaImage, imageops}; use size::Size; -mod kra; mod utils; #[derive(Clone, Debug)] @@ -302,7 +301,7 @@ impl State { fn main() -> Result<(), iced::Error> { jxl_oxide::integration::register_image_decoding_hook(); - kra::register(); + kra_image_integration::register(); iced::application(State::new, State::update, State::view) .subscription(State::subscription) From a87a7bc0a156b322bde55a8be366fbff82cf9694 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 14 Aug 2026 10:25:46 -0700 Subject: [PATCH 15/22] feat: svg support --- Cargo.lock | 245 +++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/main.rs | 2 + src/svg.rs | 59 +++++++++++++ src/utils.rs | 2 +- 5 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 src/svg.rs diff --git a/Cargo.lock b/Cargo.lock index b164cb9..54fbfb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,6 +403,12 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bit-set" version = "0.8.0" @@ -841,8 +847,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "173852283a9a57a3cbe365d86e74dc428a09c50421477d5ad6fe9d9509e37737" dependencies = [ "bitflags 2.13.0", - "fontdb", - "harfrust", + "fontdb 0.23.0", + "harfrust 0.3.2", "linebender_resource_handle", "log", "rangemap", @@ -959,6 +965,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + [[package]] name = "dav1d" version = "0.11.1" @@ -1264,6 +1276,12 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "foldhash" version = "0.1.5" @@ -1294,13 +1312,22 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "font-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75382bc7392ef10aad10935f92fc3db36d2d4dad0e5d96d8d65e04f89a07ec39" +dependencies = [ + "bytemuck", +] + [[package]] name = "fontconfig-parser" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ - "roxmltree", + "roxmltree 0.20.0", ] [[package]] @@ -1317,6 +1344,19 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "fontdb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2660c5e9157bf76d2db1294e4a9feba604ef610819a3b591088d0d8392a3290f" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1625,6 +1665,18 @@ dependencies = [ "smallvec", ] +[[package]] +name = "harfrust" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" +dependencies = [ + "bitflags 2.13.0", + "bytemuck", + "read-fonts 0.41.0", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.13.2" @@ -1827,11 +1879,11 @@ dependencies = [ "cosmic-text", "iced_debug", "iced_graphics", - "kurbo", + "kurbo 0.10.4", "log", "rustc-hash 2.1.2", "softbuffer", - "tiny-skia", + "tiny-skia 0.11.4", ] [[package]] @@ -1923,6 +1975,12 @@ dependencies = [ "quick-error", ] +[[package]] +name = "imagesize" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b27460c2c92b037f3f94c538ed9a3342f3fdf923606781629ccb35f82d042a" + [[package]] name = "imagey" version = "0.1.0" @@ -1933,6 +1991,7 @@ dependencies = [ "image", "jxl-oxide", "kra-image-integration", + "resvg", "rfd", "size", "zip", @@ -2288,6 +2347,18 @@ dependencies = [ "smallvec", ] +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + [[package]] name = "lebe" version = "0.5.3" @@ -3191,6 +3262,12 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.13" @@ -3279,6 +3356,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -3547,6 +3633,17 @@ dependencies = [ "font-types 0.11.3", ] +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types 0.12.3", + "once_cell", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -3591,6 +3688,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "resvg" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e3803f97b999e80cbf7c6ecdd07a8102204d92e1633cf48783720c521196bd" +dependencies = [ + "bytemuck", + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia 0.12.0", + "usvg", + "zune-jpeg", +] + [[package]] name = "rfd" version = "0.17.2" @@ -3623,6 +3738,9 @@ name = "rgb" version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] [[package]] name = "roxmltree" @@ -3630,6 +3748,15 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3714,7 +3841,7 @@ dependencies = [ "log", "memmap2", "smithay-client-toolkit 0.19.2", - "tiny-skia", + "tiny-skia 0.11.4", ] [[package]] @@ -3848,6 +3975,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "size" version = "0.5.0" @@ -3874,6 +4016,16 @@ dependencies = [ "read-fonts 0.39.2", ] +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts 0.41.0", +] + [[package]] name = "slab" version = "0.4.12" @@ -4024,6 +4176,9 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] name = "svg_fmt" @@ -4031,6 +4186,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo 0.13.1", + "siphasher", +] + [[package]] name = "swash" version = "0.2.9" @@ -4188,7 +4353,22 @@ dependencies = [ "bytemuck", "cfg-if", "log", - "tiny-skia-path", + "tiny-skia-path 0.11.4", +] + +[[package]] +name = "tiny-skia" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path 0.12.0", ] [[package]] @@ -4202,6 +4382,17 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tiny-skia-path" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tiny-xlib" version = "0.2.5" @@ -4386,12 +4577,46 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "usvg" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977d0a4abdef933f424a99fe09f95576e089b90aebc6f016a3bc813762493e91" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb 0.24.0", + "harfrust 0.12.0", + "imagesize", + "kurbo 0.13.1", + "log", + "pico-args", + "roxmltree 0.21.1", + "simplecss", + "siphasher", + "skrifa 0.44.0", + "strict-num", + "svgtypes", + "tiny-skia-path 0.12.0", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "uuid" version = "1.23.4" @@ -5268,6 +5493,12 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "y4m" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 96d9fb8..a957ad4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "AGPL-3.0-or-later" [dependencies] dirs = "6.0.0" kra-image-integration = "0.1.0" +resvg = "0.48.1" rfd = "0.17.2" size = "0.5.0" zip = "8.6.0" diff --git a/src/main.rs b/src/main.rs index c676e44..d8a3e7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use iced::{ use image::{EncodableLayout, RgbaImage, imageops}; use size::Size; +mod svg; mod utils; #[derive(Clone, Debug)] @@ -302,6 +303,7 @@ impl State { fn main() -> Result<(), iced::Error> { jxl_oxide::integration::register_image_decoding_hook(); kra_image_integration::register(); + svg::register(); iced::application(State::new, State::update, State::view) .subscription(State::subscription) diff --git a/src/svg.rs b/src/svg.rs new file mode 100644 index 0000000..f77fafe --- /dev/null +++ b/src/svg.rs @@ -0,0 +1,59 @@ +use std::io::Read; + +use image::{ + ImageDecoder, ImageError, ImageResult, + error::DecodingError, + hooks::{self, GenericReader}, +}; +use resvg::usvg::{Options, Transform, Tree}; + +pub fn register() -> bool { + hooks::register_decoding_hook("svg".into(), Box::new(hook)) + && hooks::register_decoding_hook("svgz".into(), Box::new(hook)) +} + +fn hook<'a>(mut reader: GenericReader<'a>) -> ImageResult> { + let mut buf = Vec::new(); + reader.read_to_end(&mut buf)?; + let tree = Tree::from_data(&buf, &Options::default()).map_err(to_image_error)?; + Ok(Box::new(SvgDecoder::new(tree))) +} + +struct SvgDecoder { + tree: Tree, +} +impl SvgDecoder { + fn new(tree: Tree) -> Self { + Self { tree } + } +} +impl ImageDecoder for SvgDecoder { + fn color_type(&self) -> image::ColorType { + image::ColorType::Rgba8 + } + fn dimensions(&self) -> (u32, u32) { + self.tree.size().to_int_size().dimensions() + } + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> + where + Self: Sized, + { + let (width, height) = self.dimensions(); + let mut pixmap = resvg::tiny_skia::PixmapMut::from_bytes(buf, width, height).unwrap(); + resvg::render(&self.tree, Transform::default(), &mut pixmap); + + Ok(()) + } + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } +} + +fn to_image_error( + e: impl Into>, +) -> ImageError { + ImageError::Decoding(DecodingError::new( + image::error::ImageFormatHint::PathExtension("svg".into()), + e, + )) +} diff --git a/src/utils.rs b/src/utils.rs index ae1dbb1..f1684f7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -40,7 +40,7 @@ pub async fn pick_image() -> Result { "image", &[ "png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "avif", "jxl", "bmp", "exr", "ff", - "gif", "hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp", "kra", + "gif", "hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp", "kra", "svg", "svgz", ], ) .pick_file() From 16e13be21495389941e9a5314c5a17fb6944c6ca Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 14 Aug 2026 10:53:07 -0700 Subject: [PATCH 16/22] minor: use name SVG over extension svg to identify errors since the extension could be svg or svgz --- src/svg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/svg.rs b/src/svg.rs index f77fafe..8c3ada5 100644 --- a/src/svg.rs +++ b/src/svg.rs @@ -53,7 +53,7 @@ fn to_image_error( e: impl Into>, ) -> ImageError { ImageError::Decoding(DecodingError::new( - image::error::ImageFormatHint::PathExtension("svg".into()), + image::error::ImageFormatHint::Name("SVG".into()), e, )) } From 85a80346e3c2a6b1c424481d563edf62e509bb70 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 14 Aug 2026 11:27:07 -0700 Subject: [PATCH 17/22] refactor: move svg integration into crate --- Cargo.lock | 23 +++++++++++---------- Cargo.toml | 2 +- src/main.rs | 4 +--- src/svg.rs | 59 ----------------------------------------------------- 4 files changed, 14 insertions(+), 74 deletions(-) delete mode 100644 src/svg.rs diff --git a/Cargo.lock b/Cargo.lock index 54fbfb9..ddb04ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1990,7 +1990,7 @@ dependencies = [ "iced", "image", "jxl-oxide", - "kra-image-integration", + "misc-image-integration", "resvg", "rfd", "size", @@ -2327,16 +2327,6 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -[[package]] -name = "kra-image-integration" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab1db0e6caced9f4b26057c10ea190c447a8be4fcac03afd7213e35168ad700" -dependencies = [ - "image", - "zip", -] - [[package]] name = "kurbo" version = "0.10.4" @@ -2555,6 +2545,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "misc-image-integration" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021bf53776c68684f91b0eea6317b518d6b113db09fc929f51f1c01a760ca8f" +dependencies = [ + "image", + "resvg", + "zip", +] + [[package]] name = "moxcms" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index a957ad4..c1c42bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "AGPL-3.0-or-later" [dependencies] dirs = "6.0.0" -kra-image-integration = "0.1.0" +misc-image-integration = "0.1.0" resvg = "0.48.1" rfd = "0.17.2" size = "0.5.0" diff --git a/src/main.rs b/src/main.rs index d8a3e7e..2b7bad5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,6 @@ use iced::{ use image::{EncodableLayout, RgbaImage, imageops}; use size::Size; -mod svg; mod utils; #[derive(Clone, Debug)] @@ -302,8 +301,7 @@ impl State { fn main() -> Result<(), iced::Error> { jxl_oxide::integration::register_image_decoding_hook(); - kra_image_integration::register(); - svg::register(); + misc_image_integration::register(); iced::application(State::new, State::update, State::view) .subscription(State::subscription) diff --git a/src/svg.rs b/src/svg.rs deleted file mode 100644 index 8c3ada5..0000000 --- a/src/svg.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::io::Read; - -use image::{ - ImageDecoder, ImageError, ImageResult, - error::DecodingError, - hooks::{self, GenericReader}, -}; -use resvg::usvg::{Options, Transform, Tree}; - -pub fn register() -> bool { - hooks::register_decoding_hook("svg".into(), Box::new(hook)) - && hooks::register_decoding_hook("svgz".into(), Box::new(hook)) -} - -fn hook<'a>(mut reader: GenericReader<'a>) -> ImageResult> { - let mut buf = Vec::new(); - reader.read_to_end(&mut buf)?; - let tree = Tree::from_data(&buf, &Options::default()).map_err(to_image_error)?; - Ok(Box::new(SvgDecoder::new(tree))) -} - -struct SvgDecoder { - tree: Tree, -} -impl SvgDecoder { - fn new(tree: Tree) -> Self { - Self { tree } - } -} -impl ImageDecoder for SvgDecoder { - fn color_type(&self) -> image::ColorType { - image::ColorType::Rgba8 - } - fn dimensions(&self) -> (u32, u32) { - self.tree.size().to_int_size().dimensions() - } - fn read_image(self, buf: &mut [u8]) -> ImageResult<()> - where - Self: Sized, - { - let (width, height) = self.dimensions(); - let mut pixmap = resvg::tiny_skia::PixmapMut::from_bytes(buf, width, height).unwrap(); - resvg::render(&self.tree, Transform::default(), &mut pixmap); - - Ok(()) - } - fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { - (*self).read_image(buf) - } -} - -fn to_image_error( - e: impl Into>, -) -> ImageError { - ImageError::Decoding(DecodingError::new( - image::error::ImageFormatHint::Name("SVG".into()), - e, - )) -} From 65ddef28dfa7baff48fd2ab2ea5a279b078bb5c6 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 14 Aug 2026 11:36:40 -0700 Subject: [PATCH 18/22] fix: missing svg mime type in desktopItem --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index 5a57299..2004328 100644 --- a/flake.nix +++ b/flake.nix @@ -75,6 +75,7 @@ "image/bmp" "image/vnd.microsoft.icon" # .ico "application/x-krita" + "image/svg+xml" ]; }; in From 869e38f3a52755052cb754fddce2f5301538c6aa Mon Sep 17 00:00:00 2001 From: electria Date: Sat, 15 Aug 2026 09:47:03 -0700 Subject: [PATCH 19/22] refactor: filter out events in State::subscription this may improve performance, saving update and view calls --- src/main.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2b7bad5..dd94f79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,8 @@ enum Message { ImageDisplayReady(Result), SavePathPicked(Result), - Event(iced::Event), + KeyPressed(keyboard::Key, keyboard::Modifiers), + FileDropped(PathBuf), } #[derive(Default)] @@ -115,11 +116,7 @@ impl State { .err(); } - Message::Event(iced::Event::Keyboard(keyboard::Event::KeyPressed { - key, - modifiers, - .. - })) => match key.as_ref() { + Message::KeyPressed(key, modifiers) => match key.as_ref() { // input field cycling Key::Named(key::Named::Tab) => { if modifiers.shift() { @@ -215,18 +212,13 @@ impl State { _ => {} }, - Message::Event(iced::Event::Window(window::Event::FileDropped(file))) => { - match self.load_image(file) { - Ok(task) => { - self.error = None; - return task; - } - Err(e) => self.error = Some(e), + Message::FileDropped(path) => match self.load_image(path) { + Ok(task) => { + self.error = None; + return task; } - } - - // ignore unused events - Message::Event(_) => {} + Err(e) => self.error = Some(e), + }, } Task::none() @@ -284,7 +276,15 @@ impl State { } fn subscription(&self) -> Subscription { - event::listen().map(Message::Event) + event::listen().filter_map(|event| match event { + iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => { + Some(Message::KeyPressed(key, modifiers)) + } + iced::Event::Window(window::Event::FileDropped(path)) => { + Some(Message::FileDropped(path)) + } + _ => None, + }) } fn title(&self) -> String { self.info.as_ref().map_or_else( From 63f6b6f81b299f3c7200b4d686f509eb1287a16e Mon Sep 17 00:00:00 2001 From: electria Date: Tue, 25 Aug 2026 09:34:17 -0700 Subject: [PATCH 20/22] refactor: match keyboard keys in State::subscription this is more flexible, and maybe more performant --- src/main.rs | 178 +++++++++++++++++++++++++++++----------------------- 1 file changed, 100 insertions(+), 78 deletions(-) diff --git a/src/main.rs b/src/main.rs index dd94f79..7cd446b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,11 +19,24 @@ mod utils; #[derive(Clone, Debug)] enum Message { + Open, + Rotate, + Invert, + Filter, + Bar, + Save, + Quit, + + Yank, + Put, + + FocusNext, + FocusPrevious, + ImagePicked(Result), ImageDisplayReady(Result), SavePathPicked(Result), - KeyPressed(keyboard::Key, keyboard::Modifiers), FileDropped(PathBuf), } @@ -116,101 +129,91 @@ impl State { .err(); } - Message::KeyPressed(key, modifiers) => match key.as_ref() { - // input field cycling - Key::Named(key::Named::Tab) => { - if modifiers.shift() { - return widget::operation::focus_previous(); - } - return widget::operation::focus_next(); - } + Message::FocusNext => return widget::operation::focus_next(), + Message::FocusPrevious => return widget::operation::focus_previous(), - Key::Character("o") => { - return Task::perform(utils::pick_image(), Message::ImagePicked); - } + Message::Open => { + return Task::perform(utils::pick_image(), Message::ImagePicked); + } - Key::Character("r") => match self.image.as_ref() { - None => self.error = Some("no image to rotate".into()), - Some(image) => { - self.error = None; - self.image = Some(imageops::rotate90(image)); - return self.allocate_image(); - } - }, - Key::Character("i") => match self.image.as_mut() { - None => self.error = Some("no image to invert".into()), - Some(image) => { - self.error = None; - imageops::invert(image); - return self.allocate_image(); - } - }, - Key::Character("f") => { - self.image_filter = match self.image_filter { - widget::image::FilterMethod::Linear => widget::image::FilterMethod::Nearest, - widget::image::FilterMethod::Nearest => widget::image::FilterMethod::Linear, - } + Message::Rotate => match self.image.as_ref() { + None => self.error = Some("no image to rotate".into()), + Some(image) => { + self.error = None; + self.image = Some(imageops::rotate90(image)); + return self.allocate_image(); } - Key::Character("b") => { - self.infobar_shown = !self.infobar_shown; + }, + Message::Invert => match self.image.as_mut() { + None => self.error = Some("no image to invert".into()), + Some(image) => { + self.error = None; + imageops::invert(image); + return self.allocate_image(); } + }, + Message::Filter => { + self.image_filter = match self.image_filter { + widget::image::FilterMethod::Linear => widget::image::FilterMethod::Nearest, + widget::image::FilterMethod::Nearest => widget::image::FilterMethod::Linear, + } + } + Message::Bar => { + self.infobar_shown = !self.infobar_shown; + } - Key::Character("s") => { - if self.image.is_some() { - self.error = None; - return Task::perform(utils::pick_save_path(), Message::SavePathPicked); - } - self.error = Some("no image to save".into()); + Message::Save => { + if self.image.is_some() { + self.error = None; + return Task::perform(utils::pick_save_path(), Message::SavePathPicked); } + self.error = Some("no image to save".into()); + } - Key::Character("q") => return window::latest().and_then(window::close), + Message::Quit => return window::latest().and_then(window::close), - Key::Character("y") | Key::Character("c") => { - if let Some(image) = self.image.as_ref() { - match match self.clipboard.take() { - Some(c) => Ok(c), - None => Clipboard::new(), - } { - Ok(mut clipboard) => { - self.error = clipboard - .set_image(utils::arboard_from_rgbaimage(image)) - .map_err(|e| e.to_string()) - .err(); - self.clipboard = Some(clipboard); - } - Err(e) => self.error = Some(e.to_string()), - } - } else { - self.error = Some("no image to yank".into()); - } - } - Key::Character("p") | Key::Character("v") => { + Message::Yank => { + if let Some(image) = self.image.as_ref() { match match self.clipboard.take() { Some(c) => Ok(c), None => Clipboard::new(), } { Ok(mut clipboard) => { - let result = clipboard.get_image().map(utils::rgbaimage_from_arboard); - - self.error = result.as_ref().map_err(|e| e.to_string()).err(); + self.error = clipboard + .set_image(utils::arboard_from_rgbaimage(image)) + .map_err(|e| e.to_string()) + .err(); self.clipboard = Some(clipboard); - - if let Ok(image) = result { - self.info = Some(format!("{}x{}", image.width(), image.height(),)); - if image.dimensions() < (100, 100) { - self.image_filter = widget::image::FilterMethod::Nearest; - } - self.image = Some(image); - return self.allocate_image(); - } } Err(e) => self.error = Some(e.to_string()), } + } else { + self.error = Some("no image to yank".into()); } + } + Message::Put => { + match match self.clipboard.take() { + Some(c) => Ok(c), + None => Clipboard::new(), + } { + Ok(mut clipboard) => { + let result = clipboard.get_image().map(utils::rgbaimage_from_arboard); - // ignore unused keys - _ => {} - }, + self.error = result.as_ref().map_err(|e| e.to_string()).err(); + self.clipboard = Some(clipboard); + + if let Ok(image) = result { + self.info = Some(format!("{}x{}", image.width(), image.height(),)); + if image.dimensions() < (100, 100) { + self.image_filter = widget::image::FilterMethod::Nearest; + } + self.image = Some(image); + return self.allocate_image(); + } + } + Err(e) => self.error = Some(e.to_string()), + } + } Message::FileDropped(path) => match self.load_image(path) { Ok(task) => { @@ -278,7 +281,26 @@ impl State { fn subscription(&self) -> Subscription { event::listen().filter_map(|event| match event { iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => { - Some(Message::KeyPressed(key, modifiers)) + match key.as_ref() { + Key::Named(key::Named::Tab) => Some(if modifiers.shift() { + Message::FocusPrevious + } else { + Message::FocusNext + }), + + Key::Character("o") => Some(Message::Open), + Key::Character("r") => Some(Message::Rotate), + Key::Character("i") => Some(Message::Invert), + Key::Character("f") => Some(Message::Filter), + Key::Character("b") => Some(Message::Bar), + Key::Character("s") => Some(Message::Save), + Key::Character("q") => Some(Message::Quit), + + Key::Character("y") | Key::Character("c") => Some(Message::Yank), + Key::Character("p") | Key::Character("v") => Some(Message::Put), + + _ => None, + } } iced::Event::Window(window::Event::FileDropped(path)) => { Some(Message::FileDropped(path)) From 95a24fa9870b7cdd9de3ab4d64ec2732a31add53 Mon Sep 17 00:00:00 2001 From: electria Date: Tue, 25 Aug 2026 09:37:00 -0700 Subject: [PATCH 21/22] refactor: use Cow for errors since some errors are just string literals; this lets us avoid allocating a new string --- src/main.rs | 35 +++++++++++++++++++---------------- src/utils.rs | 4 ++-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7cd446b..d2e411c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,9 +33,9 @@ enum Message { FocusNext, FocusPrevious, - ImagePicked(Result), + ImagePicked(Result>), ImageDisplayReady(Result), - SavePathPicked(Result), + SavePathPicked(Result>), FileDropped(PathBuf), } @@ -46,7 +46,7 @@ struct State { image_display: Option, image_filter: widget::image::FilterMethod, - error: Option, + error: Option>, info: Option, infobar_shown: bool, @@ -58,7 +58,7 @@ impl State { if let Some(path) = env::args().nth(1) { match state.load_image(path) { - Err(e) => state.error = Some(e), + Err(e) => state.error = Some(e.into()), Ok(task) => return (state, task), } } @@ -108,22 +108,25 @@ impl State { } fn update(&mut self, message: Message) -> Task { match message { - Message::ImagePicked(result) => match result.and_then(|path| self.load_image(path)) { - Err(e) => self.error = Some(e), - Ok(task) => { - self.error = None; - return task; + Message::ImagePicked(result) => { + match result.and_then(|path| self.load_image(path).map_err(Cow::from)) { + Err(e) => self.error = Some(e.into()), + Ok(task) => { + self.error = None; + return task; + } } - }, + } Message::ImageDisplayReady(result) => { self.image_display = Some(result.unwrap()); } Message::SavePathPicked(result) => { self.error = result + .map_err(Cow::from) .and_then(|path| { self.image.as_ref().map_or_else( || Err("no image to save".into()), - |image| image.save(path).map_err(|e| e.to_string()), + |image| image.save(path).map_err(|e| e.to_string().into()), ) }) .err(); @@ -181,11 +184,11 @@ impl State { Ok(mut clipboard) => { self.error = clipboard .set_image(utils::arboard_from_rgbaimage(image)) - .map_err(|e| e.to_string()) + .map_err(|e| e.to_string().into()) .err(); self.clipboard = Some(clipboard); } - Err(e) => self.error = Some(e.to_string()), + Err(e) => self.error = Some(e.to_string().into()), } } else { self.error = Some("no image to yank".into()); @@ -199,7 +202,7 @@ impl State { Ok(mut clipboard) => { let result = clipboard.get_image().map(utils::rgbaimage_from_arboard); - self.error = result.as_ref().map_err(|e| e.to_string()).err(); + self.error = result.as_ref().map_err(|e| e.to_string().into()).err(); self.clipboard = Some(clipboard); if let Ok(image) = result { @@ -211,7 +214,7 @@ impl State { return self.allocate_image(); } } - Err(e) => self.error = Some(e.to_string()), + Err(e) => self.error = Some(e.to_string().into()), } } @@ -220,7 +223,7 @@ impl State { self.error = None; return task; } - Err(e) => self.error = Some(e), + Err(e) => self.error = Some(e.into()), }, } diff --git a/src/utils.rs b/src/utils.rs index f1684f7..8c1d427 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -34,7 +34,7 @@ fn load_image_impl(path: impl AsRef) -> ImageResult { Ok(decoded_image.into_rgba8()) } -pub async fn pick_image() -> Result { +pub async fn pick_image() -> Result> { let Some(filehandle) = AsyncFileDialog::new() .add_filter( "image", @@ -52,7 +52,7 @@ pub async fn pick_image() -> Result { Ok(filehandle.path().to_owned()) } -pub async fn pick_save_path() -> Result { +pub async fn pick_save_path() -> Result> { let Some(filehandle) = AsyncFileDialog::new().save_file().await else { return Err("no path to save provided".into()); }; From 6b4af8cd7dae030a792e90131f7c5ea4d64ec414 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 28 Aug 2026 12:28:45 -0700 Subject: [PATCH 22/22] refactor: use iced 0.15 rich clipboard it does use arboard internally, and yet somehow it's able to copy huge (25MB) images. either way, it's nicer to use stuff built into iced instead of dealing with the Clipboard object ourselves. --- Cargo.lock | 619 ++++++++++++++++----------------------------------- Cargo.toml | 7 +- src/main.rs | 56 ++--- src/utils.rs | 28 +-- 4 files changed, 226 insertions(+), 484 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddb04ea..0508bd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.1" @@ -411,18 +417,18 @@ checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bit-set" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" [[package]] name = "bit_field" @@ -460,12 +466,6 @@ dependencies = [ "no_std_io2", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.12.1" @@ -596,43 +596,18 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "calloop" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" -dependencies = [ - "bitflags 2.13.0", - "polling", - "rustix 1.1.4", - "slab", - "tracing", -] - [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop 0.13.0", + "calloop", "rustix 0.38.44", "wayland-backend", "wayland-client", ] -[[package]] -name = "calloop-wayland-source" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" -dependencies = [ - "calloop 0.14.4", - "rustix 1.1.4", - "wayland-backend", - "wayland-client", -] - [[package]] name = "cc" version = "1.2.65" @@ -686,36 +661,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "clipboard_macos" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" -dependencies = [ - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "clipboard_wayland" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" -dependencies = [ - "smithay-clipboard", -] - -[[package]] -name = "clipboard_x11" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd63e33452ffdafd39924c4f05a5dd1e94db646c779c6bd59148a3d95fff5ad4" -dependencies = [ - "thiserror 2.0.18", - "x11rb", -] - [[package]] name = "cmov" version = "0.5.4" @@ -724,9 +669,9 @@ checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "codespan-reporting" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ "serde", "termcolor", @@ -780,16 +725,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -803,8 +738,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", + "core-foundation", + "core-graphics-types", "foreign-types", "libc", ] @@ -816,18 +751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.13.0", - "core-foundation 0.10.1", + "core-foundation", "libc", ] @@ -842,20 +766,20 @@ dependencies = [ [[package]] name = "cosmic-text" -version = "0.15.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173852283a9a57a3cbe365d86e74dc428a09c50421477d5ad6fe9d9509e37737" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" dependencies = [ "bitflags 2.13.0", "fontdb 0.23.0", - "harfrust 0.3.2", + "harfrust 0.5.2", "linebender_resource_handle", "log", "rangemap", - "rustc-hash 1.1.0", + "rustc-hash 2.1.2", "self_cell", - "skrifa 0.37.0", - "smol_str", + "skrifa 0.40.0", + "smol_str 0.3.6", "swash", "sys-locale", "unicode-bidi", @@ -922,8 +846,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cryoglyph" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc795bdbccdbd461736fb163930a009da6597b226d6f6fce33e7a8eb6ec519" +source = "git+https://github.com/iced-rs/cryoglyph.git?rev=f4e7e4eb84dc1d2f335a98c67d8694640d53a433#f4e7e4eb84dc1d2f335a98c67d8694640d53a433" dependencies = [ "cosmic-text", "etagere", @@ -1083,9 +1006,8 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +version = "0.1.1" +source = "git+https://github.com/iced-rs/winit.git?rev=05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed#05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed" [[package]] name = "dtor" @@ -1294,15 +1216,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "font-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" -dependencies = [ - "bytemuck", -] - [[package]] name = "font-types" version = "0.11.3" @@ -1554,15 +1467,15 @@ dependencies = [ [[package]] name = "glam" -version = "0.25.0" +version = "0.33.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" +checksum = "21fef0953c54fd3de2f44b743fbf77e044c81a25faee03636dfccc0d35135e23" [[package]] name = "glow" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" dependencies = [ "js-sys", "slotmap", @@ -1579,35 +1492,18 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.13.0", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.13.0", -] - [[package]] name = "gpu-allocator" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" dependencies = [ + "ash", + "hashbrown 0.16.1", "log", "presser", - "thiserror 1.0.69", - "windows 0.58.0", + "thiserror 2.0.18", + "windows", ] [[package]] @@ -1654,14 +1550,14 @@ dependencies = [ [[package]] name = "harfrust" -version = "0.3.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" dependencies = [ "bitflags 2.13.0", "bytemuck", "core_maths", - "read-fonts 0.35.0", + "read-fonts 0.37.0", "smallvec", ] @@ -1701,6 +1597,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", ] @@ -1754,9 +1652,8 @@ dependencies = [ [[package]] name = "iced" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000e01026c93ba643f8357a3db3ada0e6555265a377f6f9291c472f6dd701fb3" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "iced_core", "iced_debug", @@ -1771,9 +1668,8 @@ dependencies = [ [[package]] name = "iced_core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ab1937d699403e7e69252ae743a902bcee9f4ab2052cc4c9a46fcf34729d85" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "bitflags 2.13.0", "bytes", @@ -1781,17 +1677,18 @@ dependencies = [ "lilt", "log", "num-traits", + "raw-window-handle", "rustc-hash 2.1.2", - "smol_str", + "smol_str 0.2.2", "thiserror 2.0.18", + "unicode-segmentation", "web-time", ] [[package]] name = "iced_debug" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25035ab0215a620e53f4103e36fc4e59a1fb2817e4bfc38a30ad27b4202ea0be" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "iced_core", "iced_futures", @@ -1800,9 +1697,8 @@ dependencies = [ [[package]] name = "iced_futures" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c0c85ccad42dfbec7293c36c018af0ea0dbcc52d137a4a9a0b0f6822a3fdf0a" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "futures", "iced_core", @@ -1814,9 +1710,8 @@ dependencies = [ [[package]] name = "iced_graphics" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234ca1c2cec4155055f68fa5fad1b5242c496ac8238d80a259bca382fb44a102" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "bitflags 2.13.0", "bytemuck", @@ -1830,14 +1725,12 @@ dependencies = [ "raw-window-handle", "rustc-hash 2.1.2", "thiserror 2.0.18", - "unicode-segmentation", ] [[package]] name = "iced_program" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfafec2947cda688d8eb00dac337ba11aa60f9ef6335aed343e189d26e4a673" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "iced_graphics", "iced_runtime", @@ -1845,9 +1738,8 @@ dependencies = [ [[package]] name = "iced_renderer" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250cc0802408e8c077986ec56c7d07c65f423ee658a4b9fd795a1f2aae5dac05" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -1858,22 +1750,19 @@ dependencies = [ [[package]] name = "iced_runtime" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1889b819ce4c06674183242e336c8d49465665441396914dc07cc86f44fa8d4" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "bytes", "iced_core", "iced_futures", - "raw-window-handle", "thiserror 2.0.18", ] [[package]] name = "iced_tiny_skia" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0acf8b75a3bc914aff5f2329fdffc1b36eeaea29dda0e4bd232f1c62e9cc3d" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "bytemuck", "cosmic-text", @@ -1888,15 +1777,13 @@ dependencies = [ [[package]] name = "iced_wgpu" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff144a999b0ca0f8a10257934500060240825c42e950ec0ebee9c8ae30561c13" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "bitflags 2.13.0", "bytemuck", "cryoglyph", "futures", - "glam", "guillotiere", "iced_debug", "iced_graphics", @@ -1908,11 +1795,11 @@ dependencies = [ [[package]] name = "iced_widget" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1596afa0d3109c2618e8bc12bae6c11d3064df8f95c42dfce570397dbe957ab" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ "iced_renderer", + "iced_runtime", "log", "num-traits", "rustc-hash 2.1.2", @@ -1922,20 +1809,21 @@ dependencies = [ [[package]] name = "iced_winit" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7dbedc47562d1de3b9707d939f678b88c382004b7ab5a18f7a7dd723162d75" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#a8ff2d522552c1252b88afc02bc310dd657cd25e" dependencies = [ + "arboard", "iced_debug", "iced_program", + "iced_runtime", "log", "mundy", + "objc2 0.5.2", "rustc-hash 2.1.2", "thiserror 2.0.18", "tracing", "wasm-bindgen-futures", "web-sys", - "window_clipboard", "winit", ] @@ -1985,7 +1873,6 @@ checksum = "65b27460c2c92b037f3f94c538ed9a3342f3fdf923606781629ccb35f82d042a" name = "imagey" version = "0.1.0" dependencies = [ - "arboard", "dirs", "iced", "image", @@ -2112,12 +1999,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ - "cfg-if", - "futures-util", + "once_cell", "wasm-bindgen", ] @@ -2477,15 +2363,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2520,21 +2397,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metal" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" -dependencies = [ - "bitflags 2.13.0", - "block", - "core-graphics-types 0.2.0", - "foreign-types", - "log", - "objc", - "paste", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2601,7 +2463,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows 0.62.2", + "windows", "zbus", ] @@ -2613,9 +2475,9 @@ checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" [[package]] name = "naga" -version = "27.0.3" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" dependencies = [ "arrayvec", "bit-set", @@ -2776,15 +2638,6 @@ dependencies = [ "syn", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2938,7 +2791,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] @@ -3055,6 +2908,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3065,7 +2930,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] @@ -3078,6 +2943,7 @@ dependencies = [ "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", ] [[package]] @@ -3593,6 +3459,18 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + [[package]] name = "rayon" version = "1.12.0" @@ -3615,13 +3493,13 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.35.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" dependencies = [ "bytemuck", "core_maths", - "font-types 0.10.1", + "font-types 0.11.3", ] [[package]] @@ -3841,7 +3719,7 @@ dependencies = [ "ab_glyph", "log", "memmap2", - "smithay-client-toolkit 0.19.2", + "smithay-client-toolkit", "tiny-skia 0.11.4", ] @@ -3999,12 +3877,12 @@ checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" [[package]] name = "skrifa" -version = "0.37.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" dependencies = [ "bytemuck", - "read-fonts 0.35.0", + "read-fonts 0.37.0", ] [[package]] @@ -4055,8 +3933,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.13.0", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", + "calloop", + "calloop-wayland-source", "cursor-icon", "libc", "log", @@ -4073,44 +3951,6 @@ dependencies = [ "xkeysym", ] -[[package]] -name = "smithay-client-toolkit" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" -dependencies = [ - "bitflags 2.13.0", - "calloop 0.14.4", - "calloop-wayland-source 0.4.1", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 1.1.4", - "thiserror 2.0.18", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-experimental", - "wayland-protocols-misc", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" -dependencies = [ - "libc", - "smithay-client-toolkit 0.20.0", - "wayland-backend", -] - [[package]] name = "smol_str" version = "0.2.2" @@ -4120,6 +3960,12 @@ dependencies = [ "serde", ] +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" + [[package]] name = "softbuffer" version = "0.4.8" @@ -4153,9 +3999,9 @@ dependencies = [ [[package]] name = "spirv" -version = "0.3.0+sdk-1.3.268.0" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ "bitflags 2.13.0", ] @@ -4479,7 +4325,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4679,9 +4524,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -4692,19 +4537,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ + "cfg-if", + "futures-util", "js-sys", + "once_cell", "wasm-bindgen", + "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4712,9 +4561,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -4725,9 +4574,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -4806,32 +4655,6 @@ dependencies = [ "wayland-scanner", ] -[[package]] -name = "wayland-protocols-experimental" -version = "20250721.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" -dependencies = [ - "bitflags 2.13.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-misc" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" -dependencies = [ - "bitflags 2.13.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - [[package]] name = "wayland-protocols-plasma" version = "0.3.12" @@ -4883,9 +4706,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -4909,12 +4732,13 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "wgpu" -version = "27.0.1" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" dependencies = [ "arrayvec", "bitflags 2.13.0", + "bytemuck", "cfg-if", "cfg_aliases", "document-features", @@ -4938,9 +4762,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "27.0.3" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" dependencies = [ "arrayvec", "bit-set", @@ -4965,55 +4789,54 @@ dependencies = [ "wgpu-core-deps-emscripten", "wgpu-core-deps-windows-linux-android", "wgpu-hal", + "wgpu-naga-bridge", "wgpu-types", ] [[package]] name = "wgpu-core-deps-apple" -version = "27.0.0" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "27.0.0" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "27.0.0" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "27.0.4" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", "bitflags 2.13.0", - "block", + "block2 0.6.2", "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", - "gpu-alloc", "gpu-allocator", "gpu-descriptor", "hashbrown 0.16.1", @@ -5022,10 +4845,13 @@ dependencies = [ "libc", "libloading", "log", - "metal", "naga", "ndk-sys", - "objc", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", "once_cell", "ordered-float", "parking_lot", @@ -5034,27 +4860,41 @@ dependencies = [ "profiling", "range-alloc", "raw-window-handle", + "raw-window-metal", "renderdoc-sys", "smallvec", "thiserror 2.0.18", "wasm-bindgen", + "wayland-sys", "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows", + "windows-core", + "windows-result", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga", "wgpu-types", - "windows 0.58.0", - "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "27.0.1" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" dependencies = [ "bitflags 2.13.0", "bytemuck", "js-sys", "log", - "thiserror 2.0.18", + "raw-window-handle", "web-sys", ] @@ -5067,30 +4907,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "window_clipboard" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5654226305eaf2dde8853fb482861d28e5dcecbbd40cb88e8393d94bb80d733" -dependencies = [ - "clipboard-win", - "clipboard_macos", - "clipboard_wayland", - "clipboard_x11", - "raw-window-handle", - "thiserror 2.0.18", -] - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets", -] - [[package]] name = "windows" version = "0.62.2" @@ -5098,7 +4914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.62.2", + "windows-core", "windows-future", "windows-numerics", ] @@ -5109,20 +4925,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets", + "windows-core", ] [[package]] @@ -5131,11 +4934,11 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-result", + "windows-strings", ] [[package]] @@ -5144,22 +4947,11 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link", "windows-threading", ] -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -5171,17 +4963,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -5205,19 +4986,10 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -5227,16 +4999,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets", -] - [[package]] name = "windows-strings" version = "0.5.1" @@ -5348,9 +5110,8 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +version = "0.30.8" +source = "git+https://github.com/iced-rs/winit.git?rev=05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed#05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed" dependencies = [ "ahash", "android-activity", @@ -5358,10 +5119,10 @@ dependencies = [ "bitflags 2.13.0", "block2 0.5.1", "bytemuck", - "calloop 0.13.0", + "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation 0.9.4", + "core-foundation", "core-graphics", "cursor-icon", "dpi", @@ -5380,8 +5141,8 @@ dependencies = [ "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", - "smithay-client-toolkit 0.19.2", - "smol_str", + "smithay-client-toolkit", + "smol_str 0.2.2", "tracing", "unicode-segmentation", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index c1c42bf..ccdbc3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ size = "0.5.0" zip = "8.6.0" [dependencies.iced] -version = "0.14.0" +version = "0.15.0-dev" features = [ "image" ] [dependencies.image] @@ -25,6 +25,5 @@ version = "0.12.6" # I really wish the image crate was named better features = [ "image" ] -[dependencies.arboard] -version = "3.6.1" -features = [ "wayland-data-control" ] +[patch.crates-io] +iced.git = "https://github.com/iced-rs/iced" diff --git a/src/main.rs b/src/main.rs index d2e411c..01ddd8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,8 @@ use std::{ path::{Path, PathBuf}, }; -use arboard::Clipboard; use iced::{ - Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event, + Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, clipboard, event, keyboard::{self, Key, key}, theme, widget, window, }; @@ -29,6 +28,8 @@ enum Message { Yank, Put, + ImageYanked(Result<(), clipboard::Error>), + ImagePut(Result), FocusNext, FocusPrevious, @@ -49,8 +50,6 @@ struct State { error: Option>, info: Option, infobar_shown: bool, - - clipboard: Option, } impl State { fn new() -> (Self, Task) { @@ -177,44 +176,27 @@ impl State { Message::Yank => { if let Some(image) = self.image.as_ref() { - match match self.clipboard.take() { - Some(c) => Ok(c), - None => Clipboard::new(), - } { - Ok(mut clipboard) => { - self.error = clipboard - .set_image(utils::arboard_from_rgbaimage(image)) - .map_err(|e| e.to_string().into()) - .err(); - self.clipboard = Some(clipboard); - } - Err(e) => self.error = Some(e.to_string().into()), - } + return clipboard::write(utils::clipboard_image_from_rgbaimage(&image)) + .map(Message::ImageYanked); } else { self.error = Some("no image to yank".into()); } } - Message::Put => { - match match self.clipboard.take() { - Some(c) => Ok(c), - None => Clipboard::new(), - } { - Ok(mut clipboard) => { - let result = clipboard.get_image().map(utils::rgbaimage_from_arboard); + Message::Put => return clipboard::read_image().map(Message::ImagePut), - self.error = result.as_ref().map_err(|e| e.to_string().into()).err(); - self.clipboard = Some(clipboard); + Message::ImageYanked(result) => { + self.error = result.err().map(|e| format!("{e:?}").into()); + } + Message::ImagePut(result) => { + self.error = result.as_ref().err().map(|e| format!("{e:?}").into()); - if let Ok(image) = result { - self.info = Some(format!("{}x{}", image.width(), image.height(),)); - if image.dimensions() < (100, 100) { - self.image_filter = widget::image::FilterMethod::Nearest; - } - self.image = Some(image); - return self.allocate_image(); - } + if let Ok(image) = result { + self.info = Some(format!("{}x{}", image.size.width, image.size.height,)); + if (image.size.width, image.size.height) < (100, 100) { + self.image_filter = widget::image::FilterMethod::Nearest; } - Err(e) => self.error = Some(e.to_string().into()), + self.image = Some(utils::rgbaimage_from_clipboard_image(image)); + return self.allocate_image(); } } @@ -333,10 +315,10 @@ fn main() -> Result<(), iced::Error> { .title(State::title) .theme(Theme::custom( "custom", - theme::Palette { + theme::palette::Seed { background: Color::BLACK, text: Color::WHITE, - ..theme::Palette::DARK + ..theme::palette::Seed::DARK }, )) .run() diff --git a/src/utils.rs b/src/utils.rs index 8c1d427..5158c08 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,8 +4,8 @@ use std::{ path::{Path, PathBuf}, }; -use iced::widget; -use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; +use iced::{clipboard, widget}; +use image::{DynamicImage, EncodableLayout, ImageDecoder, ImageReader, ImageResult, RgbaImage}; use rfd::AsyncFileDialog; pub fn load_image(path: impl AsRef) -> Result { @@ -67,18 +67,18 @@ pub const fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static } } -pub fn arboard_from_rgbaimage<'a>(image: &'a RgbaImage) -> arboard::ImageData<'a> { - arboard::ImageData { - width: image.width() as usize, - height: image.height() as usize, - bytes: Cow::Borrowed(image.as_ref()), +pub fn clipboard_image_from_rgbaimage(image: &RgbaImage) -> clipboard::Image { + clipboard::Image { + size: (image.width(), image.height()).into(), + // SAFETY: this is also probably a race condition, + // where if the user switches the image before it's done, + // then something will die. + // + // however, especially with larger images, + // copying them is just too expensive. + rgba: unsafe { std::mem::transmute::<&[u8], &'static [u8]>(image.as_bytes()) }.into(), } } -pub fn rgbaimage_from_arboard(image: arboard::ImageData) -> RgbaImage { - RgbaImage::from_raw( - image.width as u32, - image.height as u32, - image.bytes.into_owned(), - ) - .unwrap() +pub fn rgbaimage_from_clipboard_image(image: clipboard::Image) -> RgbaImage { + RgbaImage::from_raw(image.size.width, image.size.height, image.rgba.into()).unwrap() }