diff --git a/agb-image-converter/src/config.rs b/agb-image-converter/src/config.rs index bccb252d..eaa2cd20 100644 --- a/agb-image-converter/src/config.rs +++ b/agb-image-converter/src/config.rs @@ -23,20 +23,19 @@ pub(crate) fn parse(filename: &str) -> Box { pub(crate) trait Config { fn crate_prefix(&self) -> String; fn images(&self) -> HashMap; - fn colours(&self) -> Colours; } pub(crate) trait Image { fn filename(&self) -> String; fn transparent_colour(&self) -> Option; fn tilesize(&self) -> TileSize; + fn colours(&self) -> Colours; } #[derive(Deserialize)] pub struct ConfigV1 { version: String, crate_prefix: Option, - colours: Option, image: HashMap, } @@ -54,14 +53,6 @@ impl Config for ConfigV1 { .map(|(filename, image)| (filename.clone(), image as &dyn Image)) .collect() } - - fn colours(&self) -> Colours { - match self.colours { - None | Some(16) => Colours::Colours16, - Some(256) => Colours::Colours256, - _ => panic!("colours must either not be set or 16 or 256"), - } - } } #[derive(Deserialize)] @@ -69,6 +60,7 @@ pub struct ImageV1 { filename: String, transparent_colour: Option, tile_size: TileSizeV1, + colours: Option, } impl Image for ImageV1 { @@ -95,6 +87,14 @@ impl Image for ImageV1 { fn tilesize(&self) -> TileSize { self.tile_size.into() } + + fn colours(&self) -> Colours { + match self.colours { + None | Some(16) => Colours::Colours16, + Some(256) => Colours::Colours256, + _ => panic!("colours must either not be set or 16 or 256"), + } + } } #[derive(Deserialize, Clone, Copy)] diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs index ee0c5b57..235de782 100644 --- a/agb-image-converter/src/lib.rs +++ b/agb-image-converter/src/lib.rs @@ -1,4 +1,5 @@ use palette16::{Palette16OptimisationResults, Palette16Optimiser}; +use palette256::Palette256; use proc_macro::TokenStream; use proc_macro2::Literal; use syn::parse::Parser; @@ -17,6 +18,7 @@ mod config; mod font_loader; mod image_loader; mod palette16; +mod palette256; mod rust_generator; use image::GenericImageView; @@ -74,28 +76,38 @@ pub fn include_gfx(input: TokenStream) -> TokenStream { let mut assignment_offsets = HashMap::new(); let mut assignment_offset = 0; + let mut palette256 = Palette256::new(); + for (name, settings) in images.iter() { let image_filename = &parent.join(&settings.filename()); let image = Image::load_from_file(image_filename); - let tile_size = settings.tilesize().to_size(); - if image.width % tile_size != 0 || image.height % tile_size != 0 { - panic!("Image size not a multiple of tile size"); + match settings.colours() { + Colours::Colours16 => { + let tile_size = settings.tilesize().to_size(); + if image.width % tile_size != 0 || image.height % tile_size != 0 { + panic!("Image size not a multiple of tile size"); + } + + add_to_optimiser( + &mut optimiser, + &image, + tile_size, + settings.transparent_colour(), + ); + + let num_tiles = image.width * image.height / settings.tilesize().to_size().pow(2); + assignment_offsets.insert(name, assignment_offset); + assignment_offset += num_tiles; + } + Colours::Colours256 => { + palette256.add_image(&image); + } } - - add_to_optimiser( - &mut optimiser, - &image, - tile_size, - settings.transparent_colour(), - ); - - let num_tiles = image.width * image.height / settings.tilesize().to_size().pow(2); - assignment_offsets.insert(name, assignment_offset); - assignment_offset += num_tiles; } let optimisation_results = optimiser.optimise_palettes(); + let optimisation_results = palette256.extend_results(&optimisation_results); let mut image_code = vec![]; diff --git a/agb-image-converter/src/palette16.rs b/agb-image-converter/src/palette16.rs index 01d0af0f..7cf1def4 100644 --- a/agb-image-converter/src/palette16.rs +++ b/agb-image-converter/src/palette16.rs @@ -28,6 +28,19 @@ impl Palette16 { true } + 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 + } + pub fn colour_index(&self, colour: Colour, transparent_colour: Option) -> u8 { let colour_to_search = match (transparent_colour, colour.is_transparent()) { (Some(transparent_colour), true) => transparent_colour, @@ -45,6 +58,10 @@ impl Palette16 { }) as u8 } + pub fn colours<'a>(&'a self) -> impl Iterator + 'a { + self.colours.iter() + } + fn union_length(&self, other: &Palette16) -> usize { self.colours .iter() diff --git a/agb-image-converter/src/palette256.rs b/agb-image-converter/src/palette256.rs new file mode 100644 index 00000000..fc9b47ff --- /dev/null +++ b/agb-image-converter/src/palette256.rs @@ -0,0 +1,72 @@ +use std::{collections::HashSet, iter::FromIterator}; + +use crate::{ + colour::Colour, + image_loader::Image, + palette16::{Palette16, Palette16OptimisationResults}, +}; + +pub struct Palette256 { + colours: HashSet, +} + +impl Palette256 { + pub fn new() -> Self { + Self { + colours: HashSet::new(), + } + } + + pub(crate) fn add_image(&mut self, image: &Image) { + for y in 0..image.height { + for x in 0..image.width { + self.colours.insert(image.colour(x, y)); + } + } + + assert!( + self.colours.len() <= 256, + "Must have at most 256 colours in the palette" + ); + } + + pub(crate) fn extend_results( + &self, + palette16: &Palette16OptimisationResults, + ) -> Palette16OptimisationResults { + let optimised_palette_colours: Vec<_> = palette16 + .optimised_palettes + .iter() + .flat_map(|p| p.colours()) + .cloned() + .collect(); + + let current_colours_set = HashSet::from_iter(optimised_palette_colours.iter().cloned()); + let new_colours: HashSet<_> = self + .colours + .symmetric_difference(¤t_colours_set) + .collect(); + + assert!( + new_colours.len() + optimised_palette_colours.len() <= 256, + "Cannot optimise 16 colour and 256 colour palettes together, produces too many colours" + ); + + let mut new_palettes = palette16.optimised_palettes.clone(); + new_palettes.resize_with(16, Palette16::new); + + for colour in new_colours { + for palette in new_palettes.iter_mut() { + if palette.try_add_colour(*colour) { + break; + } + } + } + + Palette16OptimisationResults { + optimised_palettes: new_palettes, + assignments: palette16.assignments.clone(), + transparent_colour: palette16.transparent_colour, + } + } +}