Files
bspeice fc76a8308a
CI / cargo fmt (push) Failing after 27s
CI / cargo test (push) Failing after 53s
CI / cargo test (GPU) (push) Successful in 13m6s
Add image accumulation
2026-08-02 11:10:25 -04:00

107 lines
4.7 KiB
Rust

//! # Fractal flame
//!
//! The `flam3` sources include example parameters (`test.flam3`) to demonstrate the fractal flame algorithm,
//! which are copied here and used to generate an image that can be compared against the reference `flam3` renderer
use anyhow::Result;
use enkou_shaders::chaos_game::ChaosGame;
use enkou_shaders::transform::Transform;
use enkou_shaders::variation::{Variation, VariationKind};
use glam::{UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec4};
use rand::SeedableRng;
use rand_xoshiro::Xoshiro256StarStar;
use enkou_shaders::camera::Camera;
use enkou_shaders::image::BlendMode;
const ITERATIONS_DISCARD: usize = 100;
const IMAGE_DIMENSIONS: UVec2 = UVec2::new(640, 480);
const IMAGE_QUALITY: f32 = 10.0;
// Palette data copied from `flam3-palettes.xml`.
// Palettes are ASCII hexadecimal strings; two characters form a byte,
// and bytes are grouped in triplets to get a single RGB color.
const PALETTE: &'static str = "\
00dadebc00eee6c500eef2ce00eef2cf00e6eee100eaeed800f2f1eb00f2f5d8
00e6f2ce00deeac500d6dac600ced2bc00c2caa900becaa000ced6aa00dee2c5
00eaedce00eaf2c500dee2c500c2caaa00aebeaa00a5b29600a2a98d0096a284
008d8d7a0085897100858d710085856700797d6700797d670071795e00656d55
004d5d4200344025003040250030381c002c3c1c002c341c00242c1200242400
00242c0900283409003840120030401c0040502f0055694200657555006c7d5e
00748d710074898400748d8400788d840079897a0079857100757d670071795e
006c715e006d705e006c795e0068755e00697155006d7555006d755500697155
0065715500696d550064715e006870670068706700686c67006c6c5e0071715e
0079796700818571007d91710085927a0085927a007d9284007992840078928d
00788d8d00748d84007492840075927a006c85670064795e0059694b00aa5700
0038441c00303c1c002c3c1c003440250050614b005d6d5e0064715e0060715e
0060755e0068755e006c795e006c795e0071796700707967006c7d6700687967
006c7967006c75670071755e0071755e0075795e00757d5e00818d5e008d925e
008d9267009a9a71009aa27a009aa27a009aa17a00929a71008992670081855e
007d7d550069794b00616d4200444c250038441c0040512500454d2500716d42
00797d4b00817d5500797955006d755500697d55006c795e006579540068795e
00647967006479670068755e0064715e00646c5e00656d55004d584200344025
002c381c0020281c001c1409001818000004140000081000000c1800001c2809
00243012003c4425005d6555007579550085895e008991710096a271009aa27a
009eaa7a009eaa7a00aaae7100a6aa7a00a2aa7a00a1a57a00969e7a0085967a
0081927a0078927a0075927a00758d7a00708167007d7d670089896700929a71
009eaa7a00aab68400b2b68d00b6ba9700c2ca9700b2be8d00b2b68d00aab28d
00a2ae84009aa67a00929e7a00859a7a007d967a007d927a007d9284007d9284
0081968400859684008596840081928400859a8400859a84008d9a8400929684
009ea98400aeb28400aaba8400b2be8d00b6c2a000c6caa000c6ceaa00d6dab3
00dae2c500d2d6bc00bec2a000aab68d009ea67a00929a710089897100817d67
007d7d6700817867007d7d5e0079795e0079815e00817d6700817d6700818167
008189710085917a0089927a00969d7a00969e7a0092968400969a8d00929284
0089918400819284007d928d0078928d0074928d0078928d007896970081968d
0081968d00819a8d00859a8d00899e8d00899e8d008da2970095a297008da297
0096a68d009aa18d009ea984009ea67a00a2a571009ea671009aa67100959d71";
/// Generate an image
pub fn main() -> Result<()> {
let transforms: &[Transform] = &[];
let weights = [0.25; 4];
let variations = [Variation::new(
VariationKind::Spherical,
1.0,
[0.0; 4].into(),
)];
let camera = Camera::new(IMAGE_DIMENSIONS, Vec2::ZERO, 0.0, Vec2::ZERO, (IMAGE_DIMENSIONS / 2).as_vec2());
let palette_chars: String = PALETTE.chars().filter(|c| c.is_alphanumeric()).collect();
let palette_bytes: Vec<u8> = (0..palette_chars.len())
.step_by(2)
.map(|i| {
let s = &palette_chars[i..i + 2];
let b = u8::from_str_radix(s, 16).expect("illegal palette value");
b
})
.collect();
let palette_colors: Vec<Vec4> = palette_bytes
.chunks(3)
.map(|chunk| [chunk[0] as f32, chunk[1] as f32, chunk[2] as f32, 255.0].into())
.collect();
let mut rng = Xoshiro256StarStar::from_seed([4u8; 32]);
let chaos_game = ChaosGame::new(&mut rng, transforms, &weights, &variations);
let mut image_accum: Vec<Vec4> = Vec::new();
image_accum.resize(IMAGE_DIMENSIONS.element_product() as usize, Vec4::ZERO);
let iterations: usize = (IMAGE_DIMENSIONS.element_product() as f32 * IMAGE_QUALITY) as usize;
chaos_game.skip(ITERATIONS_DISCARD).take(iterations).for_each(|(ifs_point, ifs_color)| {
let pixel_point = camera.transform_point_to_image(ifs_point);
if pixel_point.is_none() {
return;
}
let pixel_point = pixel_point.unwrap();
let pixel_index = pixel_point.y * IMAGE_DIMENSIONS.x + pixel_point.x;
let pixel_color = BlendMode::Linear.ifs_to_rgb(ifs_color, palette_colors.as_ref());
image_accum[pixel_index as usize] += pixel_color;
});
Ok(())
}