Add color blending
This commit is contained in:
@@ -53,15 +53,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn has_entry_main_chaos_game() {
|
||||
assert!(has_entry_point(
|
||||
ExecutionModel::GLCompute,
|
||||
"main_chaos_game"
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn has_entry_main_camera() {
|
||||
fn has_entry_main_camera() {
|
||||
assert!(has_entry_point(ExecutionModel::GLCompute, "main_camera"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ mod test {
|
||||
use libm::powf;
|
||||
|
||||
#[test]
|
||||
pub fn manual_camera() {
|
||||
fn manual_camera() {
|
||||
let starting_point = vec2(1.0, 1.0);
|
||||
|
||||
// Move the origin; points move right and up by one unit, giving us (2.0, 2.0)
|
||||
@@ -154,7 +154,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn point_outside_camera() {
|
||||
fn point_outside_camera() {
|
||||
// Scale 250 for an image 1000 x 1000 gives an effective range of [-2, 2]
|
||||
let camera = Camera::new(
|
||||
uvec2(1000, 1000),
|
||||
@@ -169,7 +169,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn point_outside_camera_negative() {
|
||||
fn point_outside_camera_negative() {
|
||||
// Scale 250 for an image 1000 x 1000 gives an effective range of [-2, 2]
|
||||
let camera = Camera::new(
|
||||
uvec2(1000, 1000),
|
||||
@@ -184,7 +184,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn aspect_ratio() {
|
||||
fn aspect_ratio() {
|
||||
// Scale 100 for an image 1600 x 900 gives an effective X range of [-8, 8],
|
||||
// and effective Y range of [-4.5, 4.5]
|
||||
let camera = Camera::new(
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
//! Image
|
||||
use glam::Vec4;
|
||||
use libm::floorf;
|
||||
|
||||
/// Blending modes for mapping IFS color values (which are on a scale `[0, 1]`)
|
||||
/// to RGBA colors.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
#[repr(u32)]
|
||||
pub enum BlendMode {
|
||||
/// Map IFS color values to a linear blend of the nearest two palette colors
|
||||
#[default]
|
||||
Linear = 0,
|
||||
|
||||
/// Map IFS color values to the nearest single palette color
|
||||
Step = 1,
|
||||
}
|
||||
|
||||
impl BlendMode {
|
||||
/// Map an IFS color value to RGBA color from the provided palette.
|
||||
pub fn ifs_to_rgb(&self, color: f32, palette: &[Vec4]) -> Vec4 {
|
||||
let colors_m_one = palette.len() - 1;
|
||||
let period = 1.0 / colors_m_one as f32;
|
||||
let index_lower = floorf(color / period) as usize;
|
||||
let index_upper = (index_lower + 1).clamp(0, colors_m_one);
|
||||
let rem = color % period / period;
|
||||
|
||||
match self {
|
||||
BlendMode::Linear => palette[index_lower].lerp(palette[index_upper], rem),
|
||||
BlendMode::Step => palette[index_lower],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UNSAFE: Sound because enum has guaranteed layout (u32) and defined zero-value
|
||||
unsafe impl bytemuck::Zeroable for BlendMode {}
|
||||
// UNSAFE: Sound because enum has guaranteed layout (u32) and defined zero-value
|
||||
unsafe impl bytemuck::Pod for BlendMode {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use glam::Vec4;
|
||||
use crate::image::BlendMode;
|
||||
|
||||
#[test]
|
||||
fn blend_linear() {
|
||||
let ifs_to_rgb = |color, palette| BlendMode::Linear.ifs_to_rgb(color, palette);
|
||||
|
||||
let palette = &[Vec4::splat(0.0), Vec4::splat(1.0)];
|
||||
assert_eq!(ifs_to_rgb(0.0, palette), Vec4::splat(0.0));
|
||||
assert_eq!(ifs_to_rgb(0.5, palette), Vec4::splat(0.5));
|
||||
assert_eq!(ifs_to_rgb(1.0, palette), Vec4::splat(1.0));
|
||||
|
||||
let palette = &[Vec4::splat(1.0), Vec4::splat(2.0), Vec4::splat(3.0)];
|
||||
assert_eq!(ifs_to_rgb(0.0, palette), Vec4::splat(1.0));
|
||||
assert_eq!(ifs_to_rgb(0.5, palette), Vec4::splat(2.0));
|
||||
assert_eq!(ifs_to_rgb(1.0, palette), Vec4::splat(3.0));
|
||||
|
||||
let palette = &[Vec4::splat(1.0), Vec4::splat(2.0), Vec4::splat(3.0), Vec4::splat(4.0)];
|
||||
assert_eq!(ifs_to_rgb(0.0, palette), Vec4::splat(1.0));
|
||||
assert_eq!(ifs_to_rgb(0.5, palette), Vec4::splat(2.5));
|
||||
assert_eq!(ifs_to_rgb(1.0, palette), Vec4::splat(4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_step() {
|
||||
let ifs_to_rgb = |color, palette| BlendMode::Step.ifs_to_rgb(color, palette);
|
||||
|
||||
let palette = &[Vec4::splat(0.0), Vec4::splat(1.0)];
|
||||
assert_eq!(ifs_to_rgb(0.5, palette), Vec4::splat(0.0));
|
||||
|
||||
let palette = &[Vec4::splat(1.0), Vec4::splat(2.0), Vec4::splat(3.0)];
|
||||
assert_eq!(ifs_to_rgb(0.0, palette), palette[0]);
|
||||
assert_eq!(ifs_to_rgb(0.25, palette), palette[0]);
|
||||
assert_eq!(ifs_to_rgb(0.4, palette), palette[0]);
|
||||
assert_eq!(ifs_to_rgb(0.5, palette), palette[1]);
|
||||
assert_eq!(ifs_to_rgb(0.7, palette), palette[1]);
|
||||
assert_eq!(ifs_to_rgb(1.0, palette), palette[2]);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ pub mod chaos_game;
|
||||
mod rng;
|
||||
pub mod transform;
|
||||
pub mod variation;
|
||||
pub mod image;
|
||||
|
||||
use glam::Affine2;
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ use rand_xoshiro::Xoshiro256StarStar;
|
||||
/// This function assumes a properly-initialized state array;
|
||||
/// output may silently degenerate if the initial state is all zeros,
|
||||
/// so this module is private to the crate.
|
||||
// Temporarily unused, will be required once the main image accumulation entry point is implemented
|
||||
#[allow(unused)]
|
||||
pub(crate) fn xoshiro256starstar_from_seed(
|
||||
rng_state: <Xoshiro256StarStar as SeedableRng>::Seed,
|
||||
) -> Xoshiro256StarStar {
|
||||
|
||||
@@ -125,6 +125,6 @@ mod test {
|
||||
|
||||
assert_eq!(transform.transform_color(0.0), 0.5);
|
||||
assert_eq!(transform.transform_color(1.0), 1.0);
|
||||
assert_eq!(transform.transform_color(2.0), 1.0);
|
||||
assert_eq!(transform.transform_color(2.0), 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user