use crate::render::font::Font;
use crate::render::path::Path;
use crate::render::texture::Texture;
use crate::types::{Color, Fixed, Opa, Point, Rect, Transform};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum CompositeMode {
#[default]
SourceOver,
Add,
Screen,
Multiply,
Darken,
Lighten,
Difference,
}
impl CompositeMode {
#[inline]
pub fn blend_channel(self, src: u8, dst: u8) -> u8 {
let s = src as u32;
let d = dst as u32;
match self {
Self::SourceOver => src,
Self::Add => (s + d).min(255) as u8,
Self::Screen => {
let inv = (255 - s) * (255 - d);
(255 - ((inv + 127) / 255)) as u8
}
Self::Multiply => ((s * d + 127) / 255) as u8,
Self::Darken => src.min(dst),
Self::Lighten => src.max(dst),
Self::Difference => src.abs_diff(dst),
}
}
}
#[cfg(test)]
mod composite_mode_tests {
use super::CompositeMode::*;
#[test]
fn source_over_returns_src() {
assert_eq!(SourceOver.blend_channel(0, 0), 0);
assert_eq!(SourceOver.blend_channel(128, 200), 128);
assert_eq!(SourceOver.blend_channel(255, 0), 255);
}
#[test]
fn add_saturates_at_255() {
assert_eq!(Add.blend_channel(128, 128), 255);
assert_eq!(Add.blend_channel(255, 255), 255);
assert_eq!(Add.blend_channel(0, 0), 0);
assert_eq!(Add.blend_channel(64, 64), 128);
}
#[test]
fn screen_is_inverse_multiply_of_inverses() {
assert!((190..=192).contains(&Screen.blend_channel(128, 128)));
assert_eq!(Screen.blend_channel(255, 0), 255);
assert_eq!(Screen.blend_channel(0, 0), 0);
assert_eq!(Screen.blend_channel(0, 200), 200);
}
#[test]
fn multiply_halves_at_50_percent() {
assert!((63..=65).contains(&Multiply.blend_channel(128, 128)));
assert_eq!(Multiply.blend_channel(0, 200), 0);
assert_eq!(Multiply.blend_channel(255, 200), 200);
}
#[test]
fn darken_keeps_smaller() {
assert_eq!(Darken.blend_channel(64, 192), 64);
assert_eq!(Darken.blend_channel(200, 100), 100);
assert_eq!(Darken.blend_channel(128, 128), 128);
}
#[test]
fn lighten_keeps_larger() {
assert_eq!(Lighten.blend_channel(64, 192), 192);
assert_eq!(Lighten.blend_channel(200, 100), 200);
}
#[test]
fn difference_is_absolute_diff() {
assert_eq!(Difference.blend_channel(192, 64), 128);
assert_eq!(Difference.blend_channel(64, 192), 128);
assert_eq!(Difference.blend_channel(100, 100), 0);
}
}
pub enum DrawCommand<'a> {
Fill {
area: Rect,
transform: Transform,
quad: Option<[Point; 4]>,
color: Color,
radius: Fixed,
opa: Opa,
},
Border {
area: Rect,
transform: Transform,
quad: Option<[Point; 4]>,
color: Color,
width: Fixed,
radius: Fixed,
opa: Opa,
},
Label {
pos: Point,
transform: Transform,
text: &'a str,
font: &'a Font,
color: Color,
opa: Opa,
},
Line {
p1: Point,
p2: Point,
transform: Transform,
color: Color,
width: Fixed,
opa: Opa,
},
Arc {
center: Point,
transform: Transform,
radius: Fixed,
start_angle: Fixed,
end_angle: Fixed,
color: Color,
width: Fixed,
opa: Opa,
},
Blit {
pos: Point,
size: Point,
transform: Transform,
quad: Option<[Point; 4]>,
texture: &'a Texture<'a>,
opa: Opa,
radius: Fixed,
composite: CompositeMode,
},
FillPath {
path: &'a Path,
transform: Transform,
color: Color,
opa: Opa,
},
}
impl DrawCommand<'_> {
#[inline]
pub fn transform(&self) -> Transform {
match *self {
Self::Fill { transform, .. }
| Self::Border { transform, .. }
| Self::Label { transform, .. }
| Self::Line { transform, .. }
| Self::Arc { transform, .. }
| Self::Blit { transform, .. }
| Self::FillPath { transform, .. } => transform,
}
}
}