diff --git a/.gitignore b/.gitignore index 322ad03..e02e30c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target *.png !images/*.png +fractal_settings.json diff --git a/Cargo.lock b/Cargo.lock index def79cb..1cd74db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1183,6 +1183,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + [[package]] name = "jni" version = "0.21.1" @@ -1230,6 +1236,8 @@ dependencies = [ "eframe", "image", "native-dialog", + "serde", + "serde_json", ] [[package]] @@ -1866,6 +1874,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ryu" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" + [[package]] name = "same-file" version = "1.0.6" @@ -1920,6 +1934,17 @@ dependencies = [ "syn 2.0.25", ] +[[package]] +name = "serde_json" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.14" diff --git a/Cargo.toml b/Cargo.toml index dff42c9..7aefa21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" eframe = "0.22.0" image = { version = "0.24.6", default_features = false, features = ["png"] } native-dialog = "0.6.4" +serde = "1.0.171" +serde_json = "1.0.102" # Enable high optimizations for dependencies [profile.dev.package."*"] diff --git a/src/generate.rs b/src/generate.rs index 67c8943..537d91a 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1,6 +1,7 @@ use image::{Rgb, RgbImage}; +use serde::{Deserialize, Serialize}; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct RenderOptions { pub width: u32, pub height: u32, @@ -11,12 +12,26 @@ pub struct RenderOptions { pub fill_style: FillStyle, } -#[derive(PartialEq, Clone)] +#[derive(Clone, PartialEq, Serialize, Deserialize)] pub enum FillStyle { Bright, Black, } +impl Default for RenderOptions { + fn default() -> Self { + Self { + width: 512, + height: 512, + unit_width: 4.0, + max_iterations: 128, + cx: -0.8, + cy: -0.27, + fill_style: FillStyle::Bright, + } + } +} + pub fn render(q: &RenderOptions, color: (u8, u8, u8)) -> RgbImage { let mut img = RgbImage::new(q.width, q.height); diff --git a/src/main.rs b/src/main.rs index bd65818..103fd41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,10 @@ #![windows_subsystem = "windows"] -use std::{env, time::SystemTime}; +use std::{ + env, + fs::{self, File}, + io::Write, + time::SystemTime, +}; use eframe::{ egui::{self, DragValue, RichText, Slider, TextureOptions}, @@ -9,9 +14,12 @@ use eframe::{ use generate::{render, FillStyle, RenderOptions}; use image::EncodableLayout; use native_dialog::FileDialog; +use serde::{Deserialize, Serialize}; mod generate; +const SETTINGS_FILE: &str = "fractal_settings.json"; + fn main() { let native_options = NativeOptions { initial_window_size: Some(Vec2::new(1280.0, 720.0)), @@ -26,58 +34,77 @@ fn main() { .unwrap(); } +#[derive(Serialize, Deserialize)] struct JuliaGUI { color: (u8, u8, u8), - preview: TextureHandle, + #[serde(skip)] + preview: Option, render_options: RenderOptions, + #[serde(skip)] preview_render_ms: f64, + #[serde(skip)] export_render_ms: f64, export_res_multiplier: u32, export_iterations: u32, export_name: String, + #[serde(skip)] settings_changed: bool, } +impl Default for JuliaGUI { + fn default() -> Self { + Self { + color: (12, 5, 10), + preview: None, + render_options: RenderOptions::default(), + preview_render_ms: 0.0, + export_render_ms: f64::NAN, + export_res_multiplier: 8, + export_iterations: 512, + export_name: String::from("julia_fractal.png"), + settings_changed: true, + } + } +} + impl JuliaGUI { fn new(cc: &eframe::CreationContext<'_>) -> Self { + let mut n: Self = fs::read_to_string(SETTINGS_FILE) + .map(|s| serde_json::from_str(&s).ok()) + .ok() + .flatten() + .unwrap_or_default(); + let preview = cc.egui_ctx.load_texture( "preview_image", egui::ColorImage::from_rgb([1, 1], &[0, 0, 0]), TextureOptions::default(), ); - let preview_quality = RenderOptions { - width: 512, - height: 512, - unit_width: 4.0, - max_iterations: 128, - cx: -0.981, - cy: -0.277, - fill_style: FillStyle::Bright, - }; - Self { - color: (12, 5, 10), - preview, - render_options: preview_quality, - preview_render_ms: 0.0, - export_render_ms: f64::NAN, - export_res_multiplier: 8, - export_iterations: 512, - export_name: String::from("julia_set.png"), - settings_changed: true, - } + n.preview = Some(preview); + n.settings_changed = true; + n + } + + fn save_settings(&self) { + let settings = serde_json::to_string_pretty(&self).unwrap(); + let mut file = File::create(SETTINGS_FILE).unwrap(); + file.write_all(settings.as_bytes()).unwrap(); } fn update_preview(&mut self) { let start_time = SystemTime::now(); - let preview = render(&self.render_options, self.color); - self.preview.set( - egui::ColorImage::from_rgb( - [preview.width() as usize, preview.height() as usize], - preview.as_bytes(), - ), - TextureOptions::default(), - ); + let frame = render(&self.render_options, self.color); + + if let Some(preview) = &mut self.preview { + preview.set( + egui::ColorImage::from_rgb( + [frame.width() as usize, frame.height() as usize], + frame.as_bytes(), + ), + TextureOptions::default(), + ); + } self.preview_render_ms = start_time.elapsed().unwrap().as_micros() as f64 / 1000.0; } @@ -94,6 +121,7 @@ impl JuliaGUI { println!("Error exporting render: {err}"); } self.export_render_ms = start_time.elapsed().unwrap().as_micros() as f64 / 1000.0; + self.save_settings(); } } @@ -101,6 +129,7 @@ impl eframe::App for JuliaGUI { fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) { if self.settings_changed { self.update_preview(); + self.save_settings(); self.settings_changed = false; } @@ -152,11 +181,15 @@ impl eframe::App for JuliaGUI { ui.label("Preview resolution:"); ui.horizontal(|ui| { let set_width = ui.add( - DragValue::new(&mut self.render_options.width).clamp_range(128..=16384), + DragValue::new(&mut self.render_options.width) + .clamp_range(128..=4096) + .suffix("px"), ); ui.label("x"); let set_height = ui.add( - DragValue::new(&mut self.render_options.height).clamp_range(128..=16384), + DragValue::new(&mut self.render_options.height) + .clamp_range(128..=4096) + .suffix("px"), ); if set_width.changed() || set_height.changed() { self.settings_changed = true; @@ -207,9 +240,13 @@ impl eframe::App for JuliaGUI { }); egui::CentralPanel::default().show(ctx, |ui| { - ui.image(&self.preview, self.preview.size_vec2()); + if let Some(texture) = &self.preview { + ui.image(texture, texture.size_vec2()); + } }); } - fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {} + fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) { + self.save_settings() + } }