2022-02-16 08:33:48 +11:00
|
|
|
use palette16::Palette16OptimisationResults;
|
2021-07-22 07:46:22 +10:00
|
|
|
use proc_macro::TokenStream;
|
2021-07-23 03:43:27 +10:00
|
|
|
use syn::parse_macro_input;
|
2021-04-20 08:01:28 +10:00
|
|
|
|
2022-02-16 08:33:48 +11:00
|
|
|
use std::{
|
|
|
|
fs::File,
|
|
|
|
iter,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
process::Command,
|
|
|
|
str,
|
|
|
|
};
|
2021-06-06 02:45:21 +10:00
|
|
|
|
2021-07-27 08:27:16 +10:00
|
|
|
use quote::{format_ident, quote};
|
2021-07-23 04:03:49 +10:00
|
|
|
|
2022-02-16 08:28:21 +11:00
|
|
|
mod aseprite;
|
2021-04-20 08:15:03 +10:00
|
|
|
mod colour;
|
2021-07-22 07:46:22 +10:00
|
|
|
mod config;
|
2021-04-20 09:40:07 +10:00
|
|
|
mod image_loader;
|
|
|
|
mod palette16;
|
2021-04-21 05:41:04 +10:00
|
|
|
mod rust_generator;
|
2021-04-20 09:40:07 +10:00
|
|
|
|
|
|
|
use image_loader::Image;
|
2021-04-20 08:15:03 +10:00
|
|
|
|
2021-07-22 07:07:09 +10:00
|
|
|
use colour::Colour;
|
2021-04-20 08:01:28 +10:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
2021-07-22 07:07:09 +10:00
|
|
|
pub(crate) enum TileSize {
|
2021-04-20 08:01:28 +10:00
|
|
|
Tile8,
|
|
|
|
Tile16,
|
2021-10-31 04:22:09 +11:00
|
|
|
Tile32,
|
2021-04-20 08:01:28 +10:00
|
|
|
}
|
|
|
|
|
2021-04-20 09:40:07 +10:00
|
|
|
impl TileSize {
|
2021-04-21 07:56:47 +10:00
|
|
|
fn to_size(self) -> usize {
|
|
|
|
match self {
|
2021-04-20 09:40:07 +10:00
|
|
|
TileSize::Tile8 => 8,
|
|
|
|
TileSize::Tile16 => 16,
|
2021-10-31 04:22:09 +11:00
|
|
|
TileSize::Tile32 => 32,
|
2021-04-20 09:40:07 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-22 07:07:09 +10:00
|
|
|
#[proc_macro]
|
|
|
|
pub fn include_gfx(input: TokenStream) -> TokenStream {
|
2021-07-23 03:43:27 +10:00
|
|
|
let input = parse_macro_input!(input as syn::LitStr);
|
2021-06-06 02:47:13 +10:00
|
|
|
|
2021-07-23 03:43:27 +10:00
|
|
|
let filename = input.value();
|
2021-07-22 07:07:09 +10:00
|
|
|
|
|
|
|
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
|
|
|
|
let path = Path::new(&root).join(&*filename);
|
2021-07-22 07:46:22 +10:00
|
|
|
let parent = path
|
|
|
|
.parent()
|
|
|
|
.expect("Expected a parent directory for the path");
|
2021-07-22 07:07:09 +10:00
|
|
|
|
|
|
|
let config = config::parse(&path.to_string_lossy());
|
|
|
|
|
2021-07-27 08:27:16 +10:00
|
|
|
let module_name = format_ident!(
|
|
|
|
"{}",
|
|
|
|
path.file_stem()
|
|
|
|
.expect("Expected a file stem")
|
|
|
|
.to_string_lossy()
|
|
|
|
);
|
2021-07-23 04:03:49 +10:00
|
|
|
let include_path = path.to_string_lossy();
|
|
|
|
|
|
|
|
let images = config.images();
|
2021-07-27 08:27:16 +10:00
|
|
|
let image_code = images.iter().map(|(image_name, &image)| {
|
2022-02-16 08:29:16 +11:00
|
|
|
convert_image(image, parent, image_name, &config.crate_prefix())
|
2021-07-27 08:27:16 +10:00
|
|
|
});
|
2021-07-22 07:07:09 +10:00
|
|
|
|
2021-07-23 04:03:49 +10:00
|
|
|
let module = quote! {
|
2022-01-03 04:54:44 +11:00
|
|
|
mod #module_name {
|
2021-07-23 04:03:49 +10:00
|
|
|
const _: &[u8] = include_bytes!(#include_path);
|
|
|
|
|
|
|
|
#(#image_code)*
|
|
|
|
}
|
|
|
|
};
|
2021-07-22 07:07:09 +10:00
|
|
|
|
2021-07-23 04:03:49 +10:00
|
|
|
TokenStream::from(module)
|
2021-04-20 09:40:07 +10:00
|
|
|
}
|
|
|
|
|
2022-02-16 08:33:48 +11:00
|
|
|
#[proc_macro]
|
|
|
|
pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
|
|
|
|
let input = parse_macro_input!(input as syn::LitStr);
|
|
|
|
let filename = input.value();
|
|
|
|
|
|
|
|
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
|
|
|
|
let path = Path::new(&root).join(&*filename);
|
|
|
|
|
|
|
|
let out_dir = std::env::var("OUT_DIR").expect("Expected OUT_DIR");
|
|
|
|
|
|
|
|
let output_filename = Path::new(&out_dir).join(&*filename);
|
|
|
|
let image_output = output_filename.with_extension("png");
|
|
|
|
let json_output = output_filename.with_extension("json");
|
|
|
|
|
|
|
|
let command = Command::new("aseprite")
|
|
|
|
.args([
|
|
|
|
&PathBuf::from("-b"),
|
|
|
|
&path,
|
|
|
|
&"--sheet".into(),
|
|
|
|
&image_output,
|
|
|
|
&"--format".into(),
|
|
|
|
&"json-array".into(),
|
|
|
|
&"--data".into(),
|
|
|
|
&json_output,
|
|
|
|
&"--list-tags".into(),
|
|
|
|
])
|
|
|
|
.output()
|
|
|
|
.expect("Could not run aseprite");
|
|
|
|
assert!(
|
|
|
|
command.status.success(),
|
|
|
|
"Aseprite did not complete successfully : {}",
|
|
|
|
str::from_utf8(&*command.stdout).unwrap_or("Output contains invalid string")
|
|
|
|
);
|
|
|
|
|
|
|
|
let json: aseprite::Aseprite = serde_json::from_reader(
|
|
|
|
File::open(&json_output).expect("The json output from aseprite could not be openned"),
|
|
|
|
)
|
|
|
|
.expect("The output from aseprite could not be decoded");
|
|
|
|
|
|
|
|
// check that the size of the sprites are valid
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
json.frames[0].frame.w == json.frames[0].frame.h
|
|
|
|
&& json.frames[0].frame.w.is_power_of_two()
|
|
|
|
&& json.frames[0].frame.w <= 32
|
|
|
|
);
|
|
|
|
|
|
|
|
let image = Image::load_from_file(image_output.as_path());
|
|
|
|
|
|
|
|
let optimised_results =
|
|
|
|
optimiser_for_image(&image, json.frames[0].frame.w as usize).optimise_palettes(None);
|
|
|
|
|
|
|
|
let (palette_data, tile_data, assignments) =
|
|
|
|
palete_tile_data(&optimised_results, json.frames[0].frame.w as usize, &image);
|
|
|
|
|
|
|
|
let palette_data = palette_data.iter().map(|colours| {
|
|
|
|
quote! {
|
|
|
|
Palette16::new([
|
|
|
|
#(#colours),*
|
|
|
|
])
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let mut pre = 0;
|
|
|
|
let sprites = json
|
|
|
|
.frames
|
|
|
|
.iter()
|
|
|
|
.zip(assignments.iter())
|
|
|
|
.map(|(f, assignment)| {
|
|
|
|
let start: usize = pre;
|
|
|
|
let end: usize = pre + (f.frame.w as usize / 8) * (f.frame.h as usize / 8) * 32;
|
|
|
|
let data = &tile_data[start..end];
|
|
|
|
pre = end;
|
|
|
|
let width = f.frame.w as usize;
|
|
|
|
let height = f.frame.h as usize;
|
|
|
|
quote! {
|
|
|
|
Sprite::new(
|
|
|
|
&PALETTES[#assignment],
|
|
|
|
&[
|
|
|
|
#(#data),*
|
|
|
|
],
|
|
|
|
Size::from_width_height(#width, #height)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let tags = json.meta.frame_tags.iter().map(|tag| {
|
|
|
|
let start = tag.from as usize;
|
|
|
|
let end = tag.to as usize;
|
|
|
|
let direction = tag.direction as usize;
|
|
|
|
|
|
|
|
let name = &tag.name;
|
|
|
|
assert!(start <= end, "Tag {} has start > end", name);
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#name => Tag::new(SPRITES, #start, #end, #direction)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let include_path = path.to_string_lossy();
|
|
|
|
|
|
|
|
let module = quote! {
|
|
|
|
const _: &[u8] = include_bytes!(#include_path);
|
|
|
|
|
|
|
|
|
|
|
|
const PALETTES: &[Palette16] = &[
|
|
|
|
#(#palette_data),*
|
|
|
|
];
|
|
|
|
|
|
|
|
pub const SPRITES: &[Sprite] = &[
|
|
|
|
#(#sprites),*
|
|
|
|
];
|
|
|
|
|
|
|
|
const TAGS: &TagMap = &TagMap::new(
|
|
|
|
phf::phf_map! {
|
|
|
|
#(#tags),*
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
TokenStream::from(module)
|
|
|
|
}
|
|
|
|
|
2021-07-22 07:46:22 +10:00
|
|
|
fn convert_image(
|
|
|
|
settings: &dyn config::Image,
|
|
|
|
parent: &Path,
|
|
|
|
variable_name: &str,
|
|
|
|
crate_prefix: &str,
|
2021-07-23 04:04:30 +10:00
|
|
|
) -> proc_macro2::TokenStream {
|
2021-07-22 07:07:09 +10:00
|
|
|
let image_filename = &parent.join(&settings.filename());
|
|
|
|
let image = Image::load_from_file(image_filename);
|
2021-04-20 09:40:07 +10:00
|
|
|
|
2021-07-22 07:07:09 +10:00
|
|
|
let tile_size = settings.tilesize().to_size();
|
2021-04-20 09:40:07 +10:00
|
|
|
if image.width % tile_size != 0 || image.height % tile_size != 0 {
|
|
|
|
panic!("Image size not a multiple of tile size");
|
|
|
|
}
|
|
|
|
|
|
|
|
let optimiser = optimiser_for_image(&image, tile_size);
|
2021-07-22 07:07:09 +10:00
|
|
|
let optimisation_results = optimiser.optimise_palettes(settings.transparent_colour());
|
2021-04-20 09:40:07 +10:00
|
|
|
|
2021-04-21 05:41:04 +10:00
|
|
|
rust_generator::generate_code(
|
2021-07-22 07:07:09 +10:00
|
|
|
variable_name,
|
2021-04-21 05:41:04 +10:00
|
|
|
&optimisation_results,
|
|
|
|
&image,
|
2021-07-22 07:07:09 +10:00
|
|
|
&image_filename.to_string_lossy(),
|
|
|
|
settings.tilesize(),
|
|
|
|
crate_prefix.to_owned(),
|
2021-07-27 08:27:16 +10:00
|
|
|
)
|
2021-04-20 08:01:28 +10:00
|
|
|
}
|
|
|
|
|
2021-04-20 09:40:07 +10:00
|
|
|
fn optimiser_for_image(image: &Image, tile_size: usize) -> palette16::Palette16Optimiser {
|
|
|
|
let tiles_x = image.width / tile_size;
|
|
|
|
let tiles_y = image.height / tile_size;
|
|
|
|
|
|
|
|
let mut palette_optimiser = palette16::Palette16Optimiser::new();
|
|
|
|
|
|
|
|
for y in 0..tiles_y {
|
|
|
|
for x in 0..tiles_x {
|
|
|
|
let mut palette = palette16::Palette16::new();
|
|
|
|
|
|
|
|
for j in 0..tile_size {
|
|
|
|
for i in 0..tile_size {
|
|
|
|
let colour = image.colour(x * tile_size + i, y * tile_size + j);
|
|
|
|
|
2021-04-20 09:57:47 +10:00
|
|
|
palette.add_colour(colour);
|
2021-04-20 09:40:07 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
palette_optimiser.add_palette(palette);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
palette_optimiser
|
|
|
|
}
|
2022-02-16 08:33:48 +11:00
|
|
|
|
|
|
|
fn palete_tile_data(
|
|
|
|
optimiser: &Palette16OptimisationResults,
|
|
|
|
tile_size: usize,
|
|
|
|
image: &Image,
|
|
|
|
) -> (Vec<Vec<u16>>, Vec<u8>, Vec<usize>) {
|
|
|
|
let palette_data: Vec<Vec<u16>> = optimiser
|
|
|
|
.optimised_palettes
|
|
|
|
.iter()
|
|
|
|
.map(|palette| {
|
|
|
|
palette
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.map(|colour| colour.to_rgb15())
|
|
|
|
.chain(iter::repeat(0))
|
|
|
|
.take(16)
|
|
|
|
.map(|colour| colour as u16)
|
|
|
|
.collect()
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let tiles_x = image.width / tile_size;
|
|
|
|
let tiles_y = image.height / tile_size;
|
|
|
|
|
|
|
|
let mut tile_data = vec![];
|
|
|
|
|
|
|
|
for y in 0..tiles_y {
|
|
|
|
for x in 0..tiles_x {
|
|
|
|
let palette_index = optimiser.assignments[y * tiles_x + x];
|
|
|
|
let palette = &optimiser.optimised_palettes[palette_index];
|
|
|
|
|
|
|
|
for inner_y in 0..tile_size / 8 {
|
|
|
|
for inner_x in 0..tile_size / 8 {
|
|
|
|
for j in inner_y * 8..inner_y * 8 + 8 {
|
|
|
|
for i in inner_x * 8..inner_x * 8 + 8 {
|
|
|
|
let colour = image.colour(x * tile_size + i, y * tile_size + j);
|
|
|
|
tile_data.push(palette.colour_index(colour));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let tile_data = tile_data
|
|
|
|
.chunks(2)
|
|
|
|
.map(|chunk| chunk[0] | (chunk[1] << 4))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let assignments = optimiser.assignments.clone();
|
|
|
|
|
|
|
|
(palette_data, tile_data, assignments)
|
|
|
|
}
|