add BlendMap

This commit is contained in:
Gered 2022-07-13 07:34:36 -04:00
parent 100c02fc58
commit 7ee6df7d7c
2 changed files with 238 additions and 0 deletions

View file

@ -0,0 +1,236 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum BlendMapError {
#[error("Source color {0} is out of range for this BlendMap")]
InvalidSourceColor(u8),
}
/// A lookup table used by [`BlendMap`]s. This table stores destination color to blend color
/// mappings, where the indices are the destination colors and the values at those indices are the
/// blend colors.
pub type BlendMapping = [u8; 256];
/// A blend map containing a lookup table to match source colors with destination colors to
/// produce blended colors.
///
/// Some definitions:
/// * **source color**: colors in some source bitmap that is to be drawn onto a destination
/// * **destination color**: colors on the destination that will be drawn over by the source colors
/// * **blended color**: the final drawn color, found by looking up the source and destination colors
///
/// A blend map will not necessarily have mappings for all possible 256 source colors. But for each
/// source color, it will have 256 destination to blended color mappings.
#[derive(Clone)]
pub struct BlendMap {
start_color: u8,
end_color: u8,
mapping: Box<[BlendMapping]>,
}
impl BlendMap {
/// Creates and returns a new [`BlendMap`] with source color mappings for the given inclusive
/// range only. The `start_color` and `end_color` may also be equal to create a blend map with
/// only a single source color mapping.
pub fn new(start_color: u8, end_color: u8) -> Self {
let (start_color, end_color) = if start_color > end_color {
(end_color, start_color)
} else {
(start_color, end_color)
};
let num_colors = (end_color - start_color) as usize + 1;
BlendMap {
start_color,
end_color,
mapping: vec![[0u8; 256]; num_colors].into_boxed_slice(),
}
}
/// The beginning source color that is mapped in this blend map.
#[inline]
pub fn start_color(&self) -> u8 {
self.start_color
}
/// The ending source color that is mapped in this blend map.
#[inline]
pub fn end_color(&self) -> u8 {
self.end_color
}
/// Returns true if the given source color is mapped in this blend map.
#[inline]
pub fn is_mapped(&self, color: u8) -> bool {
color >= self.start_color && color <= self.end_color
}
#[inline]
fn get_mapping_index(&self, color: u8) -> Option<usize> {
if color >= self.start_color && color <= self.end_color {
let index = (color - self.start_color) as usize;
Some(index)
} else {
None
}
}
/// Returns a reference to the destination-to-blend color mapping table for the given source
/// color. Returns `None` if the specified source color is not in this blend map.
#[inline]
pub fn get_mapping(&self, color: u8) -> Option<&BlendMapping> {
if let Some(index) = self.get_mapping_index(color) {
// safety: index cannot be outside 0-255 since color and start_color are both u8
unsafe { Some(self.mapping.get_unchecked(index)) }
} else {
None
}
}
/// Returns a mutable reference to the destination-to-blend color mapping table for the given
/// source color. Returns `None` if the specified source color is not in this blend map.
#[inline]
pub fn get_mapping_mut(&mut self, color: u8) -> Option<&mut BlendMapping> {
if let Some(index) = self.get_mapping_index(color) {
// safety: index cannot be outside 0-255 since color and start_color are both u8
unsafe { Some(self.mapping.get_unchecked_mut(index)) }
} else {
None
}
}
/// Sets the blend color mapping for the given source color and destination color combination.
pub fn set_mapping(&mut self, source_color: u8, dest_color: u8, blended_color: u8) -> Result<(), BlendMapError> {
if let Some(mapping) = self.get_mapping_mut(source_color) {
mapping[dest_color as usize] = blended_color;
Ok(())
} else {
Err(BlendMapError::InvalidSourceColor(source_color))
}
}
/// Sets a series of blend color mappings for the given source color and starting from a base
/// destination color.
pub fn set_mappings<const N: usize>(&mut self, source_color: u8, base_dest_color: u8, mappings: [u8; N]) -> Result<(), BlendMapError> {
if let Some(mapping) = self.get_mapping_mut(source_color) {
assert!((base_dest_color as usize + N - 1) <= 255, "mappings array is too big for the remaining colors available");
for index in 0..N {
mapping[index + base_dest_color as usize] = mappings[index];
}
Ok(())
} else {
Err(BlendMapError::InvalidSourceColor(source_color))
}
}
/// Returns the blend color for the given source and destination colors. If the source color
/// is not in this blend map, `None` is returned.
#[inline]
pub fn blend(&self, source_color: u8, dest_color: u8) -> Option<u8> {
if let Some(mapping) = self.get_mapping(source_color) {
Some(mapping[dest_color as usize])
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use claim::*;
use super::*;
#[test]
pub fn create() -> Result<(), BlendMapError> {
let blend_map = BlendMap::new(10, 12);
assert_eq!(10, blend_map.start_color());
assert_eq!(12, blend_map.end_color());
assert!(blend_map.is_mapped(10));
assert!(blend_map.is_mapped(11));
assert!(blend_map.is_mapped(12));
assert!(!blend_map.is_mapped(9));
assert!(!blend_map.is_mapped(13));
assert_some!(blend_map.get_mapping(10));
assert_some!(blend_map.get_mapping(11));
assert_some!(blend_map.get_mapping(12));
assert_none!(blend_map.get_mapping(9));
assert_none!(blend_map.get_mapping(13));
let blend_map = BlendMap::new(12, 10);
assert_eq!(10, blend_map.start_color());
assert_eq!(12, blend_map.end_color());
assert!(blend_map.is_mapped(10));
assert!(blend_map.is_mapped(11));
assert!(blend_map.is_mapped(12));
assert!(!blend_map.is_mapped(9));
assert!(!blend_map.is_mapped(13));
assert_some!(blend_map.get_mapping(10));
assert_some!(blend_map.get_mapping(11));
assert_some!(blend_map.get_mapping(12));
assert_none!(blend_map.get_mapping(9));
assert_none!(blend_map.get_mapping(13));
let blend_map = BlendMap::new(130, 130);
assert_eq!(130, blend_map.start_color());
assert_eq!(130, blend_map.end_color());
assert!(blend_map.is_mapped(130));
assert!(!blend_map.is_mapped(129));
assert!(!blend_map.is_mapped(131));
assert_some!(blend_map.get_mapping(130));
assert_none!(blend_map.get_mapping(129));
assert_none!(blend_map.get_mapping(131));
Ok(())
}
#[test]
pub fn mapping() -> Result<(), BlendMapError> {
let mut blend_map = BlendMap::new(16, 31);
assert_none!(blend_map.blend(15, 0));
assert_eq!(Some(0), blend_map.blend(16, 0));
assert_eq!(Some(0), blend_map.blend(16, 1));
assert_ok!(blend_map.set_mapping(16, 0, 116));
assert_eq!(Some(116), blend_map.blend(16, 0));
assert_eq!(Some(0), blend_map.blend(16, 1));
let mapping = blend_map.get_mapping(16).unwrap();
assert_eq!(116, mapping[0]);
assert_eq!(0, mapping[1]);
assert_eq!(Some(0), blend_map.blend(17, 0));
assert_ok!(blend_map.set_mapping(17, 0, 117));
assert_eq!(Some(117), blend_map.blend(17, 0));
let mapping = blend_map.get_mapping_mut(17).unwrap();
assert_eq!(117, mapping[0]);
mapping[0] = 217;
assert_eq!(Some(217), blend_map.blend(17, 0));
assert_matches!(
blend_map.set_mapping(64, 1, 2),
Err(BlendMapError::InvalidSourceColor(64))
);
Ok(())
}
#[test]
pub fn bulk_mappings() -> Result<(), BlendMapError> {
let mut blend_map = BlendMap::new(0, 7);
let mapping = blend_map.get_mapping(2).unwrap();
assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], mapping[0..8]);
assert_ok!(blend_map.set_mappings(2, 4, [1, 2, 3, 4, 5, 6, 7, 8]));
let mapping = blend_map.get_mapping(2).unwrap();
assert_eq!(
[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0],
mapping[0..16]
);
Ok(())
}
}

View file

@ -1,10 +1,12 @@
pub use self::bitmap::*;
pub use self::bitmapatlas::*;
pub use self::blendmap::*;
pub use self::font::*;
pub use self::palette::*;
pub mod bitmap;
pub mod bitmapatlas;
pub mod blendmap;
pub mod font;
pub mod palette;