分形與 Mandelbrot
Mandelbrot 集 (Mandelbrot Set) 是復數平面上一個點的集合,以數學家 Beno?t Mandelbrot 的名字命名。它是最著名的分形之一。一個復數 c 是否屬于 Mandelbrot 集,取決于一個簡單的迭代過程:
z n + 1 = z n 2 + c z_{n+1}=z_{n}^2+c zn+1?=zn2?+c
如果這個序列 z0?,z1?,z2?,… 的大小(模)保持在一定范圍內(具體來說,不超過 2),那么我們就說復數 c 屬于 Mandelbrot 集。如果序列的大小趨向于無窮大,那么 c 就不屬于這個集合。
當我們為復數平面上的每個點 c 運行這個迭代過程,并根據它“逃逸”到無窮大的速度(或者它是否保持有界)給它上色時,我們就會得到 Mandelbrot 集那標志性的、無限復雜的圖像。
那些“逃逸”得慢的點或者不逃逸的點,通常被涂成黑色,而那些“逃逸”得快的點,則根據它們的逃逸速度被賦予不同的顏色,從而形成了圖像中絢麗多彩的部分。
項目準備
創建一個 Rust 項目:
cargo new mandelbrot
程序需要以下依賴:
[dependencies]
clap = { version = "4.x", features = ["derive"] }
image = "0.25.1"
num = "0.4.3"
計算 Mandelbrot 迭代
計算相對而言是比較簡單的,我們需要引入 Rust 的一個數值類型庫 num
,從而支持復數類型。
如果對復數不熟悉,也沒關系,就把實部想象成笛卡爾坐標系的 x 軸(橫向),虛部想象成 y 軸(只不過以
i
為單位)。
use num::Complex;fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> {let mut z = Complex { re: 0.0, im: 0.0 };for i in 0..limit {if z.norm_sqr() > 4.0 {return Some(i);}z = z * z + c;}None
}
程序上非常簡單,就是持續迭代計算 z * z + c
,如果 limit
次迭代里它沒有越界,我們就認為它屬于 Mandelbrot 集合。如果超過了,就返回迭代的次數(即逃逸時間);如果在達到上限之前都沒有超過,就返回 None
,表示我們認為這個點可能屬于 Mandelbrot 集。
像素到點的映射
我們需要一種方法來將圖像中的每個像素(如 (25, 175))映射到復數平面上的一個點(如 -0.5 - 0.75i)。
fn pixel_to_point(bounds: (usize, usize), // 圖像尺寸 (寬, 高)pixel: (usize, usize), // 像素坐標 (列, 行)upper_left: Complex<f64>, // 左上角對應的復數lower_right: Complex<f64>, // 右下角對應的復數
) -> Complex<f64> {let (width, height) = (lower_right.re - upper_left.re, // 復數平面的寬度upper_left.im - lower_right.im, // 復數平面的高度);Complex {re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64,im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64,}
}
由于數學上的坐標系和計算機通常的屏幕坐標系的 y 軸方向是相反。計算機圖像通常左上角是 (0, 0),y 軸向下增長,復平面通常 y 軸(虛部)向上增長。因此,在計算虛部 im
時,我們是從 upper_left.im
減去。
渲染圖片
要繪制 Mandelbrot 集,只需要將 escape_time
用在復平面上的點,根據逃逸時間賦以不同的灰度值。
fn render(pixels: &mut [u8],bounds: (usize, usize),upper_left: Complex<f64>,lower_right: Complex<f64>,
) {assert!(pixels.len() == bounds.0 * bounds.1);for row in 0..bounds.1 {for column in 0..bounds.0 {let point = pixel_to_point(bounds, (column, row), upper_left, lower_right);pixels[row * bounds.0 + column] = match escape_time(point, 255) {None => 0, // 屬于 Mandelbrot 集,設為黑色 (0)Some(count) => 255 - count as u8, // 不屬于,顏色與逃逸時間相關}}}
}
如果沒有逃逸,則是黑色 0
,否則就根據逃逸時間賦值 255 - count
。
到這里,我們就完成了核心的程序。下面的部分屬于渲染和保存。
寫入圖片
use std::fs::File;
use image::{ExtendedColorType, ImageEncoder