Skip to main content

lutgen_studio/
lib.rs

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    /// Main app state
22    state: UiState,
23    /// Handle to background worker
24    worker: WorkerHandle,
25
26    inline_layout: bool,
27    scene_rect: egui::Rect,
28
29    /// Filter box for selecting palettes
30    palette_box: PaletteFilterBox,
31    palette_edit: PaletteEditor,
32    // File pickers
33    pub open_picker: FileDialog,
34    #[cfg(not(target_arch = "wasm32"))]
35    pub save_picker: FileDialog,
36    /// Lutgen icon
37    icon: egui::TextureHandle,
38}
39
40impl App {
41    pub fn new(cc: &eframe::CreationContext<'_>, input: Option<PathBuf>) -> Self {
42        // Load previous app state (if any).
43        let mut state = cc
44            .storage
45            .and_then(|storage| eframe::get_value::<UiState>(storage, eframe::APP_KEY))
46            .unwrap_or_default();
47
48        // Set current image if provided by cli
49        if let Some(path) = input {
50            state.current_image = Some(path);
51        }
52
53        // Manually load texture for lutgen icon
54        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        // Spawn background worker thread
66        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        // Optionally load current image and apply settings right away
83        #[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    /// Collect arguments and send apply request to the worker
93    pub fn apply(&mut self) {
94        // Show the spinner until we receive an edited image
95        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        // Handle any incoming events from the backend
127        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        // Show UI
151        self.show(ctx);
152    }
153}