diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6e65dd8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'post_maker'", + "cargo": { + "args": [ + "build", + "--bin=post_maker", + "--package=post_maker" + ], + "filter": { + "name": "post_maker", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'post_maker'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=post_maker", + "--package=post_maker" + ], + "filter": { + "name": "post_maker", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 26613a0..e3ce78d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -556,6 +556,7 @@ dependencies = [ "fltk-theme", "image", "imageproc", + "lazy_static", "rusttype", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index a802e54..4ab46bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ imageproc = "0.22" rusttype = "0.9" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } +lazy_static = "1.4" diff --git a/src/draw_thread.rs b/src/draw_thread.rs new file mode 100644 index 0000000..8dd90a8 --- /dev/null +++ b/src/draw_thread.rs @@ -0,0 +1,182 @@ +use crate::{ + main_window::{MainWindow, Page}, + AppMessage, +}; +use fltk::{ + app, + input::{Input, MultilineInput}, + menu, + misc::Spinner, + prelude::*, +}; + +use std::{ + fs, + path::Path, + sync::{mpsc, Arc, RwLock}, +}; + +use crate::utils::{ImageContainer, ImageProperties}; + +#[derive(Debug, Clone)] +pub(crate) enum DrawMessage { + Open, + Recalc, + Flush, +} + +pub(crate) fn spawn_image_thread( + reciver: mpsc::Receiver, + app_sender: app::Sender, + properties: Arc>, + main_win: &MainWindow, +) { + let mut file_choice = main_win.file_choice.clone(); + let mut quote = main_win.quote.clone(); + let mut tag = main_win.tag.clone(); + let mut layer_red = main_win.layer_red.clone(); + let mut layer_green = main_win.layer_green.clone(); + let mut layer_blue = main_win.layer_blue.clone(); + let mut layer_alpha = main_win.layer_alpha.clone(); + let mut quote_position = main_win.quote_position.clone(); + let mut tag_position = main_win.tag_position.clone(); + let mut page = main_win.page.clone(); + + let mut _container: Option = None; + std::thread::spawn(move || loop { + if let Ok(val) = reciver.recv() { + match val { + DrawMessage::Open => load_image( + &mut file_choice, + &mut quote, + &mut tag, + &mut layer_red, + &mut layer_green, + &mut layer_blue, + &mut layer_alpha, + &mut quote_position, + &mut tag_position, + &mut page, + &app_sender, + &properties, + &mut _container, + ), + DrawMessage::Recalc => { + if let Some(cont) = &mut _container { + cont.recalc(); + } + } + // DrawMessage::CropPos(x, y) => { + // if let Some(cont) = &mut _container { + // cont.apply_crop_pos(x, y); + // } + // } + // DrawMessage::Crop => { + // if let Some(cont) = &mut _container { + // cont.apply_crop(); + // } + // } + DrawMessage::Flush => { + flush_buffer(&app_sender, &mut _container); + println!("recived"); + } + } + } + }); +} + +fn load_image( + file_choice: &mut menu::Choice, + quote: &mut MultilineInput, + tag: &mut Input, + layer_red: &mut Spinner, + layer_green: &mut Spinner, + layer_blue: &mut Spinner, + layer_alpha: &mut Spinner, + quote_position: &mut Spinner, + tag_position: &mut Spinner, + page: &mut Page, + app_sender: &app::Sender, + properties: &Arc>, + container: &mut Option, +) { + let file: String = match file_choice.choice() { + Some(val) => val, + None => return, + }; + + *container = Some(ImageContainer::new(&file, Arc::clone(properties))); + + if let Some(cont) = container { + let file = Path::new(&file); + let conf = file.with_extension("conf"); + + let properties = Arc::clone(&cont.properties); + let mut use_defaults = true; + if conf.exists() { + let read = fs::read_to_string(&conf).unwrap(); + if let Ok(saved_prop) = serde_json::from_str::(&read) { + let mut prop = properties.write().unwrap(); + layer_red.set_value(saved_prop.rgba[0] as f64); + layer_green.set_value(saved_prop.rgba[1] as f64); + layer_blue.set_value(saved_prop.rgba[2] as f64); + layer_alpha.set_value(saved_prop.rgba[3] as f64); + quote.set_value(&saved_prop.quote); + tag.set_value(&saved_prop.tag); + quote_position.set_range(0.0, prop.original_dimension.1 as f64); + quote_position.set_value(saved_prop.quote_position as f64); + tag_position.set_range(0.0, prop.original_dimension.1 as f64); + tag_position.set_value(saved_prop.tag_position as f64); + + prop.quote = saved_prop.quote; + prop.tag = saved_prop.tag; + prop.quote_position = saved_prop.quote_position; + prop.tag_position = saved_prop.quote_position; + prop.rgba = saved_prop.rgba; + use_defaults = false; + + drop(prop); + if let Some((x, y)) = saved_prop.crop_position { + cont.apply_crop_pos(x, y); + } + } + } + + if use_defaults { + let mut prop = properties.write().unwrap(); + quote.set_value(""); + tag.set_value(""); + + quote_position.set_range(0.0, prop.original_dimension.1 as f64); + quote_position.set_value(prop.quote_position as f64); + tag_position.set_range(0.0, prop.original_dimension.1 as f64); + tag_position.set_value(prop.tag_position as f64); + + prop.rgba = [ + layer_red.value() as u8, + layer_green.value() as u8, + layer_blue.value() as u8, + layer_alpha.value() as u8, + ]; + drop(prop); + cont.apply_crop(); + } + + let prop = properties.read().unwrap(); + let (width, height) = prop.dimension; + page.col_flex.set_size(&page.image, height as i32); + page.row_flex.set_size(&page.col_flex, width as i32); + page.col_flex.recalc(); + page.row_flex.recalc(); + cont.recalc(); + } + flush_buffer(&app_sender, &container); +} + +fn flush_buffer(app_sender: &app::Sender, container: &Option) { + if let Some(cont) = container { + app_sender.send(AppMessage::RedrawMainWindowImage( + cont.buffer.as_rgb8().unwrap().as_raw().to_owned(), + )); + } +} diff --git a/src/main.rs b/src/main.rs index b41bff3..db6a858 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,25 @@ mod config; // mod crop_window; +mod draw_thread; mod main_window; mod properties; mod utils; // use crop_window::CropWindow; -use fltk::{app::App, enums::Font}; +use fltk::{ + app::{channel, App}, + enums::Font, + prelude::*, +}; use fltk_theme::WidgetTheme; + use main_window::MainWindow; -use std::{cell::RefCell, rc::Rc}; -use utils::ImageContainer; +use std::sync::{Arc, RwLock}; + +#[derive(Clone, Debug)] +pub(crate) enum AppMessage { + RedrawMainWindowImage(Vec), +} fn main() { let app = App::default(); @@ -21,14 +31,25 @@ fn main() { .into(), ) .apply(); - let f1 = Font::load_font("ReenieBeanie-Regular.ttf").unwrap(); let f2 = Font::load_font("Kalam-Regular.ttf").unwrap(); Font::set_font(Font::Times, &f1); Font::set_font(Font::TimesItalic, &f2); - let container: Rc>> = Rc::new(RefCell::new(None)); + let draw_buff: Arc>> = Arc::new(RwLock::new(vec![])); + let (main_sender, main_receiver) = channel::(); + let mut main_window = MainWindow::new(main_sender, Arc::clone(&draw_buff)); - let main_win = MainWindow::new(Rc::clone(&container)); - app.run().unwrap(); + while app.wait() { + if let Some(msg) = main_receiver.recv() { + match msg { + AppMessage::RedrawMainWindowImage(data) => { + let mut buff = draw_buff.write().unwrap(); + *buff = data; + println!("copy buff"); + main_window.win.redraw(); + } + } + } + } } diff --git a/src/main_window.rs b/src/main_window.rs index b3b3120..cfd5c47 100644 --- a/src/main_window.rs +++ b/src/main_window.rs @@ -1,6 +1,7 @@ -use crate::utils::ImageContainer; -use crate::{properties::Properties, utils}; +use crate::utils::ImageProperties; +use crate::{draw_thread::*, properties}; use fltk::{ + app, button::Button, dialog::NativeFileChooser, draw as dr, enums, @@ -13,40 +14,45 @@ use fltk::{ prelude::*, window::Window, }; -use image::GenericImageView; -use std::io::Read; -use std::{cell::RefCell, ffi::OsStr, fs, path::Path, rc::Rc}; +use std::sync::{mpsc, RwLock}; +use std::{ffi::OsStr, fs, path::Path, sync::Arc}; pub(crate) struct MainWindow { pub(crate) win: Window, - menubar: menu::SysMenuBar, - back_btn: Button, - next_btn: Button, - save_btn: Button, - file_choice: menu::Choice, - quote: MultilineInput, - tag: Input, - layer_red: Spinner, - layer_green: Spinner, - layer_blue: Spinner, - layer_alpha: Spinner, - quote_position: Spinner, - tag_position: Spinner, - crop_btn: Button, - reset_btn: Button, - page: Page, - container: Rc>>, + pub(crate) menubar: menu::SysMenuBar, + pub(crate) back_btn: Button, + pub(crate) next_btn: Button, + pub(crate) save_btn: Button, + pub(crate) file_choice: menu::Choice, + pub(crate) quote: MultilineInput, + pub(crate) tag: Input, + pub(crate) layer_red: Spinner, + pub(crate) layer_green: Spinner, + pub(crate) layer_blue: Spinner, + pub(crate) layer_alpha: Spinner, + pub(crate) quote_position: Spinner, + pub(crate) tag_position: Spinner, + pub(crate) crop_btn: Button, + pub(crate) reset_btn: Button, + pub(crate) status: Frame, + pub(crate) page: Page, + pub(crate) draw_buff: Arc>>, + pub(crate) properties: Arc>, + pub(crate) sender: mpsc::Sender, } #[derive(Clone)] pub(crate) struct Page { - image: Frame, - row_flex: Flex, - col_flex: Flex, + pub(crate) image: Frame, + pub(crate) row_flex: Flex, + pub(crate) col_flex: Flex, } impl MainWindow { - pub(crate) fn new(container: Rc>>) -> Self { + pub(crate) fn new( + sender: app::Sender, + draw_buff: Arc>>, + ) -> Self { let color = [25, 29, 34, 190]; let mut win = Window::default() @@ -144,6 +150,11 @@ impl MainWindow { actions_flex.end(); controls_flex.set_size(&actions_flex, 30); + Frame::default(); + + let status = Frame::default(); + controls_flex.set_size(&status, 30); + controls_flex.end(); workspace_flex.set_size(&controls_flex, 360); @@ -167,6 +178,8 @@ impl MainWindow { win.make_resizable(true); win.show(); + let properties = Arc::new(RwLock::new(ImageProperties::new())); + let (rx, tx) = std::sync::mpsc::channel(); let mut main_win = Self { win, menubar, @@ -184,13 +197,17 @@ impl MainWindow { tag_position, crop_btn, reset_btn, - container, + status, + draw_buff, + properties: Arc::clone(&properties), page: Page { image: img_view, row_flex: center_row_flex, col_flex: center_col_flex, }, + sender: rx, }; + spawn_image_thread(tx, sender, Arc::clone(&properties), &main_win); main_win.menu(); main_win.draw(); main_win.events(); @@ -199,27 +216,30 @@ impl MainWindow { fn menu(&mut self) { let mut file_choice = self.file_choice.clone(); - let mut quote = self.quote.clone(); - let mut tag = self.tag.clone(); - let mut layer_red = self.layer_red.clone(); - let mut layer_green = self.layer_green.clone(); - let mut layer_blue = self.layer_blue.clone(); - let mut layer_alpha = self.layer_alpha.clone(); - let mut quote_position = self.quote_position.clone(); - let mut tag_position = self.tag_position.clone(); - let mut page = self.page.clone(); - let container = Rc::clone(&self.container); + // let mut quote = self.quote.clone(); + // let mut tag = self.tag.clone(); + // let mut layer_red = self.layer_red.clone(); + // let mut layer_green = self.layer_green.clone(); + // let mut layer_blue = self.layer_blue.clone(); + // let mut layer_alpha = self.layer_alpha.clone(); + // let mut quote_position = self.quote_position.clone(); + // let mut tag_position = self.tag_position.clone(); + // let mut page = self.page.clone(); + let sender = self.sender.clone(); + // let properties = Arc::clone(&self.properties); let mut win = self.win.clone(); self.menubar.add( "&File/Open Folder...\t", Shortcut::Ctrl | 'o', menu::MenuFlag::Normal, move |_| { + win.redraw(); let mut chooser = NativeFileChooser::new(fltk::dialog::FileDialogType::BrowseDir); chooser.set_option(fltk::dialog::FileDialogOptions::NewFolder); chooser.show(); let path = chooser.filename(); if !path.exists() { + win.activate(); return; } let files = fs::read_dir(path).unwrap(); @@ -232,26 +252,13 @@ impl MainWindow { } } if text.len() == 0 { + win.activate(); return; } file_choice.clear(); file_choice.add_choice(&text[1..]); file_choice.set_value(0); - - load_image( - &mut file_choice, - &mut quote, - &mut tag, - &mut layer_red, - &mut layer_green, - &mut layer_blue, - &mut layer_alpha, - &mut quote_position, - &mut tag_position, - &mut page, - &container, - ); - win.redraw(); + sender.send(DrawMessage::Open).unwrap(); }, ); @@ -273,130 +280,193 @@ impl MainWindow { } fn draw(&mut self) { - let mut buffer = Vec::new(); - fs::File::open("ReenieBeanie-Regular.ttf") - .unwrap() - .read_to_end(&mut buffer) - .unwrap(); - let font = rusttype::Font::try_from_vec(buffer).unwrap(); - - let container = Rc::clone(&self.container); - let quote = self.quote.clone(); + println!("in draw"); + let buff = Arc::clone(&self.draw_buff); + let properties = Arc::clone(&self.properties); self.page.image.draw(move |f| { - if let Some(cont) = &*container.borrow() { - let image = cont.image.as_rgb8().unwrap(); - dr::draw_image( - image.as_raw(), - f.x(), - f.y(), - image.width() as i32, - image.height() as i32, - enums::ColorDepth::Rgb8, - ) - .unwrap(); - - dr::set_color_rgb(255, 255, 255); - - let size = utils::quote_from_height(image.height()); - dr::set_font(enums::Font::Times, size as i32); - - let (text_width, text_height) = utils::measure_line( - &font, - "e.value(), - rusttype::Scale::uniform(size as f32), - ); - - dr::draw_text( - "e.value(), - f.x() + image.width() as i32 / 2 - text_width as i32 / 2, - f.y() + image.height() as i32 / 2 - text_height as i32 / 2, - ); - } + let (width, height) = properties.read().unwrap().dimension; + let image = &*buff.read().unwrap(); + dr::draw_image( + &image, + f.x(), + f.y(), + width as i32, + height as i32, + enums::ColorDepth::Rgb8, + ) + .unwrap(); }) } fn events(&mut self) { let mut image = self.page.image.clone(); - self.quote.handle(move |_, ev| { + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.quote.handle(move |f, ev| { if ev == enums::Event::KeyUp { + let mut prop = properties.write().unwrap(); + prop.quote = f.value(); + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); image.redraw(); } true }); - } -} -fn load_image( - file_choice: &mut menu::Choice, - quote: &mut MultilineInput, - tag: &mut Input, - layer_red: &mut Spinner, - layer_green: &mut Spinner, - layer_blue: &mut Spinner, - layer_alpha: &mut Spinner, - quote_position: &mut Spinner, - tag_position: &mut Spinner, - page: &mut Page, - container: &Rc>>, -) { - let file: String = match file_choice.choice() { - Some(val) => val, - None => return, - }; - - *container.borrow_mut() = Some(ImageContainer::new(&file)); - - let file = Path::new(&file); - let conf = file.with_extension("conf"); - - let mut use_defaults = true; - if conf.exists() { - let read = fs::read_to_string(&conf).unwrap(); - if let Ok(prop) = serde_json::from_str::(&read) { - if let Some(cont) = &mut *container.borrow_mut() { - layer_red.set_value(prop.rgba[0] as f64); - layer_green.set_value(prop.rgba[1] as f64); - layer_blue.set_value(prop.rgba[2] as f64); - layer_alpha.set_value(prop.rgba[3] as f64); - quote.set_value(&prop.quote); - tag.set_value(&prop.tag); - quote_position.set_value(prop.quote_position as f64); - tag_position.set_value(prop.tag_position as f64); - cont.apply_crop_pos(prop.crop_position.0, prop.crop_position.1); - - cont.quote = prop.quote; - cont.tag = prop.tag; - cont.quote_position = prop.quote_position; - cont.tag_position = prop.quote_position; - cont.rgba = prop.rgba; + let mut image = self.page.image.clone(); + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.tag.handle(move |f, ev| { + if ev == enums::Event::KeyUp { + let mut prop = properties.write().unwrap(); + prop.tag = f.value(); + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); + image.redraw(); } - use_defaults = false; - } - } + true + }); - if use_defaults { - if let Some(cont) = &mut *container.borrow_mut() { - quote.set_value(""); - tag.set_value(""); - quote_position.set_value(cont.quote_position as f64); - tag_position.set_value(cont.tag_position as f64); - cont.apply_crop(); + let mut image = self.page.image.clone(); + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.quote_position.set_callback(move |f| { + let mut prop = properties.write().unwrap(); + prop.quote_position = f.value() as u32; + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); + image.redraw(); + }); - cont.rgba = [ - layer_red.value() as u8, - layer_green.value() as u8, - layer_blue.value() as u8, - layer_alpha.value() as u8, - ]; - } - } + let mut image = self.page.image.clone(); + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.tag_position.set_callback(move |f| { + let mut prop = properties.write().unwrap(); + prop.tag_position = f.value() as u32; + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); + image.redraw(); + }); - if let Some(cont) = &mut *container.borrow_mut() { - cont.apply_layer(); - let (width, height) = cont.image.dimensions(); - page.row_flex.set_size(&page.col_flex, width as i32); - page.col_flex.set_size(&page.image, height as i32); - page.row_flex.recalc(); - page.col_flex.recalc(); + let mut image = self.page.image.clone(); + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.layer_red.set_callback(move |f| { + let mut prop = properties.write().unwrap(); + prop.rgba[0] = f.value() as u8; + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); + image.redraw(); + }); + + let mut image = self.page.image.clone(); + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.layer_green.set_callback(move |f| { + let mut prop = properties.write().unwrap(); + prop.rgba[1] = f.value() as u8; + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); + image.redraw(); + }); + + let mut image = self.page.image.clone(); + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.layer_blue.set_callback(move |f| { + let mut prop = properties.write().unwrap(); + prop.rgba[2] = f.value() as u8; + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); + image.redraw(); + }); + + let mut image = self.page.image.clone(); + let properties = Arc::clone(&self.properties); + let sender = self.sender.clone(); + self.layer_alpha.set_callback(move |f| { + let mut prop = properties.write().unwrap(); + prop.rgba[3] = f.value() as u8; + sender.send(DrawMessage::Recalc).unwrap(); + sender.send(DrawMessage::Flush).unwrap(); + image.redraw(); + }); } } + +// fn load_image( +// file_choice: &mut menu::Choice, +// quote: &mut MultilineInput, +// tag: &mut Input, +// layer_red: &mut Spinner, +// layer_green: &mut Spinner, +// layer_blue: &mut Spinner, +// layer_alpha: &mut Spinner, +// quote_position: &mut Spinner, +// tag_position: &mut Spinner, +// page: &mut Page, +// properties: &Arc>, +// sender: &mpsc::Sender, +// ) { +// let file: String = match file_choice.choice() { +// Some(val) => val, +// None => return, +// }; + +// sender.send(DrawMessage::Open(file.clone())).unwrap(); + +// let file = Path::new(&file); +// let conf = file.with_extension("conf"); + +// let mut prop = properties.write().unwrap(); +// let mut use_defaults = true; +// if conf.exists() { +// let read = fs::read_to_string(&conf).unwrap(); +// if let Ok(saved_prop) = serde_json::from_str::(&read) { +// layer_red.set_value(saved_prop.rgba[0] as f64); +// layer_green.set_value(saved_prop.rgba[1] as f64); +// layer_blue.set_value(saved_prop.rgba[2] as f64); +// layer_alpha.set_value(saved_prop.rgba[3] as f64); +// quote.set_value(&saved_prop.quote); +// tag.set_value(&saved_prop.tag); +// quote_position.set_range(0.0, prop.original_dimension.1 as f64); +// quote_position.set_value(saved_prop.quote_position as f64); +// tag_position.set_range(0.0, prop.original_dimension.1 as f64); +// tag_position.set_value(saved_prop.tag_position as f64); + +// if let Some((x, y)) = saved_prop.crop_position { +// sender.send(DrawMessage::CropPos(x, y)).unwrap(); +// } + +// prop.quote = saved_prop.quote; +// prop.tag = saved_prop.tag; +// prop.quote_position = saved_prop.quote_position; +// prop.tag_position = saved_prop.quote_position; +// prop.rgba = saved_prop.rgba; +// use_defaults = false; +// } +// } + +// if use_defaults { +// quote.set_value(""); +// tag.set_value(""); + +// quote_position.set_range(0.0, prop.original_dimension.1 as f64); +// quote_position.set_value(prop.quote_position as f64); +// tag_position.set_range(0.0, prop.original_dimension.1 as f64); +// tag_position.set_value(prop.tag_position as f64); + +// sender.send(DrawMessage::Crop).unwrap(); + +// prop.rgba = [ +// layer_red.value() as u8, +// layer_green.value() as u8, +// layer_blue.value() as u8, +// layer_alpha.value() as u8, +// ]; +// } +// sender.send(DrawMessage::Recalc).unwrap(); +// println!("sent"); +// sender.send(DrawMessage::Flush).unwrap(); +// } diff --git a/src/properties.rs b/src/properties.rs index 081dddd..05cdcf7 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -1,11 +1,22 @@ -use serde::{Deserialize, Serialize}; +use lazy_static::lazy_static; +use rusttype::Font; +use std::io::Read; -#[derive(Serialize, Deserialize, Debug)] -pub(crate) struct Properties { - pub(crate) quote: String, - pub(crate) tag: String, - pub(crate) quote_position: u32, - pub(crate) tag_position: u32, - pub(crate) crop_position: (u32, u32), - pub(crate) rgba: [u8; 4], +lazy_static! { + pub static ref FONT_QUOTE: Font<'static> = { + let mut buffer = Vec::new(); + std::fs::File::open("ReenieBeanie-Regular.ttf") + .unwrap() + .read_to_end(&mut buffer) + .unwrap(); + rusttype::Font::try_from_vec(buffer).unwrap() + }; + pub static ref FONT_TAG: Font<'static> = { + let mut buffer = Vec::new(); + std::fs::File::open("Kalam-Regular.ttf") + .unwrap() + .read_to_end(&mut buffer) + .unwrap(); + rusttype::Font::try_from_vec(buffer).unwrap() + }; } diff --git a/src/utils.rs b/src/utils.rs index 552ef66..36bac38 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,135 @@ -use fltk::prelude::ImageExt; +use std::sync::{Arc, RwLock}; + use image::{DynamicImage, GenericImageView, ImageBuffer}; +use serde::{Deserialize, Serialize}; + +use crate::properties; #[derive(Debug)] pub(crate) struct ImageContainer { - pub(crate) image: DynamicImage, + pub(crate) image: DynamicImage, //plain + pub(crate) buffer: DynamicImage, //buffer to show + pub(crate) properties: Arc>, +} + +impl ImageContainer { + pub(crate) fn new(path: &str, properties: Arc>) -> Self { + let img = image::open(path).unwrap(); + let (width, height) = img.dimensions(); + let (s_width, s_height) = ((width * 500) / height, 500); + let img = img.resize(s_width, s_height, image::imageops::FilterType::Triangle); + + let mut prop = properties.write().unwrap(); + prop.path = path.to_owned(); + prop.dimension = (s_width, s_height); + prop.original_dimension = (width, height); + prop.quote_position = height / 2; + prop.tag_position = (height * 2) / 3; + + Self { + image: img.clone(), + buffer: img, + properties: Arc::clone(&properties), + } + } + + pub(crate) fn apply_crop(&mut self) { + let mut prop = self.properties.write().unwrap(); + let (original_width, original_height) = prop.original_dimension; + let (origina_crop_width, origina_crop_height) = get_4_5(original_width, original_height); + prop.crop_position = Some(( + original_width / 2 - origina_crop_width / 2, + original_height / 2 - origina_crop_height / 2, + )); + + let (s_width, s_height) = self.image.dimensions(); + let (c_width, c_height) = get_4_5(s_width, s_height); + let (cx, cy) = (s_width / 2 - c_width / 2, s_height / 2 - c_height / 2); + + prop.dimension = (c_width, c_height); + + self.image = self.image.crop(cx, cy, c_width, c_height); + self.buffer = self.image.clone(); + } + + pub(crate) fn apply_crop_pos(&mut self, original_x: u32, original_y: u32) { + let mut prop = self.properties.write().unwrap(); + let (original_width, original_height) = prop.original_dimension; + prop.crop_position = Some((original_x, original_y)); + + let (s_width, s_height) = self.image.dimensions(); + let (c_width, c_height) = get_4_5(s_width, s_height); + let (cx, cy) = ( + (original_x * s_width) / original_width, + (original_y * s_height) / original_height, + ); + + prop.dimension = (c_width, c_height); + + self.image = self.image.crop(cx, cy, c_width, c_height); + self.buffer = self.image.clone(); + } + + pub(crate) fn recalc(&mut self) { + let prop = self.properties.read().unwrap(); + let mut tmp = self.image.clone(); + let (width, height) = tmp.dimensions(); + + let layer = DynamicImage::ImageRgba8(ImageBuffer::from_fn(width, height, |_, _| { + image::Rgba(prop.rgba) + })); + image::imageops::overlay(&mut tmp, &layer, 0, 0); + + let size = quote_from_height(height); + for (index, line) in prop.quote.lines().enumerate() { + let (text_width, text_height) = measure_line( + &properties::FONT_QUOTE, + line, + rusttype::Scale::uniform(size as f32), + ); + + imageproc::drawing::draw_text_mut( + &mut tmp, + image::Rgba([255, 255, 255, 255]), + ((width as f32 - text_width) / 2.0) as u32, + (prop.quote_position * height) / prop.original_dimension.1 + + (text_height / 2.0) as u32 + + index as u32 * (text_height * 1.2) as u32, + rusttype::Scale::uniform(size as f32), + &properties::FONT_QUOTE, + line, + ); + } + + let size = tag_from_height(height); + for (index, line) in prop.tag.lines().enumerate() { + let (text_width, text_height) = measure_line( + &properties::FONT_TAG, + line, + rusttype::Scale::uniform(size as f32), + ); + + imageproc::drawing::draw_text_mut( + &mut tmp, + image::Rgba([255, 255, 255, 255]), + (width as f32 * 0.99 - text_width) as u32, + (prop.tag_position * height) / prop.original_dimension.1 + + (text_height / 2.0) as u32 + + index as u32 * (text_height * 1.2) as u32, + rusttype::Scale::uniform(size as f32), + &properties::FONT_TAG, + line, + ); + } + + self.buffer = tmp; + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) struct ImageProperties { + pub(crate) path: String, + pub(crate) dimension: (u32, u32), pub(crate) original_dimension: (u32, u32), pub(crate) crop_position: Option<(u32, u32)>, pub(crate) quote: String, @@ -14,62 +140,21 @@ pub(crate) struct ImageContainer { pub(crate) is_saved: bool, } -impl ImageContainer { - pub(crate) fn new(path: &str) -> Self { - let img = image::open(path).unwrap(); - let (width, height) = img.dimensions(); - let (s_width, s_height) = ((width * 500) / height, 500); - let mut img = img.resize(s_width, s_height, image::imageops::FilterType::Triangle); - +impl ImageProperties { + pub(crate) fn new() -> Self { Self { - image: img, - original_dimension: (width, height), + path: "".to_owned(), + dimension: (0, 0), + original_dimension: (0, 0), crop_position: None, - quote: String::new(), - tag: String::new(), - quote_position: (width * 2) / 3, - tag_position: width / 2, + quote: "".to_owned(), + tag: "".to_owned(), + quote_position: 0, + tag_position: 0, rgba: [0; 4], is_saved: true, } } - - pub(crate) fn apply_crop(&mut self) { - let (original_width, original_height) = self.original_dimension; - let (origina_crop_width, origina_crop_height) = get_4_5(original_width, original_height); - self.crop_position = Some(( - original_width / 2 - origina_crop_width / 2, - original_height / 2 - origina_crop_height / 2, - )); - - let (s_width, s_height) = self.image.dimensions(); - let (c_width, c_height) = get_4_5(s_width, s_height); - let (cx, cy) = (s_width / 2 - c_width / 2, s_height / 2 - c_height / 2); - - self.image = self.image.crop(cx, cy, c_width, c_height); - } - - pub(crate) fn apply_crop_pos(&mut self, original_x: u32, original_y: u32) { - let (original_width, original_height) = self.original_dimension; - self.crop_position = Some((original_x, original_y)); - - let (s_width, s_height) = self.image.dimensions(); - let (c_width, c_height) = get_4_5(s_width, s_height); - let (cx, cy) = ( - (original_x * s_width) / original_width, - (original_y * s_height) / original_height, - ); - - self.image = self.image.crop(cx, cy, c_width, c_height); - } - - pub(crate) fn apply_layer(&mut self) { - let (width, height) = self.image.dimensions(); - let layer = DynamicImage::ImageRgba8(ImageBuffer::from_fn(width, height, |_, _| { - image::Rgba(self.rgba) - })); - image::imageops::overlay(&mut self.image, &layer, 0, 0); - } } pub(crate) fn get_4_5(width: u32, height: u32) -> (u32, u32) { @@ -89,11 +174,11 @@ pub(crate) fn height_from_width(width: u32) -> u32 { } pub(crate) fn quote_from_height(height: u32) -> u32 { - (height * 65) / 1556 + (height * 70) / 1350 } -pub(crate) fn id_from_height(height: u32) -> u32 { - (height * 30) / 1556 +pub(crate) fn tag_from_height(height: u32) -> u32 { + (height * 50) / 1350 } pub(crate) fn measure_line(