Initial commit

This commit is contained in:
mateoferon 2024-07-30 14:47:48 +02:00
commit dd639174f0
9 changed files with 652 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
*.png

416
Cargo.lock generated Normal file
View file

@ -0,0 +1,416 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anstream"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "colorchoice"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "fdeflate"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "image"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
dependencies = [
"bytemuck",
"byteorder-lite",
"num-traits",
"png",
"rayon",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "mandelbrot"
version = "0.1.0"
dependencies = [
"clap",
"image",
"num-complex",
"num-traits",
"rayon",
]
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
"simd-adler32",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "png"
version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "mandelbrot"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.11", features = ["derive"] }
image = { version = "0.25.2", default-features = false, features = [
"rayon",
"png",
] }
num-complex = "0.4.6"
num-traits = "0.2.19"
rayon = "1.10.0"

42
src/cli.rs Normal file
View file

@ -0,0 +1,42 @@
use clap::Parser;
use std::{path::PathBuf, str::FromStr};
/// Map MIDI signals to commands
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Cli {
/// Center point of the image
#[arg(long, short, default_value = "0:0")]
pub center: String,
/// Scale of the set on the X axis
#[arg(long, short, default_value_t = 4.0)]
pub scale: f64,
/// Max number of iterations
#[arg(long, short, default_value_t = 200)]
pub nmax: u32,
/// Image resolution
#[arg(long, short, default_value = "1000:1000")]
pub resolution: String,
/// Output filepath
#[arg(long, short, default_value = "output.png")]
pub output: PathBuf,
/// Use double precision
#[clap(long, action)]
pub f64: bool,
}
pub fn parse_two_nums<T>(input: &str) -> Result<(T, T), Box<dyn std::error::Error>>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error + 'static,
{
let col = input.find(":").unwrap();
let v1 = input[0..col].parse::<T>()?;
let v2 = input[col + 1..].parse::<T>()?;
Ok((v1, v2))
}

50
src/color.rs Normal file
View file

@ -0,0 +1,50 @@
use image::Rgb;
pub type Color = Rgb<u8>;
const COLOR_CYCLE: u32 = 120;
const N1: f32 = 1.0 * (COLOR_CYCLE as f32) / 6.0;
const N2: f32 = 2.0 * (COLOR_CYCLE as f32) / 6.0;
const N3: f32 = 3.0 * (COLOR_CYCLE as f32) / 6.0;
const N4: f32 = 4.0 * (COLOR_CYCLE as f32) / 6.0;
const N5: f32 = 5.0 * (COLOR_CYCLE as f32) / 6.0;
const N6: f32 = 6.0 * (COLOR_CYCLE as f32) / 6.0;
// const WHITE: Color = Rgb([u8::MAX, u8::MAX, u8::MAX]);
const BLACK: Color = Rgb([0, 0, 0]);
const RED: Color = Rgb([u8::MAX, 0, 0]);
const YELLOW: Color = Rgb([u8::MAX, u8::MAX, 0]);
const GREEN: Color = Rgb([0, u8::MAX, 0]);
const CYAN: Color = Rgb([0, u8::MAX, u8::MAX]);
const BLUE: Color = Rgb([0, 0, u8::MAX]);
const PURPLE: Color = Rgb([u8::MAX, 0, u8::MAX]);
fn gradient_fct(c1: Color, c2: Color, res: &mut Color, cross: f32) {
res.0[0] = (c1.0[0] as f32 * (1.0 - cross) + c2.0[0] as f32 * cross) as u8;
res.0[1] = (c1.0[1] as f32 * (1.0 - cross) + c2.0[1] as f32 * cross) as u8;
res.0[2] = (c1.0[2] as f32 * (1.0 - cross) + c2.0[2] as f32 * cross) as u8;
}
pub fn pixel_color_fct(n: u32, res: &mut Color) {
if n == 0 {
*res = BLACK;
} else {
let n: f32 = (n % COLOR_CYCLE) as f32;
if n < N1 {
gradient_fct(BLUE, CYAN, res, n / N1);
} else if n < N2 {
gradient_fct(CYAN, GREEN, res, (n - N1) / (N2 - N1));
} else if n < N3 {
gradient_fct(GREEN, YELLOW, res, (n - N2) / (N3 - N2));
} else if n < N4 {
gradient_fct(YELLOW, RED, res, (n - N3) / (N4 - N3));
} else if n < N5 {
gradient_fct(RED, PURPLE, res, (n - N4) / (N5 - N4));
} else if n < N6 {
gradient_fct(PURPLE, BLUE, res, (n - N5) / (N6 - N5));
} else {
*res = BLACK;
}
}
}

18
src/compute.rs Normal file
View file

@ -0,0 +1,18 @@
use num_complex::Complex;
pub fn mandelbrot_n<T>(c: Complex<T>, nmax: u32) -> u32
where
T: num_traits::Float + num_traits::FromPrimitive + 'static,
{
let mut ci = c;
let mut i = 1;
let limit: T = T::from(4.0).unwrap();
while i <= nmax {
if ci.re * ci.re + ci.im * ci.im > limit {
return i;
}
ci = ci * ci + c;
i += 1;
}
return 0;
}

48
src/main.rs Normal file
View file

@ -0,0 +1,48 @@
pub mod cli;
pub mod color;
pub mod compute;
pub mod viewport;
use std::str::FromStr;
use image::{ImageBuffer, Rgb};
use num_traits::{Float, FromPrimitive};
use viewport::Viewport;
use rayon::prelude::*;
use clap::Parser;
fn main() {
let args = cli::Cli::parse();
let nmax = args.nmax;
let imgbuf = if args.f64 {
let viewport: Viewport<f64> = Viewport::from(&args);
compute(&viewport, nmax)
} else {
let viewport: Viewport<f32> = Viewport::from(&args);
compute(&viewport, nmax)
};
imgbuf.save(args.output).unwrap();
}
fn compute<T>(viewport: &Viewport<T>, nmax: u32) -> ImageBuffer<Rgb<u8>, Vec<u8>>
where
T: Float + FromStr + FromPrimitive + Sync + 'static,
<T as FromStr>::Err: std::error::Error + 'static,
{
let mut imgbuf = ImageBuffer::new(viewport.x(), viewport.y());
// Iterate over the coordinates and pixels of the image
imgbuf
.par_enumerate_pixels_mut()
.map(|(x, y, pixel)| {
let c = viewport.pixel_to_complex(x, y);
let n = compute::mandelbrot_n(c, nmax);
color::pixel_color_fct(n, pixel);
})
.count();
imgbuf
}

0
src/type.rs Normal file
View file

62
src/viewport.rs Normal file
View file

@ -0,0 +1,62 @@
use std::str::FromStr;
use num_complex::Complex;
use num_traits::Float;
use crate::cli::{parse_two_nums, Cli};
pub struct Viewport<T: Float> {
x: u32,
y: u32,
center: Complex<T>,
scale: T,
scaler: T,
}
impl<T: Float> Viewport<T> {
pub fn new(x: u32, y: u32, center: Complex<T>, scale: T) -> Self {
Self {
x,
y,
center,
scale,
scaler: scale / T::from(x).unwrap(),
}
}
pub fn x(&self) -> u32 {
self.x
}
pub fn y(&self) -> u32 {
self.y
}
pub fn center(&self) -> Complex<T> {
self.center
}
pub fn scale(&self) -> T {
self.scale
}
pub fn scaler(&self) -> T {
self.scaler
}
pub fn pixel_to_complex(&self, x: u32, y: u32) -> Complex<T> {
let cx = T::from(x as i32 - self.x as i32 / 2).unwrap() * self.scaler;
let cy = T::from(y as i32 - self.y as i32 / 2).unwrap() * self.scaler;
Complex::new(cx, cy) + self.center
}
}
impl<T> From<&Cli> for Viewport<T>
where
T: Float + FromStr,
<T as FromStr>::Err: std::error::Error + 'static,
{
fn from(args: &Cli) -> Self {
let (imgx, imgy): (u32, u32) = parse_two_nums(&args.resolution).unwrap();
let scale: T = T::from(args.scale).unwrap();
let (center_x, center_y): (T, T) = parse_two_nums(&args.center).unwrap();
let center = Complex::new(center_x, center_y);
Self::new(imgx, imgy, center, scale)
}
}