it does use arboard internally, but it's nicer to use stuff built into iced instead of dealing with the Clipboard object ourselves.
84 lines
2.7 KiB
Rust
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()
|
|
}
|