diff --git a/agb-image-converter/src/deduplicator.rs b/agb-image-converter/src/deduplicator.rs new file mode 100644 index 00000000..cb20608b --- /dev/null +++ b/agb-image-converter/src/deduplicator.rs @@ -0,0 +1,152 @@ +use std::{collections::HashMap, hash::BuildHasher}; + +use crate::{colour::Colour, image_loader::Image}; + +pub struct Transformation { + pub vflip: bool, + pub hflip: bool, +} + +impl Transformation { + pub fn none() -> Self { + Self { + vflip: false, + hflip: false, + } + } + + pub fn vflipped() -> Self { + Self { + vflip: true, + hflip: false, + } + } + + pub fn hflipped() -> Self { + Self { + vflip: false, + hflip: true, + } + } + + pub fn vhflipped() -> Self { + Self { + vflip: true, + hflip: true, + } + } +} + +pub struct DeduplicatedData { + pub new_index: usize, + pub transformation: Transformation, +} + +#[derive(Clone, PartialEq, Eq, Hash)] +struct Tile { + data: [Colour; 64], +} + +impl Tile { + fn split_image(input: &Image) -> Vec { + let mut ret = vec![]; + + for y in 0..(input.height / 8) { + for x in 0..(input.width / 8) { + let mut tile_data = Vec::with_capacity(64); + + for j in 0..8 { + for i in 0..8 { + tile_data.push(input.colour(x * 8 + i, y * 8 + j)); + } + } + + ret.push(Tile { + data: tile_data.try_into().unwrap(), + }); + } + } + + ret + } + + fn vflipped(&self) -> Self { + let mut new_data = self.data; + for y in 0..8 { + for x in 0..8 { + new_data.swap(y * 8 + x, (7 - y) * 8 + x); + } + } + + Self { data: new_data } + } + + fn hflipped(&self) -> Self { + let mut new_data = self.data; + + for y in 0..8 { + for x in 0..8 { + new_data.swap(y * 8 + x, y * 8 + (7 - x)); + } + } + + Self { data: new_data } + } +} + +pub(crate) fn deduplicate_image(input: &Image, can_flip: bool) -> (Image, Vec) { + let mut resulting_tiles = vec![]; + let mut deduplication_data = vec![]; + + let all_tiles = Tile::split_image(input); + let mut existing_tiles = HashMap::new(); + + let hasher = std::collections::hash_map::RandomState::new(); + + for tile in all_tiles { + let (tile, transformation) = if can_flip { + let vflipped = tile.vflipped(); + let hflipped = tile.hflipped(); + let vhflipped = vflipped.hflipped(); + + // find the one with the smallest hash + let tile_hash = hasher.hash_one(&tile); + let vflipped_hash = hasher.hash_one(&vflipped); + let hflipped_hash = hasher.hash_one(&hflipped); + let vhflipped_hash = hasher.hash_one(&vhflipped); + + let minimum = tile_hash + .min(vflipped_hash) + .min(hflipped_hash) + .min(vhflipped_hash); + + if minimum == tile_hash { + (tile, Transformation::none()) + } else if minimum == vflipped_hash { + (vflipped, Transformation::vflipped()) + } else if minimum == hflipped_hash { + (hflipped, Transformation::hflipped()) + } else { + (vhflipped, Transformation::vhflipped()) + } + } else { + (tile, Transformation::none()) + }; + + let index = *existing_tiles.entry(tile.clone()).or_insert_with(|| { + resulting_tiles.push(tile); + resulting_tiles.len() - 1 + }); + + deduplication_data.push(DeduplicatedData { + new_index: index, + transformation, + }); + } + + let image_data = resulting_tiles + .iter() + .flat_map(|tile| tile.data) + .collect::>(); + (Image::from_colour_data(image_data), deduplication_data) +} diff --git a/examples/hyperspace-roll/src/background.rs b/examples/hyperspace-roll/src/background.rs index 61035ce1..fa0ea199 100644 --- a/examples/hyperspace-roll/src/background.rs +++ b/examples/hyperspace-roll/src/background.rs @@ -6,11 +6,11 @@ use agb::{ use crate::sfx::Sfx; include_background_gfx!(backgrounds, "121105", - stars => "gfx/stars.aseprite", - title => "gfx/title-screen.aseprite", - help => "gfx/help-text.aseprite", - descriptions1 => "gfx/descriptions1.png", - descriptions2 => "gfx/descriptions2.png", + stars => deduplicate "gfx/stars.aseprite", + title => deduplicate "gfx/title-screen.aseprite", + help => deduplicate "gfx/help-text.aseprite", + descriptions1 => deduplicate "gfx/descriptions1.png", + descriptions2 => deduplicate "gfx/descriptions2.png", ); pub fn load_palettes(vram: &mut VRamManager) { @@ -35,12 +35,7 @@ pub(crate) fn load_help_text( vram, (x + at_tile.0, at_tile.1).into(), &help_tileset, - TileSetting::new( - tile_id, - false, - false, - backgrounds::help.palette_assignments[tile_id as usize], - ), + backgrounds::help.tile_settings[tile_id as usize], ) } } @@ -50,13 +45,13 @@ pub(crate) fn load_description( descriptions_map: &mut RegularMap, vram: &mut VRamManager, ) { - let (tileset, palette_assignments) = if face_id < 10 { + let (tileset, tile_settings) = if face_id < 10 { ( TileSet::new( backgrounds::descriptions1.tiles, agb::display::tiled::TileFormat::FourBpp, ), - backgrounds::descriptions1.palette_assignments, + backgrounds::descriptions1.tile_settings, ) } else { ( @@ -64,7 +59,7 @@ pub(crate) fn load_description( backgrounds::descriptions2.tiles, agb::display::tiled::TileFormat::FourBpp, ), - backgrounds::descriptions2.palette_assignments, + backgrounds::descriptions2.tile_settings, ) }; @@ -75,7 +70,7 @@ pub(crate) fn load_description( vram, (x, y).into(), &tileset, - TileSetting::new(tile_id, false, false, palette_assignments[tile_id as usize]), + tile_settings[tile_id as usize], ) } } @@ -87,16 +82,12 @@ fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_til for y in 0..32u16 { let blank = rng::gen().rem_euclid(32) < 30; - let (tile_id, palette_id) = if blank { - ((1 << 10) - 1, 0) + let tile_setting = if blank { + TileSetting::new((1 << 10) - 1, false, false, 0) } else { let tile_id = rng::gen().rem_euclid(64) as u16; - ( - tile_id, - backgrounds::stars.palette_assignments[tile_id as usize], - ) + backgrounds::stars.tile_settings[tile_id as usize] }; - let tile_setting = TileSetting::new(tile_id, false, false, palette_id); map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting); } @@ -121,12 +112,7 @@ pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sf vram, (x, y).into(), &tile_set, - TileSetting::new( - tile_id, - false, - false, - backgrounds::title.palette_assignments[tile_id as usize], - ), + backgrounds::title.tile_settings[tile_id as usize], ); }