Add color blending

This commit is contained in:
2026-08-01 15:52:44 -04:00
parent 7adb747625
commit 0d2e80145c
2 changed files with 80 additions and 0 deletions
+79
View File
@@ -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]);
}
}
+1
View File
@@ -6,6 +6,7 @@ pub mod camera;
pub mod chaos_game;
pub mod transform;
pub mod variation;
mod image;
use glam::Affine2;