feat: image copy and paste

This commit is contained in:
electria 2026-07-31 19:36:58 -07:00
commit cb5e1d837a
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
5 changed files with 155 additions and 2 deletions

78
Cargo.lock generated
View file

@ -136,6 +136,27 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" 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]] [[package]]
name = "arg_enum_proc_macro" name = "arg_enum_proc_macro"
version = "0.3.4" version = "0.3.4"
@ -1226,6 +1247,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.9" version = "1.1.9"
@ -1900,6 +1927,7 @@ dependencies = [
name = "imagey" name = "imagey"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arboard",
"dirs", "dirs",
"iced", "iced",
"image", "image",
@ -3065,6 +3093,16 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "owned_ttf_parser" name = "owned_ttf_parser"
version = "0.25.1" version = "0.25.1"
@ -3131,6 +3169,17 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 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]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.13" version = "1.1.13"
@ -4253,6 +4302,17 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.25.1" version = "0.25.1"
@ -5116,6 +5176,24 @@ version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" 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]] [[package]]
name = "x11-dl" name = "x11-dl"
version = "2.21.0" version = "2.21.0"

View file

@ -22,3 +22,7 @@ features = [ "avif-native" ]
version = "0.12.6" version = "0.12.6"
# I really wish the image crate was named better # I really wish the image crate was named better
features = [ "image" ] features = [ "image" ]
[dependencies.arboard]
version = "3.6.1"
features = [ "wayland-data-control" ]

View file

@ -6,6 +6,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use arboard::Clipboard;
use iced::{ use iced::{
Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event, Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event,
keyboard::{self, Key, key}, keyboard::{self, Key, key},
@ -26,7 +27,7 @@ enum Message {
Event(iced::Event), Event(iced::Event),
} }
#[derive(Clone, Debug, Default)] #[derive(Default)]
struct State { struct State {
image: Option<RgbaImage>, image: Option<RgbaImage>,
image_display: Option<widget::image::Allocation>, image_display: Option<widget::image::Allocation>,
@ -35,6 +36,8 @@ struct State {
error: Option<String>, error: Option<String>,
info: Option<String>, info: Option<String>,
infobar_shown: bool, infobar_shown: bool,
clipboard: Option<Clipboard>,
} }
impl State { impl State {
fn new() -> (Self, Task<Message>) { fn new() -> (Self, Task<Message>) {
@ -166,6 +169,52 @@ impl State {
Key::Character("q") => return window::latest().and_then(window::close), 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 // ignore unused keys
_ => {} _ => {}
}, },

View file

@ -5,3 +5,6 @@
'b' to toggle the bar 'b' to toggle the bar
's' to save the image 's' to save the image
'q' to quit 'q' to quit
'y' or 'c' to yank
'p' or 'v' to put

View file

@ -1,4 +1,7 @@
use std::path::{Path, PathBuf}; use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use iced::widget; use iced::widget;
use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; 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", 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()
}