Add color blending
This commit is contained in:
@@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ pub mod camera;
|
|||||||
pub mod chaos_game;
|
pub mod chaos_game;
|
||||||
pub mod transform;
|
pub mod transform;
|
||||||
pub mod variation;
|
pub mod variation;
|
||||||
|
mod image;
|
||||||
|
|
||||||
use glam::Affine2;
|
use glam::Affine2;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user