1#![warn(clippy::all)]
2
3use std::path::PathBuf;
4
5use crate::file_picker::FileDialog;
6use crate::state::{LutAlgorithm, UiState};
7use crate::ui::left::{PaletteEditor, PaletteFilterBox};
8pub use crate::worker::Worker;
9use crate::worker::{LutAlgorithmArgs, WorkerHandle};
10
11mod color;
12mod file_picker;
13mod palette;
14mod state;
15mod ui;
16mod updates;
17mod utils;
18mod worker;
19
20pub struct App {
21 state: UiState,
23 worker: WorkerHandle,
25
26 inline_layout: bool,
27 scene_rect: egui::Rect,
28
29 palette_box: PaletteFilterBox,
31 palette_edit: PaletteEditor,
32 pub open_picker: FileDialog,
34 #[cfg(not(target_arch = "wasm32"))]
35 pub save_picker: FileDialog,
36 icon: egui::TextureHandle,
38}
39
40impl App {
41 pub fn new(cc: &eframe::CreationContext<'_>, input: Option<PathBuf>) -> Self {
42 let mut state = cc
44 .storage
45 .and_then(|storage| eframe::get_value::<UiState>(storage, eframe::APP_KEY))
46 .unwrap_or_default();
47
48 if let Some(path) = input {
50 state.current_image = Some(path);
51 }
52
53 let image_bytes = include_bytes!("../assets/lutgen.png");
55 let image_buf = image::load_from_memory(image_bytes)
56 .expect("failed to load lutgen icon")
57 .to_rgba8();
58 let dim = image_buf.dimensions();
59 let icon = cc.egui_ctx.load_texture(
60 "lutgen.png",
61 egui::ColorImage::from_rgba_unmultiplied([dim.0 as usize, dim.1 as usize], &image_buf),
62 egui::TextureOptions::default(),
63 );
64
65 let worker = WorkerHandle::spawn(cc.egui_ctx.clone());
67
68 #[allow(unused_mut)]
69 let mut this = Self {
70 palette_box: PaletteFilterBox::new(&state.palette_selection),
71 palette_edit: PaletteEditor::new(&state.palette_selection),
72 inline_layout: false,
73 scene_rect: egui::Rect::NOTHING,
74 open_picker: FileDialog::pick(cc.egui_ctx.clone()),
75 #[cfg(not(target_arch = "wasm32"))]
76 save_picker: FileDialog::save(cc.egui_ctx.clone()),
77 state,
78 icon,
79 worker,
80 };
81
82 #[cfg(not(target_arch = "wasm32"))]
84 if let Some(path) = this.state.current_image.clone() {
85 this.worker.load_file(path);
86 this.apply();
87 }
88
89 this
90 }
91
92 pub fn apply(&mut self) {
94 self.state.processing = true;
96 let args = match self.state.current_alg {
97 LutAlgorithm::GaussianRbf => LutAlgorithmArgs::GaussianRbf {
98 rbf: self.state.common_rbf,
99 args: self.state.guassian_rbf,
100 },
101 LutAlgorithm::ShepardsMethod => LutAlgorithmArgs::ShepardsMethod {
102 rbf: self.state.common_rbf,
103 args: self.state.shepards_method,
104 },
105 LutAlgorithm::GaussianSampling => LutAlgorithmArgs::GaussianSampling {
106 args: self.state.guassian_sampling,
107 },
108 LutAlgorithm::GaussianBlur => LutAlgorithmArgs::GaussianBlur {
109 args: self.state.gaussian_blur,
110 },
111 LutAlgorithm::NearestNeighbor => LutAlgorithmArgs::NearestNeighbor,
112 };
113 self.worker
114 .apply_palette(self.state.palette.clone(), self.state.common, args);
115 }
116}
117
118impl eframe::App for App {
119 fn save(&mut self, storage: &mut dyn eframe::Storage) {
120 eframe::set_value(storage, eframe::APP_KEY, &self.state)
121 }
122
123 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
124 self.inline_layout = ctx.available_rect().width() < 600.;
125
126 if let Some(event) = self.worker.poll_event() {
128 self.state.handle_event(ctx, event);
129 }
130
131 #[cfg(not(target_arch = "wasm32"))]
132 if let Some(path) = self.open_picker.poll() {
133 self.worker.load_file(path.clone());
134 self.state.current_image = Some(path);
135 self.state.processing = true;
136 }
137
138 #[cfg(not(target_arch = "wasm32"))]
139 if let Some(item) = self.save_picker.poll() {
140 self.worker.save_as(item);
141 }
142
143 #[cfg(target_arch = "wasm32")]
144 if let Some((path, bytes)) = self.open_picker.poll() {
145 self.worker.load_file(path.clone(), bytes);
146 self.state.current_image = Some(path);
147 self.apply();
148 }
149
150 self.show(ctx);
152 }
153}