Skip to content

Commit

Permalink
Deduplicate background tiles (#477)
Browse files Browse the repository at this point in the history
This started as an attempt to get dungeon puzzler in the combo rom,
ended up fixing a _load_ of 256 colour bugs and the ability to
deduplicate tiles you import.

Fixes #448

- [x] Changelog updated / no changelog update needed
  • Loading branch information
gwilymk authored Aug 29, 2023
2 parents dd11960 + 6422ed6 commit dc5bf9d
Show file tree
Hide file tree
Showing 24 changed files with 711 additions and 201 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New tracker for playing XM files (see the `agb-tracker` crate).
- You can now declare where looping sound channels should restart.
- Fixnums now have constructors from_f32 and from_f64. This is mainly useful if using agb-fixnum outside of the Game Boy Advance e.g. in build scripts or macros.
- New option when loading a background to automatically deduplicate tiles.
- Methods on tile_setting to toggle its hflip and vflip status.

### Changed

- Sound channel panning and volume options are now `Num<i16, 8>` rather than `Num<i16, 4>` for improved precision and sound quality.
- Due to dependency changes, agb-gbafix is now released under MPL rather than GPL.
- `include_background_gfx!` now produces tile settings directly rather than palette assigments.

### Fixed

- 256-colour backgrounds are better supported.
- Mono looping samples will now correctly play to the end if it doesn't perfectly align with a buffer boundry and short samples now also loop correctly.

## [0.16.0] - 2023/07/18
Expand Down
1 change: 1 addition & 0 deletions agb-image-converter/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ pub(crate) trait Config {
pub(crate) trait Image {
fn filename(&self) -> String;
fn colours(&self) -> Colours;
fn deduplicate(&self) -> bool;
}
152 changes: 152 additions & 0 deletions agb-image-converter/src/deduplicator.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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..4 {
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..4 {
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<DeduplicatedData>) {
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::<Vec<_>>();
(Image::from_colour_data(image_data), deduplication_data)
}
9 changes: 9 additions & 0 deletions agb-image-converter/src/image_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use image::{DynamicImage, GenericImageView};

use crate::colour::Colour;

#[derive(Clone)]
pub(crate) struct Image {
pub width: usize,
pub height: usize,
Expand Down Expand Up @@ -42,6 +43,14 @@ impl Image {
}
}

pub fn from_colour_data(colour_data: Vec<Colour>) -> Self {
Self {
height: colour_data.len() / 8,
colour_data,
width: 8,
}
}

pub fn colour(&self, x: usize, y: usize) -> Colour {
self.colour_data[x + y * self.width]
}
Expand Down
38 changes: 36 additions & 2 deletions agb-image-converter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use quote::{format_ident, quote, ToTokens};
mod aseprite;
mod colour;
mod config;
mod deduplicator;
mod font_loader;
mod image_loader;
mod palette16;
Expand All @@ -36,6 +37,7 @@ struct BackgroundGfxOption {
module_name: String,
file_name: String,
colours: Colours,
deduplicate: bool,
}

impl config::Image for BackgroundGfxOption {
Expand All @@ -46,6 +48,10 @@ impl config::Image for BackgroundGfxOption {
fn colours(&self) -> Colours {
self.colours
}

fn deduplicate(&self) -> bool {
self.deduplicate
}
}

impl Parse for BackgroundGfxOption {
Expand All @@ -72,12 +78,30 @@ impl Parse for BackgroundGfxOption {
Colours::Colours16
};

let lookahead = input.lookahead1();

let deduplicate = if lookahead.peek(syn::Ident) {
let deduplicate: syn::Ident = input.parse()?;

if deduplicate == "deduplicate" {
true
} else {
return Err(syn::Error::new_spanned(
deduplicate,
"Must either be the literal deduplicate or missing",
));
}
} else {
false
};

let file_name: syn::LitStr = input.parse()?;

Ok(Self {
module_name: module_name.to_string(),
file_name: file_name.value(),
colours,
deduplicate,
})
}
}
Expand Down Expand Up @@ -406,6 +430,7 @@ fn convert_image(
) -> proc_macro2::TokenStream {
let image_filename = &parent.join(settings.filename());
let image = Image::load_from_file(image_filename);
let deduplicate = settings.deduplicate();

rust_generator::generate_code(
variable_name,
Expand All @@ -414,6 +439,7 @@ fn convert_image(
&image_filename.to_string_lossy(),
crate_prefix.to_owned(),
assignment_offset,
deduplicate,
)
}

Expand Down Expand Up @@ -468,7 +494,14 @@ fn palette_tile_data(
let mut tile_data = Vec::new();

for (image_idx, image) in images.iter().enumerate() {
add_image_to_tile_data(&mut tile_data, image, optimiser, image_idx, true)
add_image_to_tile_data(
&mut tile_data,
image,
optimiser,
image_idx,
true,
&(0..images.len()).collect::<Vec<_>>(),
);
}

let tile_data = collapse_to_4bpp(&tile_data);
Expand All @@ -491,6 +524,7 @@ fn add_image_to_tile_data(
optimiser: &Palette16OptimisationResults,
assignment_offset: usize,
is_sprite: bool,
remap_index: &[usize],
) {
let tile_size = 8;
let tiles_x = image.width / tile_size;
Expand All @@ -501,7 +535,7 @@ fn add_image_to_tile_data(
let assignment = if is_sprite {
assignment_offset
} else {
y * tiles_x + x + assignment_offset
remap_index[y * tiles_x + x] + assignment_offset
};

let palette_index = optimiser.assignments[assignment];
Expand Down
Loading

0 comments on commit dc5bf9d

Please sign in to comment.