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

View file

@ -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<RgbaImage>,
image_display: Option<widget::image::Allocation>,
@ -35,6 +36,8 @@ struct State {
error: Option<String>,
info: Option<String>,
infobar_shown: bool,
clipboard: Option<Clipboard>,
}
impl State {
fn new() -> (Self, Task<Message>) {
@ -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
_ => {}
},

View file

@ -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

View file

@ -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()
}