想象一下,你是一名赛车手,正在赛道上风驰电掣。在 Rust 的世界里,你就是那辆赛车,而 Rust 的规则就像是赛道上的护栏和规则,确保你不会失控冲出赛道,撞到观众或者其他赛车。这些规则不仅保护了你的安全,也保护了其他赛车和观众的安全。
在编程的世界里,这种“安全”意味着内存安全和线程安全。Rust 通过它独特的借用检查器和所有权系统,确保了在多线程环境下,数据的访问和修改不会发生冲突,就像是赛车在赛道上各行其道,互不干扰。
曼德博集:数学与艺术的结晶
现在,让我们把话题转向一个既神秘又美丽的数学概念——曼德博集。你可以把它想象成一幅由无数点组成的画作,每个点都代表一个复数。这些复数就像是画布上的颜料,通过一种特殊的“绘画”规则,形成了一幅幅令人惊叹的图案。
开始绘制:Rust 项目的诞生
就像一位画家准备画布和颜料一样,我们首先创建一个新的 Rust 项目,这就像是为画家搭建了一个工作室。所有的创作——代码——都将在这个工作室里完成。
数学之旅:从简单到复杂
在开始绘制之前,我们先来了解一些基础的数学知识。想象一下,你手中有一个弹跳球,每次弹跳后都会落在一个数轴上。如果这个数小于 1 或者大于 1,球就会越弹越远,最终消失在无穷远处。但如果这个数恰好是 1,球就会停留在原地。
现在,如果我们给这个弹跳球增加一点复杂性,让它每次弹跳后不仅落在数轴上,还要加上一个特定的值。这时,球的弹跳轨迹就会变得更加难以预测,但仍然有一定的规律可循。
复数的魔法:从实数到复平面
接下来,我们把视野从一维的数轴扩展到二维的复平面。在这里,每个点不仅有 x 坐标,还有 y 坐标。通过在每次弹跳中加入一个复数,我们可以让弹跳球在复平面上移动,创造出更加复杂和美丽的图案。
曼德博集的定义:寻找不逃逸的点
曼德博集的核心思想是找出那些在复平面上,经过无数次弹跳后仍然不会逃逸到无穷远处的点。这些点就像是被某种神秘力量束缚在原地的精灵,形成了一幅幅独特的图案。
并行计算:多线程的协奏曲
在绘制曼德博集时,我们可以使用 Rust 的并发特性来加速这个过程。想象一下,你有一支由多个画家组成的团队,每个人都负责画布的一部分。通过分工合作,整幅画作很快就能完成。
代码实现:从理论到实践
use num::Complex;
use std::sync::Arc;
use std::thread;
// 尝试确定 `c` 是否位于曼德博集中,最多使用 `limit` 次迭代来判断
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
}
// 绘制曼德博集的函数
fn draw_mandelbrot(width: u32, height: u32, limit: usize) {
let image = Arc::new((0..height).map(|_| vec![0; (width + 7) / 8]).collect::<Vec<Vec<u8>>>());
// 创建线程池
let mut handles = vec![];
for y in 0..height {
let image_clone = Arc::clone(&image);
let handle = thread::spawn(move || {
let py = y as f64 / height as f64 * 2.0 - 1.0;
for x in 0..width {
let px = x as f64 / width as f64 * 2.0 - 1.0;
let c = Complex { re: px, im: py };
let it = escape_time(c, limit);
let byte_index = x / 8;
let bit_index = 7 - (x % 8);
let value = it.is_some() as u8 * 255;
image_clone[py as usize][byte_index] |= value << bit_index;
}
});
handles.push(handle);
}
// 等待所有线程完成
for handle in handles {
handle.join().unwrap();
}
// 将计算好的曼德博集数据转换为图像并保存
fn save_image(image: Arc<Vec<Vec<u8>>>, width: u32, height: u32, filename: &str) {
let mut img = ImageBuffer::new(width, height);
for (y, row) in image.iter().enumerate() {
for (x, &value) in row.iter().enumerate() {
let color = if value == 0 {
// 集合内的点用黑色表示
image::Rgb([0, 0, 0])
} else {
// 集合外的点用不同亮度的灰色表示
let brightness = (value as f32 * 255.0).sqrt() as u8;
image::Rgb([brightness, brightness, brightness])
};
img.put_pixel(x as u32, y as u32, color);
}
}
let _ = img.save_with_quality(filename, image::ColorType::RGB8, 80);
}
}
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 4 {
println!("Usage: mandelbrot <width> <height> <limit>");
return;
}
let width: u32 = args[1].parse().unwrap();
let height: u32 = args[2].parse().unwrap();
let limit: usize = args[3].parse().unwrap();
draw_mandelbrot(width, height, limit);
let filename = "mandelbrot.png";
save_image(Arc::new(image), width, height, filename);
println!("Saved {}", filename);
}
在这段代码中,我们定义了 save_image 函数,它接受之前计算的 image 数据、图像的宽度和高度,以及要保存的文件名。我们使用 ImageBuffer::new 创建一个新的图像缓冲区,然后遍历 image 数据,将每个像素点的颜色设置为黑色或不同亮度的灰色,取决于它是否属于曼德博集。最后,我们使用 img.save_with_quality 方法将图像以 PNG 格式保存到文件系统中,并指定了图像质量。
请确保在运行这段代码之前,你已经将 image crate 添加到项目的依赖项中,并且你的环境中已经安装了 Rust 编译器和 Cargo。当你运行这个程序时,它将在当前目录下生成一个名为 mandelbrot.png 的图像文件,其中包含了计算得到的曼德博集。