use std::{
collections::HashSet,
fs,
path::{Path, PathBuf},
sync::{Arc, Mutex},
thread,
};
use cgmath::Vector2;
use image::{
ColorType, DynamicImage,
imageops::{
FilterType,
colorops::{contrast_in_place, huerotate_in_place},
},
};
use winit::event_loop::EventLoopProxy;
use self::imageops::{adjust_saturation_in_place, brighten_in_place};
use super::{
cache::Cache,
clipboard,
dialog_manager::DialogProxy,
image_list::ImageList,
image_view::ImageView,
load_image::{load_from_bytes, load_uncached},
save_image,
};
use crate::{
app::undo_stack::UndoStack,
rect::Rect,
util::{Image, ImageData, UserEvent, extensions::EXTENSIONS},
};
mod imageops;
#[derive(Debug)]
pub enum Op {
LoadPath(PathBuf, bool),
LoadBytes(Vec<u8>),
Next,
Prev,
Save(PathBuf),
Resize(Vector2<u32>, FilterType),
Color {
hue: f32,
saturation: f32,
contrast: f32,
brightness: f32,
grayscale: bool,
invert: bool,
},
Crop(Rect),
ColorSpace(ColorType),
FlipHorizontal,
FlipVertical,
Rotate(i32),
Undo,
Redo,
Close,
Copy,
Paste,
Delete(PathBuf),
}
pub enum Output {
ImageLoaded(Arc<ImageData>, Option<PathBuf>),
Rotate(i32),
FlipHorizontal,
FlipVertical,
Resize(Vec<Image>),
Color(Vec<Image>),
Crop(Vec<Image>, i32),
ColorSpace(Vec<Image>),
Undo,
Redo,
Close,
Saved,
Done,
}
pub trait UserEventLoopProxyExt {
fn send_output(&self, output: Output);
}
impl UserEventLoopProxyExt for EventLoopProxy<UserEvent> {
fn send_output(&self, output: Output) {
let _ = self.send_event(UserEvent::Output(Some(output)));
}
}
#[derive(Default)]
pub struct LoadingInfo {
target_file: Option<PathBuf>,
loading: HashSet<PathBuf>,
}
pub struct OpQueue {
working: bool,
loading_info: Arc<Mutex<LoadingInfo>>,
proxy: EventLoopProxy<UserEvent>,
dialog_proxy: DialogProxy,
stack: UndoStack,
pub cache: Arc<Cache>,
pub image_list: ImageList,
}
impl OpQueue {
pub fn new(
proxy: EventLoopProxy<UserEvent>,
dialog_proxy: DialogProxy,
no_cache: bool,
) -> Self {
const CACHE_SIZE: usize = 1_000_000_000;
let cache = Arc::new(Cache::new(if no_cache { 0 } else { CACHE_SIZE }));
let loading_info = Arc::new(Mutex::new(LoadingInfo::default()));
Self {
working: false,
image_list: ImageList::new(cache.clone(), proxy.clone(), loading_info.clone()),
loading_info,
dialog_proxy,
stack: UndoStack::new(),
proxy,
cache,
}
}
pub fn queue(&mut self, op: Op, view: Option<&ImageView>) {
if !self.working {
self.working = true;
match op {
Op::LoadPath(path, use_cache) => {
self.load(path, use_cache, self.stack.is_edited());
}
Op::LoadBytes(bytes) => self.load_from_bytes(bytes),
Op::Next => match self.image_list.next() {
Some(path) => {
self.load(path, true, self.stack.is_edited());
}
None => {
self.proxy.send_output(Output::Done);
}
},
Op::Prev => match self.image_list.prev() {
Some(path) => {
self.load(path, true, self.stack.is_edited());
}
None => {
self.proxy.send_output(Output::Done);
}
},
Op::Save(path) => {
if let Some(view) = view {
save_image::save(
self.proxy.clone(),
self.dialog_proxy.clone(),
path,
view.image_data.clone(),
view.rotation(),
view.horizontal_flip,
view.vertical_flip,
)
}
}
Op::Rotate(dir) => {
self.proxy.send_output(Output::Rotate(dir));
}
Op::FlipHorizontal => {
self.proxy.send_output(Output::FlipHorizontal);
}
Op::FlipVertical => {
self.proxy.send_output(Output::FlipVertical);
}
Op::Undo => {
self.proxy.send_output(Output::Undo);
}
Op::Redo => {
self.proxy.send_output(Output::Redo);
}
Op::Close => {
let proxy = self.proxy.clone();
let dialog_proxy = self.dialog_proxy.clone();
if self.stack.is_edited() {
thread::spawn(move || {
let close = dialog_proxy
.spawn_dialog("Unsaved changes", move |ui, enter| {
ui.label(
"You have unsaved changes are you sure you want to close this image?",
);
ui.with_layout(
egui::Layout::left_to_right(egui::Align::LEFT),
|ui| {
if ui.button("Ok").clicked() {
return Some(true);
}
if ui.button("Cancel").clicked() {
return Some(false);
}
if *enter {
*enter = false;
return Some(true);
}
None
},
)
.inner
})
.wait()
.unwrap_or(false);
if close {
proxy.send_output(Output::Close);
} else {
proxy.send_output(Output::Done);
}
});
} else {
proxy.send_output(Output::Close);
}
}
Op::ColorSpace(color_type) => {
let image_data = view.as_ref().unwrap().image_data.clone();
let proxy = self.proxy.clone();
thread::spawn(move || {
let guard = image_data.read().unwrap();
let mut new = Vec::new();
for image in guard.frames.iter() {
let buffer = image.buffer();
let buffer = match color_type {
ColorType::L8 => DynamicImage::ImageLuma8(buffer.to_luma8()),
ColorType::La8 => {
DynamicImage::ImageLumaA8(buffer.to_luma_alpha8())
}
ColorType::Rgb8 => DynamicImage::ImageRgb8(buffer.to_rgb8()),
ColorType::Rgba8 => DynamicImage::ImageRgba8(buffer.to_rgba8()),
ColorType::L16 => DynamicImage::ImageLuma16(buffer.to_luma16()),
ColorType::La16 => {
DynamicImage::ImageLumaA16(buffer.to_luma_alpha16())
}
ColorType::Rgb16 => DynamicImage::ImageRgb16(buffer.to_rgb16()),
ColorType::Rgba16 => DynamicImage::ImageRgba16(buffer.to_rgba16()),
ColorType::Rgb32F => DynamicImage::ImageRgb32F(buffer.to_rgb32f()),
ColorType::Rgba32F => {
DynamicImage::ImageRgba32F(buffer.to_rgba32f())
}
_ => panic!("unknown color type this a bug"),
};
new.push(Image::with_delay(buffer, image.delay));
}
proxy.send_output(Output::ColorSpace(new));
});
}
Op::Resize(size, resample) => {
let image_data = view.as_ref().unwrap().image_data.clone();
let proxy = self.proxy.clone();
thread::spawn(move || {
let guard = image_data.read().unwrap();
let mut new = Vec::new();
for image in guard.frames.iter() {
let buffer = image.buffer().resize_exact(size.x, size.y, resample);
new.push(Image::with_delay(buffer, image.delay));
}
proxy.send_output(Output::Resize(new));
});
}
Op::Color {
hue,
saturation,
contrast,
brightness,
grayscale,
invert,
} => {
let image_data = view.as_ref().unwrap().image_data.clone();
let proxy = self.proxy.clone();
thread::spawn(move || {
let guard = image_data.read().unwrap();
let mut new: Vec<_> = guard.frames.clone();
drop(guard);
for image in &mut new {
let buffer = image.buffer_mut();
if hue != 0.0 {
huerotate_in_place(buffer, hue as i32);
}
if contrast != 0.0 {
contrast_in_place(buffer, contrast);
}
if saturation != 0.0 {
adjust_saturation_in_place(buffer, saturation as f64);
}
if brightness != 0.0 {
brighten_in_place(buffer, brightness as f64);
}
if grayscale {
*buffer = DynamicImage::ImageLumaA8(imageops::grayscale(buffer));
}
if invert {
buffer.invert();
}
}
proxy.send_output(Output::Color(new));
});
}
Op::Crop(rect) => {
view.unwrap().crop(rect, self.proxy.clone());
}
Op::Copy => {
clipboard::copy(view.unwrap(), self.proxy.clone());
}
Op::Paste => {
clipboard::paste(self.proxy.clone());
}
Op::Delete(path) => match trash::delete(&path) {
Ok(_) => match self.image_list.trash(&path) {
Some(path) => {
self.load(path, true, false);
}
None => {
self.proxy.send_output(Output::Close);
}
},
Err(error) => {
let _ = self
.proxy
.send_event(UserEvent::ErrorMessage(error.to_string()));
}
},
}
}
}
pub fn undo_stack_mut(&mut self) -> &mut UndoStack {
&mut self.stack
}
pub fn undo_stack(&self) -> &UndoStack {
&self.stack
}
fn load(&self, path_buf: PathBuf, use_cache: bool, edited_prompt: bool) {
let path_buf = match path_buf.strip_prefix("file://") {
Ok(path) => Path::new("/").join(path),
Err(_) => path_buf,
};
{
let mut guard = self.loading_info.lock().unwrap();
guard.target_file = Some(path_buf.clone());
if guard.loading.contains(&path_buf) {
return;
} else {
guard.loading.insert(path_buf.clone());
}
}
if !use_cache {
self.cache.pop(&path_buf);
}
let cache = self.cache.clone();
let proxy = self.proxy.clone();
let loading_info = self.loading_info.clone();
let dialog_proxy = self.dialog_proxy.clone();
thread::spawn(move || {
let mut path = path_buf.clone();
if path.is_dir() {
let mut paths = Vec::new();
if let Ok(dir) = fs::read_dir(&path) {
for entry in dir.flatten() {
if let Ok(file_type) = entry.file_type()
&& file_type.is_file()
{
let path = entry.path();
match path.extension() {
Some(ext)
if EXTENSIONS.contains(
&&*ext.to_string_lossy().to_ascii_lowercase(),
) =>
{
paths.push(path);
}
_ => (),
}
}
}
}
paths.sort();
if let Some(p) = paths.into_iter().next() {
path = p;
}
}
if edited_prompt {
let close = dialog_proxy
.spawn_dialog("Unsaved changes", move |ui, enter| {
ui.label(
"You have unsaved changes are you sure you want to close this image?",
);
ui.with_layout(egui::Layout::left_to_right(egui::Align::LEFT), |ui| {
if ui.button("Ok").clicked() {
return Some(true);
}
if ui.button("Cancel").clicked() {
return Some(false);
}
if *enter {
*enter = false;
return Some(true);
}
None
})
.inner
})
.wait()
.unwrap_or(false);
if !close {
proxy.send_output(Output::Done);
return;
}
}
if let Some(images) = cache.get(&path_buf) {
let mut guard = loading_info.lock().unwrap();
guard.loading.remove(&path_buf);
guard.target_file = None;
proxy.send_output(Output::ImageLoaded(images, Some(path_buf)));
return;
}
let res = load_uncached(&path);
let mut guard = loading_info.lock().unwrap();
guard.loading.remove(&path_buf);
guard.target_file = None;
match res {
Ok(images) => {
let images = Arc::new(images);
cache.put(path_buf.clone(), images.clone());
proxy.send_output(Output::ImageLoaded(images, Some(path)));
}
Err(error) => {
proxy.send_output(Output::Done);
let _ = proxy.send_event(UserEvent::ErrorMessage(error.to_string()));
}
};
});
}
fn load_from_bytes(&self, bytes: Vec<u8>) {
let proxy = self.proxy.clone();
thread::spawn(move || {
let res = load_from_bytes(&bytes, None);
match res {
Ok(images) => {
let images = Arc::new(images);
proxy.send_output(Output::ImageLoaded(images, None));
}
Err(error) => {
proxy.send_output(Output::Done);
let _ = proxy.send_event(UserEvent::ErrorMessage(error.to_string()));
}
};
});
}
pub fn working(&self) -> bool {
self.working
}
pub fn set_working(&mut self, working: bool) {
self.working = working;
}
}
pub fn prefetch(
path: impl AsRef<Path>,
cache: Arc<Cache>,
proxy: EventLoopProxy<UserEvent>,
loading_info: Arc<Mutex<LoadingInfo>>,
) {
let path_buf = path.as_ref().to_path_buf();
if cache.contains(&path_buf) {
return;
}
{
let mut guard = loading_info.lock().unwrap();
if guard.loading.contains(&path_buf) {
return;
} else {
guard.loading.insert(path_buf.clone());
}
}
thread::spawn(move || {
let res = load_uncached(&path_buf);
let mut guard = loading_info.lock().unwrap();
guard.loading.remove(&path_buf);
match res {
Ok(images) => {
let images = Arc::new(images);
cache.put(path_buf.clone(), images.clone());
if guard.target_file.as_ref() == Some(&path_buf) {
proxy.send_output(Output::ImageLoaded(images, Some(path_buf.clone())));
}
}
Err(error) => {
if guard.target_file.as_ref() == Some(&path_buf) {
let _ = proxy.send_event(UserEvent::ErrorMessage(error.to_string()));
}
}
};
if guard.target_file.as_ref() == Some(&path_buf) {
guard.target_file = None;
}
});
}