//! # Sierpinski Gasket //! //! The Sierpinski Gasket is a simple iterated function system (IFS) that demonstrates //! the basic principles of using the "chaos game" to generate images. Using a set of three //! transforms, generate new points in the function system using the chaos game, //! then plot those points and display the resulting image. use anyhow::{Context, Result}; use enkou_shaders::Coefficients2; use enkou_shaders::camera::Camera; use enkou_shaders::chaos_game::ChaosGame; use enkou_shaders::transform::Transform; use enkou_shaders::variation::Variation; use glam::{Affine2, UVec2, Vec2, uvec2}; use image::{GrayImage, Luma}; use rand::SeedableRng; use rand_xoshiro::Xoshiro256StarStar; use std::mem; use std::process::Command; use tempfile::NamedTempFile; const ITERATIONS_DISCARD: usize = 20; const IMAGE_DIMENSION: UVec2 = uvec2(600, 600); const IMAGE_QUALITY: f32 = 1.0; /// Build and display a simple fractal - the Sierpinski Gasket pub fn main() -> Result<()> { let mut rng = Xoshiro256StarStar::from_seed([4u8; 32]); let transforms = [ { // F_0: (x / 2, y / 2) let coefficients = Affine2::from_coefficients(0.5, 0.0, 0.0, 0.0, 0.5, 0.0); Transform::new(coefficients, Affine2::IDENTITY, uvec2(0, 1), 0.0, 0.5) }, { // F_1: ((x + 1) / 2, y / 2) let coefficients = Affine2::from_coefficients(0.5, 0.0, 0.5, 0.0, 0.5, 0.0); Transform::new(coefficients, Affine2::IDENTITY, uvec2(0, 1), 0.5, 0.5) }, { // F_2: (x / 2, (y + 1) / 2) let coefficients = Affine2::from_coefficients(0.5, 0.0, 0.0, 0.0, 0.5, 0.5); Transform::new(coefficients, Affine2::IDENTITY, uvec2(0, 1), 1.0, 0.5) }, ]; let weights = [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0]; let variations = [Variation::IDENTITY]; // The gasket is defined on the range [0, 1] for both X and Y let camera = Camera::new( IMAGE_DIMENSION, Vec2::ONE * 0.5, 0.0, Vec2::ZERO, IMAGE_DIMENSION.as_vec2(), ); let mut image = GrayImage::new(IMAGE_DIMENSION.x, IMAGE_DIMENSION.y); let chaos_game = ChaosGame::new(&mut rng, &transforms, &weights, &variations); let iterations = (IMAGE_DIMENSION.element_product() as f32 * IMAGE_QUALITY) as usize; chaos_game .skip(ITERATIONS_DISCARD) .take(iterations) .for_each(|(ifs_point, _)| { if let Some(pixel_point) = camera.transform_point_to_image(ifs_point) { image.put_pixel(pixel_point.x, pixel_point.y, Luma([255u8])); } }); let temp = NamedTempFile::with_suffix(".png").context("Unable to create file for image")?; image.save(temp.path()).context("Unable to save image")?; let mut command = cfg_select! { unix => Command::new("xdg-open").arg(temp.path()).spawn(), windows => Command::new("PowerShell").arg("-Command").arg(format!("start {}", temp.path().display())).spawn(), _ => Err(anyhow::anyhow!("No available program to open images"))? }?; command.wait()?; // In case the image viewer forks and gives control back prior to reading the file, // drop it and don't run the destructor mem::forget(temp); Ok(()) }