use crate::formats::PixelFormatSpec;
pub type Colour = rgb::Rgba<u8, u8>;
pub type Color = Colour;
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BlendMode {
SourceCopy,
Simple,
SourceAlpha,
BackdropAlpha,
Multiply,
}
impl BlendMode {
const fn mix(a: u8, b: u8, alpha: u8) -> u8 {
(((a as u16 * alpha as u16) + (b as u16 * (255 - alpha as u16)) + 128) / 255) as u8
}
const fn mix2(a: u8, b: u8, alpha1: u8, alpha2: u8) -> u8 {
let a_term = a as u32 * alpha1 as u32;
let b_term = b as u32 * alpha2 as u32 * (255 - alpha1) as u32;
(((a_term + (b_term + 128) / 255) + 128) / 255) as u8
}
pub const fn blend(&self, source: &Colour, backdrop: &Colour) -> Colour {
match self {
Self::SourceCopy => *source,
Self::Simple => Colour {
r: Self::mix2(source.r, backdrop.r, source.a, backdrop.a),
g: Self::mix2(source.g, backdrop.g, source.a, backdrop.a),
b: Self::mix2(source.b, backdrop.b, source.a, backdrop.a),
a: Self::mix(255, backdrop.a, source.a),
},
Self::SourceAlpha => Colour {
r: Self::mix(source.r, backdrop.r, source.a),
g: Self::mix(source.g, backdrop.g, source.a),
b: Self::mix(source.b, backdrop.b, source.a),
a: Self::mix(255, backdrop.a, source.a),
},
Self::BackdropAlpha => Colour {
r: Self::mix(source.r, backdrop.r, backdrop.a),
g: Self::mix(source.g, backdrop.g, backdrop.a),
b: Self::mix(source.b, backdrop.b, backdrop.a),
a: Self::mix(255, source.a, backdrop.a),
},
Self::Multiply => Colour {
r: ((source.r as u16 * backdrop.r as u16 + 128) / 255) as u8,
g: ((source.g as u16 * backdrop.g as u16 + 128) / 255) as u8,
b: ((source.b as u16 * backdrop.b as u16 + 128) / 255) as u8,
a: ((source.a as u16 * backdrop.a as u16 + 128) / 255) as u8,
},
}
}
}
pub(crate) fn write_as_bytes<Format: PixelFormatSpec>(col: &Colour, to: &mut [u8]) {
if let Some(offset) = Format::RED_PACK.index() {
to[offset as usize] = col.r;
}
if let Some(offset) = Format::GREEN_PACK.index() {
to[offset as usize] = col.g;
}
if let Some(offset) = Format::BLUE_PACK.index() {
to[offset as usize] = col.b;
}
if let Some(offset) = Format::ALPHA_PACK.index() {
to[offset as usize] = col.a;
}
}
pub(crate) fn read_from_bytes<Format: PixelFormatSpec>(from: &[u8]) -> Colour {
Colour {
r: if let Some(offset) = Format::RED_PACK.index() {
from[offset as usize]
} else {
255
},
g: if let Some(offset) = Format::GREEN_PACK.index() {
from[offset as usize]
} else {
255
},
b: if let Some(offset) = Format::BLUE_PACK.index() {
from[offset as usize]
} else {
255
},
a: if let Some(offset) = Format::ALPHA_PACK.index() {
from[offset as usize]
} else {
255
},
}
}
#[cfg(test)]
#[allow(non_upper_case_globals)]
pub mod test {
use super::{BlendMode, Colour};
use crate::palette::css;
use rstest::rstest;
use rstest_reuse::{apply, template};
#[derive(Clone, Copy)]
pub struct BlendTestCase {
pub desc: &'static str,
pub source: Colour,
pub backdrop: Colour,
pub simple_result: Option<Colour>,
pub source_result: Option<Colour>,
pub backdrop_result: Option<Colour>,
pub multiply_result: Option<Colour>,
}
pub const fn c(v: u32, a: u8) -> Colour {
Colour::new(
((v >> 16) & 0xff) as u8,
((v >> 8) & 0xff) as u8,
((v >> 0) & 0xff) as u8,
a,
)
}
pub const tgrey: Colour = c(0x808080, 0x80);
pub const unknown: Colour = c(0, 0);
#[allow(unused)]
pub const empty_case: BlendTestCase = BlendTestCase {
desc: "",
source: unknown,
backdrop: unknown,
simple_result: None,
source_result: None,
backdrop_result: None,
multiply_result: None,
};
#[template]
#[rstest]
#[case(BlendTestCase {
desc: "opaque white over opaque black (#1)",
source: css::white,
backdrop: css::black,
simple_result: Some(css::white),
source_result: Some(css::white),
backdrop_result: Some(css::white),
multiply_result: Some(css::black),
})]
#[case(BlendTestCase {
desc: "opaque black over opaque white (#2)",
source: css::black,
backdrop: css::white,
simple_result: Some(css::black),
source_result: Some(css::black),
backdrop_result: Some(css::black),
multiply_result: Some(css::black),
})]
#[case(BlendTestCase {
desc: "transparent gray over opaque red (#3)",
source: tgrey,
backdrop: css::red,
simple_result: Some(c(0xbf4040, 0xff)),
source_result: Some(c(0xbf4040, 0xff)),
backdrop_result: Some(c(0x808080, 0xff)),
multiply_result: Some(c(0x800000, 0x80)),
})]
#[case(BlendTestCase {
desc: "transparent gray over transparent gray (#4)",
source: tgrey,
backdrop: tgrey,
simple_result: Some(c(0x606060, 0xc0)),
source_result: Some(c(0x808080, 0xc0)),
backdrop_result: Some(c(0x808080, 0xc0)),
multiply_result: Some(c(0x404040, 0x40)),
})]
#[case(BlendTestCase {
desc: "regression test for initial incorrect AVX output (#5)",
source: c(0x71fc28, 0x81),
backdrop: c(0x3589ce, 0x5d),
simple_result: Some(c(0x439839, 0xaf)),
source_result: Some(c(0x53c37a, 0xaf)),
backdrop_result: Some(c(0x4bb391, 0xaf)),
multiply_result: Some(c(0x178720, 0x2f)),
..empty_case
})]
#[case(BlendTestCase {
desc: "regression test for initial incorrect generic output (#6)",
source: c(0xa1e09e, 0xb5),
backdrop: c(0xcc7c12, 0x3e),
simple_result: Some(c(0x81a871, 0xc7)),
source_result: Some(c(0xadc375, 0xc7)),
backdrop_result: Some(c(0xc29434, 0xc7)),
multiply_result: Some(c(0x816d0b, 0x2c)),
..empty_case
})]
fn blend_template(#[case] tc: BlendTestCase) {}
#[apply(blend_template)]
fn simple_blend_mode(tc: BlendTestCase) {
println!("blending case: {}", tc.desc);
let r = tc
.simple_result
.expect("there is no expected simple result for this case");
println!("blending {:?} and {:?}", tc.source, tc.backdrop);
println!("expecting {r:?}");
assert_eq!(r, BlendMode::Simple.blend(&tc.source, &tc.backdrop),)
}
#[apply(blend_template)]
fn source_alpha_blend_mode(tc: BlendTestCase) {
println!("blending case: {}", tc.desc);
let r = tc
.source_result
.expect("there is no expected source result for this case");
println!("blending {:?} and {:?}", tc.source, tc.backdrop);
println!("expecting {r:?}");
assert_eq!(r, BlendMode::SourceAlpha.blend(&tc.source, &tc.backdrop),)
}
#[apply(blend_template)]
fn backdrop_alpha_blend_mode(tc: BlendTestCase) {
println!("blending case: {}", tc.desc);
let r = tc
.backdrop_result
.expect("there is no expected backdrop result for this case");
println!("blending {:?} and {:?}", tc.source, tc.backdrop);
println!("expecting {r:?}");
assert_eq!(r, BlendMode::BackdropAlpha.blend(&tc.source, &tc.backdrop),)
}
#[apply(blend_template)]
fn multiply_alpha_blend_mode(tc: BlendTestCase) {
println!("blending case: {}", tc.desc);
let r = tc
.multiply_result
.expect("there is no expected multiply result for this case");
println!("blending {:?} and {:?}", tc.source, tc.backdrop);
println!("expecting {r:?}");
assert_eq!(r, BlendMode::Multiply.blend(&tc.source, &tc.backdrop),)
}
}