2021-04-20 09:40:07 +10:00
|
|
|
use crate::colour::Colour;
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
const MAX_COLOURS: usize = 256;
|
|
|
|
const MAX_COLOURS_PER_PALETTE: usize = 16;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
|
|
pub(crate) struct Palette16 {
|
|
|
|
colours: Vec<Colour>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Palette16 {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Palette16 {
|
|
|
|
colours: Vec::with_capacity(MAX_COLOURS_PER_PALETTE),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_colour(&mut self, colour: Colour) -> bool {
|
|
|
|
if self.colours.contains(&colour) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.colours.len() == MAX_COLOURS_PER_PALETTE {
|
|
|
|
panic!("Can have at most 16 colours in a single palette");
|
|
|
|
}
|
|
|
|
self.colours.push(colour);
|
|
|
|
true
|
|
|
|
}
|
2022-05-23 04:23:29 +10:00
|
|
|
|
2022-08-12 07:47:52 +10:00
|
|
|
pub fn try_add_colour(&mut self, colour: Colour) -> bool {
|
|
|
|
if self.colours.contains(&colour) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.colours.len() == MAX_COLOURS_PER_PALETTE {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.colours.push(colour);
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2023-01-22 06:12:56 +11:00
|
|
|
pub fn colour_index(&self, colour: Colour) -> u8 {
|
|
|
|
// A transparent color is always index 0
|
|
|
|
if colour.is_transparent() {
|
|
|
|
return 0;
|
|
|
|
}
|
2022-05-23 04:23:29 +10:00
|
|
|
|
2021-04-21 05:41:04 +10:00
|
|
|
self.colours
|
|
|
|
.iter()
|
2023-01-22 06:12:56 +11:00
|
|
|
.position(|c| *c == colour)
|
2022-05-23 04:23:29 +10:00
|
|
|
.unwrap_or_else(|| {
|
|
|
|
panic!(
|
|
|
|
"Can't get a colour index without it existing, looking for {:?}, got {:?}",
|
|
|
|
colour, self.colours
|
|
|
|
)
|
|
|
|
}) as u8
|
2021-04-21 05:41:04 +10:00
|
|
|
}
|
2021-04-20 09:40:07 +10:00
|
|
|
|
2022-10-09 02:43:25 +11:00
|
|
|
pub fn colours(&self) -> impl Iterator<Item = &Colour> {
|
2022-08-12 07:47:52 +10:00
|
|
|
self.colours.iter()
|
|
|
|
}
|
|
|
|
|
2021-04-20 09:40:07 +10:00
|
|
|
fn union_length(&self, other: &Palette16) -> usize {
|
|
|
|
self.colours
|
|
|
|
.iter()
|
|
|
|
.chain(&other.colours)
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_satisfied_by(&self, other: &Palette16) -> bool {
|
|
|
|
self.colours
|
|
|
|
.iter()
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
.is_subset(&other.colours.iter().collect::<HashSet<_>>())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-21 05:41:04 +10:00
|
|
|
impl IntoIterator for Palette16 {
|
|
|
|
type Item = Colour;
|
|
|
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
self.colours.into_iter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-20 09:40:07 +10:00
|
|
|
pub(crate) struct Palette16Optimiser {
|
|
|
|
palettes: Vec<Palette16>,
|
|
|
|
colours: Vec<Colour>,
|
2022-05-23 04:23:29 +10:00
|
|
|
transparent_colour: Option<Colour>,
|
2021-04-20 09:40:07 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(crate) struct Palette16OptimisationResults {
|
|
|
|
pub optimised_palettes: Vec<Palette16>,
|
|
|
|
pub assignments: Vec<usize>,
|
2022-05-23 04:23:29 +10:00
|
|
|
pub transparent_colour: Option<Colour>,
|
2021-04-20 09:40:07 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Palette16Optimiser {
|
2022-05-23 04:23:29 +10:00
|
|
|
pub fn new(transparent_colour: Option<Colour>) -> Self {
|
2021-04-20 09:40:07 +10:00
|
|
|
Palette16Optimiser {
|
|
|
|
palettes: vec![],
|
|
|
|
colours: Vec::new(),
|
2022-05-23 04:23:29 +10:00
|
|
|
transparent_colour,
|
2021-04-20 09:40:07 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_palette(&mut self, palette: Palette16) {
|
|
|
|
self.palettes.push(palette.clone());
|
|
|
|
|
|
|
|
for colour in palette.colours {
|
|
|
|
if self.colours.contains(&colour) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.colours.push(colour);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.colours.len() > MAX_COLOURS {
|
|
|
|
panic!("Cannot have over 256 colours");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-23 04:23:29 +10:00
|
|
|
pub fn optimise_palettes(&self) -> Palette16OptimisationResults {
|
2021-04-20 09:40:07 +10:00
|
|
|
let mut assignments = vec![0; self.palettes.len()];
|
|
|
|
let mut optimised_palettes = vec![];
|
|
|
|
|
|
|
|
let mut unsatisfied_palettes = self
|
|
|
|
.palettes
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.collect::<HashSet<Palette16>>();
|
|
|
|
|
2021-04-21 07:56:47 +10:00
|
|
|
while !unsatisfied_palettes.is_empty() {
|
2022-05-23 04:23:29 +10:00
|
|
|
let palette = self.find_maximal_palette_for(&unsatisfied_palettes);
|
2021-04-20 09:40:07 +10:00
|
|
|
|
2023-05-07 05:31:43 +10:00
|
|
|
unsatisfied_palettes.retain(|test_palette| !test_palette.is_satisfied_by(&palette));
|
2021-04-20 09:40:07 +10:00
|
|
|
|
|
|
|
optimised_palettes.push(palette);
|
|
|
|
|
|
|
|
if optimised_palettes.len() == MAX_COLOURS / MAX_COLOURS_PER_PALETTE {
|
|
|
|
panic!("Failed to find covering palettes");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-07 05:31:43 +10:00
|
|
|
for (i, overall_palette) in self.palettes.iter().enumerate() {
|
|
|
|
assignments[i] = optimised_palettes
|
|
|
|
.iter()
|
|
|
|
.position(|palette| overall_palette.is_satisfied_by(palette))
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2021-04-20 09:40:07 +10:00
|
|
|
Palette16OptimisationResults {
|
|
|
|
optimised_palettes,
|
2021-04-21 07:56:47 +10:00
|
|
|
assignments,
|
2022-05-23 04:23:29 +10:00
|
|
|
transparent_colour: self.transparent_colour,
|
2021-04-20 09:40:07 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-23 04:23:29 +10:00
|
|
|
fn find_maximal_palette_for(&self, unsatisfied_palettes: &HashSet<Palette16>) -> Palette16 {
|
2021-04-20 09:40:07 +10:00
|
|
|
let mut palette = Palette16::new();
|
|
|
|
|
2022-10-09 02:43:25 +11:00
|
|
|
palette.add_colour(
|
|
|
|
self.transparent_colour
|
|
|
|
.unwrap_or_else(|| Colour::from_rgb(255, 0, 255, 0)),
|
|
|
|
);
|
2021-04-20 09:40:07 +10:00
|
|
|
|
|
|
|
loop {
|
|
|
|
let mut colour_usage = vec![0; MAX_COLOURS];
|
|
|
|
let mut a_colour_is_used = false;
|
|
|
|
|
|
|
|
for current_palette in unsatisfied_palettes {
|
2022-02-16 08:29:03 +11:00
|
|
|
if palette.union_length(current_palette) > MAX_COLOURS_PER_PALETTE {
|
2021-04-20 09:40:07 +10:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for colour in ¤t_palette.colours {
|
2022-02-16 08:29:03 +11:00
|
|
|
if palette.colours.contains(colour) {
|
2021-04-20 09:57:26 +10:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-04-20 09:40:07 +10:00
|
|
|
if let Some(colour_index) = self.colours.iter().position(|c| c == colour) {
|
|
|
|
colour_usage[colour_index] += 1;
|
|
|
|
a_colour_is_used = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !a_colour_is_used {
|
|
|
|
return palette;
|
|
|
|
}
|
|
|
|
|
|
|
|
let best_index = colour_usage
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.max_by(|(_, usage1), (_, usage2)| usage1.cmp(usage2))
|
|
|
|
.unwrap()
|
|
|
|
.0;
|
|
|
|
|
|
|
|
let best_colour = self.colours[best_index];
|
|
|
|
|
|
|
|
palette.add_colour(best_colour);
|
|
|
|
if palette.colours.len() == MAX_COLOURS_PER_PALETTE {
|
|
|
|
return palette;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-09-25 19:47:17 +10:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use quickcheck::{quickcheck, Arbitrary};
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
quickcheck! {
|
|
|
|
fn less_than_256_colours_always_fits(palettes: Vec<Palette16>) -> () {
|
|
|
|
let mut optimiser = Palette16Optimiser::new(None);
|
|
|
|
for palette in palettes.into_iter().take(16) {
|
|
|
|
optimiser.add_palette(palette);
|
|
|
|
}
|
|
|
|
|
|
|
|
optimiser.optimise_palettes();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Arbitrary for Palette16 {
|
|
|
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
|
|
|
let mut palette = Palette16::new();
|
|
|
|
|
|
|
|
let size: usize = Arbitrary::arbitrary(g);
|
|
|
|
let size = size.rem_euclid(16);
|
|
|
|
|
|
|
|
for _ in 0..size {
|
|
|
|
palette.add_colour(Arbitrary::arbitrary(g));
|
|
|
|
}
|
|
|
|
|
|
|
|
palette
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|