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, vec2}; 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 ITERATIONS: usize = 50_000; const IMAGE_DIMENSION: UVec2 = uvec2(600, 600); 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), vec2(0.0, 1.0)) }, { // 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), vec2(0.0, 1.0)) }, { // 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), vec2(0.0, 1.0)) }, ]; 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); chaos_game .skip(ITERATIONS_DISCARD) .take(ITERATIONS) .filter_map(|(point_ifs, _)| camera.transform_point_to_image(point_ifs)) .for_each(|point_pixel| image.put_pixel(point_pixel.x, point_pixel.y, Luma([255]))); let temp = NamedTempFile::with_suffix(".png").context("Unable to create file for image")?; image.save(temp.path()).context("Unable to save image")?; let open_program: &str = cfg_select! { unix => "xdg-open", _ => panic!("No available program to open images") }; Command::new(open_program) .arg(temp.path()) .spawn()? .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(()) }