imagey/src/utils.rs
electria f4241b9711
refactor: use iced 0.15 rich clipboard
it does use arboard internally,
but it's nicer to use stuff built into iced
instead of dealing with the Clipboard object ourselves.
2026-08-28 12:31:20 -07:00

84 lines
2.7 KiB
Rust

use std::{
borrow::Cow,
ffi::OsStr,
path::{Path, PathBuf},
};
use iced::{clipboard, widget};
use image::{DynamicImage, EncodableLayout, ImageDecoder, ImageReader, ImageResult, RgbaImage};
use rfd::AsyncFileDialog;
pub fn load_image(path: impl AsRef<Path>) -> Result<RgbaImage, String> {
load_image_impl(path).map_err(|e| e.to_string())
}
fn load_image_impl(path: impl AsRef<Path>) -> ImageResult<RgbaImage> {
let mut decoder = ImageReader::open(&path)?
.with_guessed_format()?
.into_decoder()?;
let oreintation = decoder.orientation()?;
let mut decoded_image = DynamicImage::from_decoder(decoder)?;
// 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())
}
pub async fn pick_image() -> Result<PathBuf, Cow<'static, str>> {
let Some(filehandle) = AsyncFileDialog::new()
.add_filter(
"image",
&[
"png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "avif", "jxl", "bmp", "exr", "ff",
"gif", "hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp", "kra", "svg", "svgz",
],
)
.pick_file()
.await
else {
return Err("no path to open provided".into());
};
Ok(filehandle.path().to_owned())
}
pub async fn pick_save_path() -> Result<PathBuf, Cow<'static, str>> {
let Some(filehandle) = AsyncFileDialog::new().save_file().await else {
return Err("no path to save provided".into());
};
Ok(filehandle.path().to_owned())
}
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",
}
}
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_clipboard_image(image: clipboard::Image) -> RgbaImage {
RgbaImage::from_raw(image.size.width, image.size.height, image.rgba.into()).unwrap()
}