use crate::rasterizer_scanline_aa::{RasterizerScanlineAa, Scanline};
use crate::scanline_storage_aa::ScanlineStorageAa;
use crate::scanline_storage_bin::ScanlineStorageBin;
use crate::scanline_u::ScanlineU8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SBoolOp {
Or,
And,
Xor,
XorSaddle,
XorAbsDiff,
AMinusB,
BMinusA,
}
const COVER_FULL: u32 = 255;
const COVER_SHIFT: u32 = 8;
#[inline]
fn intersect_covers(c1: u32, c2: u32) -> u8 {
let cover = c1 * c2;
if cover == COVER_FULL * COVER_FULL {
COVER_FULL as u8
} else {
(cover >> COVER_SHIFT) as u8
}
}
#[inline]
fn unite_covers(c1: u32, c2: u32) -> u8 {
let cover = COVER_FULL * COVER_FULL - (COVER_FULL - c1) * (COVER_FULL - c2);
if cover == COVER_FULL * COVER_FULL {
COVER_FULL as u8
} else {
(cover >> COVER_SHIFT) as u8
}
}
#[inline]
fn subtract_covers(c1: u32, c2: u32) -> u8 {
let cover = c1 * (COVER_FULL - c2);
if cover == COVER_FULL * COVER_FULL {
COVER_FULL as u8
} else {
(cover >> COVER_SHIFT) as u8
}
}
#[inline]
fn xor_covers_linear(c1: u32, c2: u32) -> u8 {
let cover = c1 + c2;
if cover > COVER_FULL {
(COVER_FULL + COVER_FULL - cover) as u8
} else {
cover as u8
}
}
#[inline]
fn xor_covers_saddle(c1: u32, c2: u32) -> u8 {
let k = c1 * c2;
if k == COVER_FULL * COVER_FULL {
return 0;
}
let a = (COVER_FULL * COVER_FULL - (c1 << COVER_SHIFT) + k) >> COVER_SHIFT;
let b = (COVER_FULL * COVER_FULL - (c2 << COVER_SHIFT) + k) >> COVER_SHIFT;
(COVER_FULL - ((a * b) >> COVER_SHIFT)) as u8
}
#[inline]
fn xor_covers_abs_diff(c1: u32, c2: u32) -> u8 {
(c1 as i32 - c2 as i32).unsigned_abs() as u8
}
pub fn sbool_combine_shapes_aa(
op: SBoolOp,
ras1: &mut RasterizerScanlineAa,
ras2: &mut RasterizerScanlineAa,
sl1: &mut ScanlineU8,
sl2: &mut ScanlineU8,
sl_result: &mut ScanlineU8,
storage1: &mut ScanlineStorageAa,
storage2: &mut ScanlineStorageAa,
storage_result: &mut ScanlineStorageAa,
) {
storage1.prepare();
render_to_storage(ras1, sl1, storage1);
storage2.prepare();
render_to_storage(ras2, sl2, storage2);
storage_result.prepare();
sbool_combine_storages_aa(op, storage1, storage2, sl_result, storage_result);
}
fn render_to_storage(
ras: &mut RasterizerScanlineAa,
sl: &mut ScanlineU8,
storage: &mut ScanlineStorageAa,
) {
if ras.rewind_scanlines() {
sl.reset(ras.min_x(), ras.max_x());
while ras.sweep_scanline(sl) {
storage.render_scanline_u8(sl);
}
}
}
pub fn sbool_combine_storages_aa(
op: SBoolOp,
storage1: &ScanlineStorageAa,
storage2: &ScanlineStorageAa,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageAa,
) {
let n1 = storage1.num_scanlines();
let n2 = storage2.num_scanlines();
if n1 == 0 && n2 == 0 {
return;
}
let min_x = if n1 > 0 && n2 > 0 {
storage1.min_x().min(storage2.min_x())
} else if n1 > 0 {
storage1.min_x()
} else {
storage2.min_x()
};
let max_x = if n1 > 0 && n2 > 0 {
storage1.max_x().max(storage2.max_x())
} else if n1 > 0 {
storage1.max_x()
} else {
storage2.max_x()
};
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff => {
if n1 == 0 {
copy_storage_aa(storage2, sl, result, min_x, max_x);
return;
}
if n2 == 0 {
copy_storage_aa(storage1, sl, result, min_x, max_x);
return;
}
}
SBoolOp::And => {
if n1 == 0 || n2 == 0 {
return;
}
}
SBoolOp::AMinusB => {
if n1 == 0 {
return; }
if n2 == 0 {
copy_storage_aa(storage1, sl, result, min_x, max_x);
return;
}
}
SBoolOp::BMinusA => {
if n2 == 0 {
return;
}
if n1 == 0 {
copy_storage_aa(storage2, sl, result, min_x, max_x);
return;
}
}
}
let mut i1 = 0usize;
let mut i2 = 0usize;
while i1 < n1 || i2 < n2 {
if i1 >= n1 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::BMinusA => {
emit_scanline_from_storage(storage2, i2, sl, result, min_x, max_x);
}
_ => {} }
i2 += 1;
continue;
}
if i2 >= n2 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::AMinusB => {
emit_scanline_from_storage(storage1, i1, sl, result, min_x, max_x);
}
_ => {} }
i1 += 1;
continue;
}
let y1 = storage1.scanline_y(i1);
let y2 = storage2.scanline_y(i2);
if y1 < y2 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::AMinusB => {
emit_scanline_from_storage(storage1, i1, sl, result, min_x, max_x);
}
_ => {}
}
i1 += 1;
} else if y2 < y1 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::BMinusA => {
emit_scanline_from_storage(storage2, i2, sl, result, min_x, max_x);
}
_ => {}
}
i2 += 1;
} else {
combine_scanlines_aa(op, storage1, i1, storage2, i2, sl, result, min_x, max_x);
i1 += 1;
i2 += 1;
}
}
}
fn copy_storage_aa(
src: &ScanlineStorageAa,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageAa,
min_x: i32,
max_x: i32,
) {
for i in 0..src.num_scanlines() {
emit_scanline_from_storage(src, i, sl, result, min_x, max_x);
}
}
fn emit_scanline_from_storage(
src: &ScanlineStorageAa,
sl_idx: usize,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageAa,
min_x: i32,
max_x: i32,
) {
let y = src.scanline_y(sl_idx);
sl.reset(min_x, max_x);
sl.reset_spans();
for sp in src.embedded_spans(sl_idx) {
if sp.len < 0 {
sl.add_span(sp.x, (-sp.len) as u32, sp.covers[0] as u32);
} else {
for j in 0..sp.len as usize {
sl.add_cell(sp.x + j as i32, sp.covers[j] as u32);
}
}
}
sl.finalize(y);
if sl.num_spans() > 0 {
result.render_scanline_u8(sl);
}
}
fn combine_scanlines_aa(
op: SBoolOp,
storage1: &ScanlineStorageAa,
sl_idx1: usize,
storage2: &ScanlineStorageAa,
sl_idx2: usize,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageAa,
min_x: i32,
max_x: i32,
) {
let y = storage1.scanline_y(sl_idx1);
sl.reset(min_x, max_x);
sl.reset_spans();
let width = (max_x - min_x + 1) as usize;
let mut cov1 = vec![0u8; width];
let mut cov2 = vec![0u8; width];
for sp in storage1.embedded_spans(sl_idx1) {
let abs_len = sp.abs_len();
for j in 0..abs_len {
let x = sp.x + j;
if x >= min_x && x <= max_x {
cov1[(x - min_x) as usize] = sp.cover_at(j as usize);
}
}
}
for sp in storage2.embedded_spans(sl_idx2) {
let abs_len = sp.abs_len();
for j in 0..abs_len {
let x = sp.x + j;
if x >= min_x && x <= max_x {
cov2[(x - min_x) as usize] = sp.cover_at(j as usize);
}
}
}
for i in 0..width {
let c1 = cov1[i] as u32;
let c2 = cov2[i] as u32;
let result_cover = match op {
SBoolOp::Or => {
if c1 > 0 && c2 > 0 {
unite_covers(c1, c2)
} else if c1 > 0 {
c1 as u8
} else {
c2 as u8
}
}
SBoolOp::And => intersect_covers(c1, c2),
SBoolOp::Xor => {
if c1 > 0 && c2 > 0 {
xor_covers_linear(c1, c2)
} else if c1 > 0 {
c1 as u8
} else {
c2 as u8
}
}
SBoolOp::XorSaddle => {
if c1 > 0 && c2 > 0 {
xor_covers_saddle(c1, c2)
} else if c1 > 0 {
c1 as u8
} else {
c2 as u8
}
}
SBoolOp::XorAbsDiff => {
if c1 > 0 && c2 > 0 {
xor_covers_abs_diff(c1, c2)
} else if c1 > 0 {
c1 as u8
} else {
c2 as u8
}
}
SBoolOp::AMinusB => subtract_covers(c1, c2),
SBoolOp::BMinusA => subtract_covers(c2, c1),
};
if result_cover > 0 {
sl.add_cell(min_x + i as i32, result_cover as u32);
}
}
sl.finalize(y);
if sl.num_spans() > 0 {
result.render_scanline_u8(sl);
}
}
pub fn sbool_combine_shapes_bin(
op: SBoolOp,
ras1: &mut RasterizerScanlineAa,
ras2: &mut RasterizerScanlineAa,
sl1: &mut ScanlineU8,
sl2: &mut ScanlineU8,
sl_result: &mut ScanlineU8,
storage1: &mut ScanlineStorageBin,
storage2: &mut ScanlineStorageBin,
storage_result: &mut ScanlineStorageBin,
) {
storage1.prepare();
render_to_bin_storage(ras1, sl1, storage1);
storage2.prepare();
render_to_bin_storage(ras2, sl2, storage2);
storage_result.prepare();
sbool_combine_storages_bin(op, storage1, storage2, sl_result, storage_result);
}
fn render_to_bin_storage(
ras: &mut RasterizerScanlineAa,
sl: &mut ScanlineU8,
storage: &mut ScanlineStorageBin,
) {
if ras.rewind_scanlines() {
sl.reset(ras.min_x(), ras.max_x());
while ras.sweep_scanline(sl) {
storage.render_scanline_u8(sl);
}
}
}
pub fn sbool_combine_storages_bin(
op: SBoolOp,
storage1: &ScanlineStorageBin,
storage2: &ScanlineStorageBin,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageBin,
) {
let n1 = storage1.num_scanlines();
let n2 = storage2.num_scanlines();
if n1 == 0 && n2 == 0 {
return;
}
let min_x = if n1 > 0 && n2 > 0 {
storage1.min_x().min(storage2.min_x())
} else if n1 > 0 {
storage1.min_x()
} else {
storage2.min_x()
};
let max_x = if n1 > 0 && n2 > 0 {
storage1.max_x().max(storage2.max_x())
} else if n1 > 0 {
storage1.max_x()
} else {
storage2.max_x()
};
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff => {
if n1 == 0 {
copy_storage_bin(storage2, sl, result, min_x, max_x);
return;
}
if n2 == 0 {
copy_storage_bin(storage1, sl, result, min_x, max_x);
return;
}
}
SBoolOp::And => {
if n1 == 0 || n2 == 0 {
return;
}
}
SBoolOp::AMinusB => {
if n1 == 0 {
return;
}
if n2 == 0 {
copy_storage_bin(storage1, sl, result, min_x, max_x);
return;
}
}
SBoolOp::BMinusA => {
if n2 == 0 {
return;
}
if n1 == 0 {
copy_storage_bin(storage2, sl, result, min_x, max_x);
return;
}
}
}
let mut i1 = 0usize;
let mut i2 = 0usize;
while i1 < n1 || i2 < n2 {
if i1 >= n1 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::BMinusA => {
emit_scanline_from_bin_storage(storage2, i2, sl, result, min_x, max_x);
}
_ => {}
}
i2 += 1;
continue;
}
if i2 >= n2 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::AMinusB => {
emit_scanline_from_bin_storage(storage1, i1, sl, result, min_x, max_x);
}
_ => {}
}
i1 += 1;
continue;
}
let y1 = storage1.scanline_y(i1);
let y2 = storage2.scanline_y(i2);
if y1 < y2 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::AMinusB => {
emit_scanline_from_bin_storage(storage1, i1, sl, result, min_x, max_x);
}
_ => {}
}
i1 += 1;
} else if y2 < y1 {
match op {
SBoolOp::Or | SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff | SBoolOp::BMinusA => {
emit_scanline_from_bin_storage(storage2, i2, sl, result, min_x, max_x);
}
_ => {}
}
i2 += 1;
} else {
combine_scanlines_bin(op, storage1, i1, storage2, i2, sl, result, min_x, max_x);
i1 += 1;
i2 += 1;
}
}
}
fn copy_storage_bin(
src: &ScanlineStorageBin,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageBin,
min_x: i32,
max_x: i32,
) {
let mut sl_bin = crate::scanline_bin::ScanlineBin::new();
for i in 0..src.num_scanlines() {
let y = src.scanline_y(i);
sl_bin.reset(min_x, max_x);
sl_bin.reset_spans();
for sp in src.embedded_spans(i) {
sl_bin.add_span(sp.x, sp.len as u32, COVER_FULL);
}
sl_bin.finalize(y);
if sl_bin.num_spans() > 0 {
result.render_scanline_bin(&sl_bin);
}
}
let _ = sl; }
fn emit_scanline_from_bin_storage(
src: &ScanlineStorageBin,
sl_idx: usize,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageBin,
min_x: i32,
max_x: i32,
) {
let y = src.scanline_y(sl_idx);
let mut sl_bin = crate::scanline_bin::ScanlineBin::new();
sl_bin.reset(min_x, max_x);
sl_bin.reset_spans();
for sp in src.embedded_spans(sl_idx) {
sl_bin.add_span(sp.x, sp.len as u32, COVER_FULL);
}
sl_bin.finalize(y);
if sl_bin.num_spans() > 0 {
result.render_scanline_bin(&sl_bin);
}
let _ = sl; }
fn combine_scanlines_bin(
op: SBoolOp,
storage1: &ScanlineStorageBin,
sl_idx1: usize,
storage2: &ScanlineStorageBin,
sl_idx2: usize,
sl: &mut ScanlineU8,
result: &mut ScanlineStorageBin,
min_x: i32,
max_x: i32,
) {
let y = storage1.scanline_y(sl_idx1);
let width = (max_x - min_x + 1) as usize;
let mut bits1 = vec![false; width];
let mut bits2 = vec![false; width];
for sp in storage1.embedded_spans(sl_idx1) {
for j in 0..sp.len {
let x = sp.x + j;
if x >= min_x && x <= max_x {
bits1[(x - min_x) as usize] = true;
}
}
}
for sp in storage2.embedded_spans(sl_idx2) {
for j in 0..sp.len {
let x = sp.x + j;
if x >= min_x && x <= max_x {
bits2[(x - min_x) as usize] = true;
}
}
}
let mut sl_bin = crate::scanline_bin::ScanlineBin::new();
sl_bin.reset(min_x, max_x);
sl_bin.reset_spans();
for i in 0..width {
let in1 = bits1[i];
let in2 = bits2[i];
let result_on = match op {
SBoolOp::Or => in1 || in2,
SBoolOp::And => in1 && in2,
SBoolOp::Xor | SBoolOp::XorSaddle | SBoolOp::XorAbsDiff => in1 ^ in2,
SBoolOp::AMinusB => in1 && !in2,
SBoolOp::BMinusA => in2 && !in1,
};
if result_on {
sl_bin.add_cell(min_x + i as i32, COVER_FULL);
}
}
sl_bin.finalize(y);
if sl_bin.num_spans() > 0 {
result.render_scanline_bin(&sl_bin);
}
let _ = sl; }
#[cfg(test)]
mod tests {
use super::*;
fn make_aa_rect(
x1: i32,
x2: i32,
y1: i32,
y2: i32,
cover: u8,
) -> ScanlineStorageAa {
let mut storage = ScanlineStorageAa::new();
storage.prepare();
let mut sl = ScanlineU8::new();
for y in y1..=y2 {
sl.reset(x1, x2);
sl.reset_spans();
for x in x1..=x2 {
sl.add_cell(x, cover as u32);
}
sl.finalize(y);
storage.render_scanline_u8(&sl);
}
storage
}
fn make_bin_rect(x1: i32, x2: i32, y1: i32, y2: i32) -> ScanlineStorageBin {
let mut storage = ScanlineStorageBin::new();
storage.prepare();
let mut sl = crate::scanline_bin::ScanlineBin::new();
for y in y1..=y2 {
sl.reset(x1, x2);
sl.reset_spans();
sl.add_span(x1, (x2 - x1 + 1) as u32, 255);
sl.finalize(y);
storage.render_scanline_bin(&sl);
}
storage
}
#[test]
fn test_aa_intersection() {
let s1 = make_aa_rect(0, 19, 0, 19, 255);
let s2 = make_aa_rect(10, 29, 10, 29, 255);
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::And, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 10);
assert_eq!(result.min_x(), 10);
assert_eq!(result.max_x(), 19);
assert_eq!(result.min_y(), 10);
assert_eq!(result.max_y(), 19);
}
#[test]
fn test_aa_union() {
let s1 = make_aa_rect(0, 9, 0, 4, 255);
let s2 = make_aa_rect(5, 14, 0, 4, 255);
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::Or, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 5);
assert_eq!(result.min_x(), 0);
assert_eq!(result.max_x(), 14);
}
#[test]
fn test_aa_subtract() {
let s1 = make_aa_rect(0, 19, 0, 19, 255);
let s2 = make_aa_rect(10, 29, 10, 29, 255);
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::AMinusB, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 20);
}
#[test]
fn test_aa_xor() {
let s1 = make_aa_rect(0, 9, 0, 4, 200);
let s2 = make_aa_rect(5, 14, 0, 4, 200);
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::Xor, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 5);
}
#[test]
fn test_aa_intersect_no_overlap() {
let s1 = make_aa_rect(0, 9, 0, 4, 255);
let s2 = make_aa_rect(20, 29, 10, 14, 255);
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::And, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 0);
}
#[test]
fn test_aa_union_disjoint() {
let s1 = make_aa_rect(0, 4, 0, 2, 255);
let s2 = make_aa_rect(10, 14, 5, 7, 255);
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::Or, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 6);
}
#[test]
fn test_aa_empty_operand() {
let s1 = make_aa_rect(0, 9, 0, 4, 255);
let s2 = ScanlineStorageAa::new();
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::Or, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 5);
result.prepare();
sbool_combine_storages_aa(SBoolOp::And, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 0);
}
#[test]
fn test_aa_semi_transparent_intersection() {
let s1 = make_aa_rect(0, 9, 0, 0, 128);
let s2 = make_aa_rect(0, 9, 0, 0, 128);
let mut result = ScanlineStorageAa::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_aa(SBoolOp::And, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 1);
assert!(result.rewind_scanlines());
let mut sl_out = ScanlineU8::new();
sl_out.reset(0, 9);
assert!(result.sweep_scanline(&mut sl_out));
let spans = sl_out.begin();
let covers = sl_out.covers();
let cover = covers[spans[0].cover_offset];
assert_eq!(cover, 64);
}
#[test]
fn test_bin_intersection() {
let s1 = make_bin_rect(0, 19, 0, 19);
let s2 = make_bin_rect(10, 29, 10, 29);
let mut result = ScanlineStorageBin::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_bin(SBoolOp::And, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 10);
}
#[test]
fn test_bin_union() {
let s1 = make_bin_rect(0, 4, 0, 2);
let s2 = make_bin_rect(10, 14, 5, 7);
let mut result = ScanlineStorageBin::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_bin(SBoolOp::Or, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 6);
}
#[test]
fn test_bin_xor() {
let s1 = make_bin_rect(0, 9, 0, 4);
let s2 = make_bin_rect(5, 14, 0, 4);
let mut result = ScanlineStorageBin::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_bin(SBoolOp::Xor, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 5);
}
#[test]
fn test_bin_subtract() {
let s1 = make_bin_rect(0, 19, 0, 19);
let s2 = make_bin_rect(10, 29, 10, 29);
let mut result = ScanlineStorageBin::new();
let mut sl = ScanlineU8::new();
sbool_combine_storages_bin(SBoolOp::AMinusB, &s1, &s2, &mut sl, &mut result);
assert_eq!(result.num_scanlines(), 20);
}
#[test]
fn test_cover_math_intersect() {
assert_eq!(intersect_covers(255, 255), 255); assert_eq!(intersect_covers(0, 255), 0);
assert_eq!(intersect_covers(128, 128), 64); }
#[test]
fn test_cover_math_unite() {
assert_eq!(unite_covers(255, 255), 255);
assert_eq!(unite_covers(0, 0), 0);
assert_eq!(unite_covers(0, 255), 255); assert_eq!(unite_covers(128, 128), 191);
}
#[test]
fn test_cover_math_subtract() {
assert_eq!(subtract_covers(255, 0), 255); assert_eq!(subtract_covers(255, 255), 0);
assert_eq!(subtract_covers(0, 255), 0);
}
#[test]
fn test_cover_math_xor_linear() {
assert_eq!(xor_covers_linear(0, 0), 0);
assert_eq!(xor_covers_linear(255, 0), 255);
assert_eq!(xor_covers_linear(0, 255), 255);
assert_eq!(xor_covers_linear(255, 255), 0); }
#[test]
fn test_cover_math_xor_saddle() {
assert!(xor_covers_saddle(0, 0) <= 5); assert_eq!(xor_covers_saddle(255, 255), 0);
let saddle = xor_covers_saddle(128, 128);
assert!(saddle > 0 && saddle < 255);
let linear = xor_covers_linear(128, 128);
assert_ne!(saddle, linear);
}
#[test]
fn test_cover_math_xor_abs_diff() {
assert_eq!(xor_covers_abs_diff(0, 0), 0);
assert_eq!(xor_covers_abs_diff(255, 255), 0);
assert_eq!(xor_covers_abs_diff(200, 100), 100);
assert_eq!(xor_covers_abs_diff(100, 200), 100);
}
}