mirror of
https://github.com/CrispyPin/julia-fractal-renderer.git
synced 2024-11-24 19:10:27 +01:00
create gui
This commit is contained in:
parent
70373c8ff7
commit
7c7d56e868
4 changed files with 2851 additions and 53 deletions
2628
Cargo.lock
generated
2628
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
eframe = "0.22.0"
|
||||||
image = { version = "0.24.6", default_features = false, features = ["png"] }
|
image = { version = "0.24.6", default_features = false, features = ["png"] }
|
||||||
|
|
||||||
# Enable high optimizations for dependencies
|
# Enable high optimizations for dependencies
|
||||||
|
|
61
src/generate.rs
Normal file
61
src/generate.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use image::{Rgb, RgbImage};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RenderOptions {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub unit_width: f64,
|
||||||
|
pub max_iterations: u32,
|
||||||
|
pub cx: f64,
|
||||||
|
pub cy: f64,
|
||||||
|
pub fill_style: FillStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone)]
|
||||||
|
pub enum FillStyle {
|
||||||
|
Bright,
|
||||||
|
Black,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(q: &RenderOptions, color: (u8, u8, u8)) -> RgbImage {
|
||||||
|
let mut img = RgbImage::new(q.width, q.height);
|
||||||
|
|
||||||
|
let width = q.width as f64;
|
||||||
|
let height = q.height as f64;
|
||||||
|
let ppu = width / q.unit_width;
|
||||||
|
|
||||||
|
for y in 0..q.height {
|
||||||
|
for x in 0..q.width {
|
||||||
|
let pixel = {
|
||||||
|
let x = (x as f64 - width / 2.0) / ppu;
|
||||||
|
let y = (y as f64 - height / 2.0) / ppu;
|
||||||
|
|
||||||
|
let iter = julia(x, y, q.cx, q.cy, q.max_iterations);
|
||||||
|
if q.fill_style == FillStyle::Black && iter == q.max_iterations {
|
||||||
|
Rgb([0, 0, 0])
|
||||||
|
} else {
|
||||||
|
let i = iter.min(255) as u8;
|
||||||
|
Rgb([
|
||||||
|
i.saturating_mul(color.0),
|
||||||
|
i.saturating_mul(color.1),
|
||||||
|
i.saturating_mul(color.2),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
img.put_pixel(x, y, pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img
|
||||||
|
}
|
||||||
|
|
||||||
|
fn julia(mut x: f64, mut y: f64, cx: f64, cy: f64, max_iter: u32) -> u32 {
|
||||||
|
let mut iter = 0;
|
||||||
|
while (x * x + y * y) < 4.0 && iter < max_iter {
|
||||||
|
(x, y) = (
|
||||||
|
x * x - y * y + cx, //
|
||||||
|
2.0 * x * y + cy,
|
||||||
|
);
|
||||||
|
iter += 1;
|
||||||
|
}
|
||||||
|
return iter;
|
||||||
|
}
|
212
src/main.rs
212
src/main.rs
|
@ -1,68 +1,176 @@
|
||||||
|
#![windows_subsystem = "windows"]
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use image::{Rgb, RgbImage};
|
use eframe::{
|
||||||
|
egui::{self, RichText, Slider},
|
||||||
|
epaint::{TextureHandle, Vec2},
|
||||||
|
Frame, NativeOptions,
|
||||||
|
};
|
||||||
|
use generate::{render, FillStyle, RenderOptions};
|
||||||
|
use image::EncodableLayout;
|
||||||
|
|
||||||
const WIDTH: u32 = 1920 * 2;
|
mod generate;
|
||||||
const HEIGHT: u32 = 1080 * 2;
|
|
||||||
|
|
||||||
const TOTAL_UNITS_WIDE: f64 = 4.0;
|
|
||||||
|
|
||||||
const MAX_ITER: u32 = 512;
|
|
||||||
const CX: f64 = -0.981;
|
|
||||||
const CY: f64 = -0.277;
|
|
||||||
|
|
||||||
// const COL_R: u8 = 4;
|
|
||||||
// const COL_G: u8 = 8;
|
|
||||||
// const COL_B: u8 = 12;
|
|
||||||
const COL_R: u8 = 12;
|
|
||||||
const COL_G: u8 = 5;
|
|
||||||
const COL_B: u8 = 10;
|
|
||||||
|
|
||||||
const WIDTH_F: f64 = WIDTH as f64;
|
|
||||||
const HEIGHT_F: f64 = HEIGHT as f64;
|
|
||||||
const PIXELS_PER_UNIT: f64 = WIDTH_F / TOTAL_UNITS_WIDE;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let native_options = NativeOptions {
|
||||||
|
initial_window_size: Some(Vec2::new(1280.0, 720.0)),
|
||||||
|
..NativeOptions::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
eframe::run_native(
|
||||||
|
"Julia fractal render GUI",
|
||||||
|
native_options,
|
||||||
|
Box::new(|cc| Box::new(JuliaGUI::new(cc))),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JuliaGUI {
|
||||||
|
color: (u8, u8, u8),
|
||||||
|
preview: TextureHandle,
|
||||||
|
render_options: RenderOptions,
|
||||||
|
preview_render_ms: f64,
|
||||||
|
export_render_ms: f64,
|
||||||
|
export_res_multiplier: u32,
|
||||||
|
export_iterations: u32,
|
||||||
|
export_name: String,
|
||||||
|
settings_changed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JuliaGUI {
|
||||||
|
fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
|
let preview = cc.egui_ctx.load_texture(
|
||||||
|
"my-image",
|
||||||
|
egui::ColorImage::from_rgb([1, 1], &[0, 0, 0]),
|
||||||
|
Default::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: 4,
|
||||||
|
export_iterations: 512,
|
||||||
|
export_name: String::from("julia_set.png"),
|
||||||
|
settings_changed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_preview(&mut self) {
|
||||||
let start_time = SystemTime::now();
|
let start_time = SystemTime::now();
|
||||||
|
let preview = render(&self.render_options, self.color);
|
||||||
let mut img = RgbImage::new(WIDTH, HEIGHT);
|
self.preview.set(
|
||||||
for y in 0..HEIGHT {
|
egui::ColorImage::from_rgb(
|
||||||
for x in 0..WIDTH {
|
[preview.width() as usize, preview.height() as usize],
|
||||||
let pixel = fractal(x as f64, y as f64);
|
preview.as_bytes(),
|
||||||
img.put_pixel(x, y, pixel);
|
),
|
||||||
}
|
Default::default(),
|
||||||
}
|
|
||||||
println!(
|
|
||||||
"Generating took {} ms",
|
|
||||||
start_time.elapsed().unwrap().as_millis()
|
|
||||||
);
|
);
|
||||||
|
self.preview_render_ms = start_time.elapsed().unwrap().as_micros() as f64 / 1000.0;
|
||||||
let filename = format!("julia_set_cx{}_cy{}.png", CX, CY);
|
|
||||||
img.save(filename).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fractal(x: f64, y: f64) -> Rgb<u8> {
|
fn export_render(&mut self) {
|
||||||
let mut x = (x - WIDTH_F / 2.0) / PIXELS_PER_UNIT;
|
let start_time = SystemTime::now();
|
||||||
let mut y = (y - HEIGHT_F / 2.0) / PIXELS_PER_UNIT;
|
let settings = RenderOptions {
|
||||||
|
width: self.render_options.width * self.export_res_multiplier,
|
||||||
|
height: self.render_options.height * self.export_res_multiplier,
|
||||||
|
max_iterations: self.export_iterations,
|
||||||
|
..self.render_options.clone()
|
||||||
|
};
|
||||||
|
let image = render(&settings, self.color);
|
||||||
|
if let Err(e) = image.save(&self.export_name) {
|
||||||
|
println!("Error exporting render: {e}");
|
||||||
|
}
|
||||||
|
self.export_render_ms = start_time.elapsed().unwrap().as_micros() as f64 / 1000.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut iterations = 0;
|
impl eframe::App for JuliaGUI {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) {
|
||||||
|
if self.settings_changed {
|
||||||
|
self.update_preview();
|
||||||
|
self.settings_changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
while (x * x + y * y) < 4.0 {
|
egui::SidePanel::left("main_left_panel")
|
||||||
(x, y) = (
|
.resizable(false)
|
||||||
x * x - y * y + CX, //
|
.exact_width(200.0)
|
||||||
2.0 * x * y + CY,
|
.show(ctx, |ui| {
|
||||||
|
if ui.button("Update preview").clicked() {
|
||||||
|
self.settings_changed = true;
|
||||||
|
}
|
||||||
|
ui.label(format!(
|
||||||
|
"last preview render took {:.2}ms",
|
||||||
|
self.preview_render_ms
|
||||||
|
));
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.button(format!("Render to '{}'", &self.export_name))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.export_render();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.label(format!(
|
||||||
|
"last exported render took {:.2}ms",
|
||||||
|
self.export_render_ms
|
||||||
|
));
|
||||||
|
|
||||||
|
ui.label("CX:");
|
||||||
|
let set_cx = ui.add(Slider::new(&mut self.render_options.cx, -2.0..=2.0));
|
||||||
|
ui.label("CY:");
|
||||||
|
let set_cy = ui.add(Slider::new(&mut self.render_options.cy, -2.0..=2.0));
|
||||||
|
ui.label("Image width in space units:");
|
||||||
|
let set_unit_width =
|
||||||
|
ui.add(Slider::new(&mut self.render_options.unit_width, 0.01..=6.0));
|
||||||
|
ui.label("Fill style:");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let set_black = ui.radio_value(
|
||||||
|
&mut self.render_options.fill_style,
|
||||||
|
FillStyle::Black,
|
||||||
|
"Black",
|
||||||
);
|
);
|
||||||
|
let set_bright = ui.radio_value(
|
||||||
iterations += 1;
|
&mut self.render_options.fill_style,
|
||||||
if iterations == MAX_ITER {
|
FillStyle::Bright,
|
||||||
return Rgb([0, 0, 0]);
|
"Bright",
|
||||||
|
);
|
||||||
|
if set_bright.changed() || set_black.changed() {
|
||||||
|
self.settings_changed = true;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.label(RichText::new("Quality settings").heading());
|
||||||
|
ui.label("iterations:");
|
||||||
|
let set_iter = ui.add(
|
||||||
|
Slider::new(&mut self.render_options.max_iterations, 5..=256)
|
||||||
|
.clamp_to_range(false),
|
||||||
|
);
|
||||||
|
//todo resolution
|
||||||
|
|
||||||
|
if set_cx.changed()
|
||||||
|
|| set_cy.changed() || set_unit_width.changed()
|
||||||
|
|| set_iter.changed()
|
||||||
|
{
|
||||||
|
self.settings_changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.image(&self.preview, self.preview.size_vec2());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let i = iterations.min(255) as u8;
|
fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {}
|
||||||
Rgb([
|
|
||||||
i.saturating_mul(COL_R),
|
|
||||||
i.saturating_mul(COL_G),
|
|
||||||
i.saturating_mul(COL_B),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue