Skip to content

Commit

Permalink
Sort Pokemon and trainer sprite palettes, with Makefile-specified exc…
Browse files Browse the repository at this point in the history
…eptions

This avoids the need to define their order via indexed PNG palettes

It also avoids the need to use tools/palfix.py on custom sprites

Fixes #1136
  • Loading branch information
Rangi42 committed Oct 7, 2024
1 parent a69cfb1 commit 91c0898
Show file tree
Hide file tree
Showing 7 changed files with 493 additions and 260 deletions.
46 changes: 41 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,50 @@ gfx/pokemon/girafarig/front.animated.tilemap: gfx/pokemon/girafarig/front.2bpp g
tools/pokemon_animation_graphics --girafarig -t $@ $^


### Misc file-specific graphics rules
### Pokemon and trainer sprite rules

gfx/pokemon/%/back.2bpp: rgbgfx += --columns
gfx/pokemon/%/back.2bpp: gfx/pokemon/%/back.png gfx/pokemon/%/normal.gbcpal
$(RGBGFX) $(rgbgfx) --colors gbc:$(word 2,$^) -o $@ $<
gfx/pokemon/%/front.2bpp: gfx/pokemon/%/front.png gfx/pokemon/%/normal.gbcpal
$(RGBGFX) $(rgbgfx) --colors gbc:$(word 2,$^) -o $@ $<
gfx/pokemon/%/normal.gbcpal: gfx/pokemon/%/front.gbcpal gfx/pokemon/%/back.gbcpal
tools/gbcpal $(tools/gbcpal) $@ $^

gfx/trainers/%.2bpp: rgbgfx += --columns
gfx/trainers/%.2bpp: gfx/trainers/%.png gfx/trainers/%.gbcpal
$(RGBGFX) $(rgbgfx) --colors gbc:$(word 2,$^) -o $@ $<

# Egg does not have a back sprite, so it only uses front.gbcpal
gfx/pokemon/egg/front.2bpp: gfx/pokemon/egg/front.png gfx/pokemon/egg/front.gbcpal
gfx/pokemon/egg/front.2bpp: rgbgfx += --colors gbc:$(word 2,$^)

gfx/pokemon/%/back.2bpp: rgbgfx += --columns --colors embedded
gfx/pokemon/%/front.2bpp: rgbgfx += --colors embedded
# Unown letters share one normal.pal, so they don't already build each normal.gbcpal
$(foreach png, $(wildcard gfx/pokemon/unown_*/front.png),\
$(eval $(png:.png=.2bpp): $(png) $(png:front.png=normal.gbcpal)))
gfx/pokemon/unown_%/front.2bpp: rgbgfx += --colors gbc:$(@:front.2bpp=normal.gbcpal)

gfx/trainers/%.2bpp: rgbgfx += --columns --colors embedded

### Misc file-specific graphics rules

gfx/pokemon/egg/unused_front.2bpp: rgbgfx += --columns

gfx/pokemon/caterpie/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/diglett/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/dugtrio/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/farfetch_d/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/fearow/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/jynx/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/magnemite/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/magneton/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/marill/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/porygon2/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/scyther/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/shellder/normal.gbcpal: tools/gbcpal += --reverse
gfx/pokemon/spearow/normal.gbcpal: tools/gbcpal += --reverse

gfx/trainers/swimmer_m.gbcpal: tools/gbcpal += --reverse

gfx/new_game/shrink1.2bpp: rgbgfx += --columns
gfx/new_game/shrink2.2bpp: rgbgfx += --columns

Expand Down Expand Up @@ -315,7 +350,8 @@ gfx/mobile/stadium2_n64.2bpp: tools/gfx += --trim-whitespace
tools/gfx $(tools/gfx) --depth 1 -o $@ $@)

%.gbcpal: %.png
$(RGBGFX) --colors embedded -p $@ $<
$(RGBGFX) -p $@ $<
tools/gbcpal $(tools/gbcpal) $@ $@

%.dimensions: %.png
tools/png_dimensions $< $@
508 changes: 254 additions & 254 deletions data/pokemon/palettes.asm

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tools/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
gbcpal
gfx
lzcomp
make_patch
Expand Down
2 changes: 2 additions & 0 deletions tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CFLAGS := -O3 -flto -std=c11 -Wall -Wextra -pedantic

tools := \
lzcomp \
gbcpal \
gfx \
make_patch \
png_dimensions \
Expand All @@ -19,6 +20,7 @@ all: $(tools)
clean:
$(RM) $(tools)

gbcpal: common.h
gfx: common.h
png_dimensions: common.h
pokemon_animation: common.h
Expand Down
154 changes: 154 additions & 0 deletions tools/gbcpal.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#define PROGRAM_NAME "gbcpal"
#define USAGE_OPTS "[-h|--help] [-r|--reverse] out.gbcpal in.gbcpal..."

#include "common.h"

bool reverse;

void parse_args(int argc, char *argv[]) {
struct option long_options[] = {
{"reverse", no_argument, 0, 'r'},
{"help", no_argument, 0, 'h'},
{0}
};
for (int opt; (opt = getopt_long(argc, argv, "rh", long_options)) != -1;) {
switch (opt) {
case 'r':
reverse = true;
break;
case 'h':
usage_exit(0);
break;
default:
usage_exit(1);
}
}
}

struct Color {
uint8_t r, g, b;
};

uint16_t pack_color(struct Color color) {
return (color.b << 10) | (color.g << 5) | color.r;
}

struct Color unpack_color(uint16_t gbc_color) {
return (struct Color){
.r = gbc_color & 0x1f,
.g = (gbc_color >> 5) & 0x1f,
.b = (gbc_color >> 10) & 0x1f,
};
}

double luminance(struct Color color) {
return 0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b;
}

int compare_colors(const void *color1, const void *color2) {
double lum1 = luminance(*(const struct Color *)color1);
double lum2 = luminance(*(const struct Color *)color2);
return (lum1 < lum2) - (lum1 > lum2); // sort lightest to darkest
}

void read_gbcpal(const char *filename, struct Color **colors, size_t *num_colors) {
long filesize;
uint8_t *bytes = read_u8(filename, &filesize);
if (filesize == 0) {
error_exit("%s: empty gbcpal file\n", filename);
}
if (filesize % 2) {
error_exit("%s: invalid gbcpal file\n", filename);
}

size_t new_colors = filesize / 2;
*colors = xrealloc(*colors, (sizeof **colors) * (*num_colors + new_colors));
for (size_t i = 0; i < new_colors; i++) {
uint16_t gbc_color = (bytes[i * 2 + 1] << 8) | bytes[i * 2];
(*colors)[*num_colors + i] = unpack_color(gbc_color);
}
*num_colors += new_colors;

free(bytes);
}

void filter_colors(struct Color **colors, size_t *num_colors) {
size_t num_filtered = 0;
struct Color *filtered = xmalloc((sizeof **colors) * *num_colors);

for (size_t i = 0; i < *num_colors; i++) {
struct Color color = (*colors)[i];
if (color.r == 0 && color.g == 0 && color.b == 0) {
continue; // filter out black
}
if (color.r == 31 && color.g == 31 && color.b == 31) {
continue; // filter out white
}
if (num_filtered > 0) {
struct Color last = filtered[num_filtered - 1];
if (color.r == last.r && color.g == last.g && color.b == last.b) {
continue; // filter out duplicates
}
}
filtered[num_filtered++] = color;
}

free(*colors);
*num_colors = num_filtered;
*colors = filtered;
}

int main(int argc, char *argv[]) {
parse_args(argc, argv);

argc -= optind;
argv += optind;
if (argc < 2) {
usage_exit(1);
}

const char *out_filename = argv[0];

struct Color *colors = NULL;
size_t num_colors = 0;
for (int i = 1; i < argc; i++) {
read_gbcpal(argv[i], &colors, &num_colors);
}

qsort(colors, num_colors, sizeof(*colors), compare_colors);
filter_colors(&colors, &num_colors);

uint16_t first, second;
if (num_colors == 0) {
// colors are white and black
first = 0x7fff;
second = 0x0000;
} else if (num_colors == 1) {
// colors are both the same one
first = pack_color(colors[0]);
second = first;
} else if (num_colors == 2) {
// colors are light and dark
first = pack_color(colors[0]);
second = pack_color(colors[1]);
if (reverse) {
// darker color is first
uint16_t tmp = first;
first = second;
second = tmp;
}
} else {
error_exit("%s: more than 2 colors besides black and white (%zu)\n", out_filename, num_colors);
}

uint8_t bytes[8] = {
0xff, 0x7f, // white
first & 0xff, first >> 8,
second & 0xff, second >> 8,
0x00, 0x00, // black
};
write_u8(out_filename, bytes, COUNTOF(bytes));

free(colors);
return 0;
}
2 changes: 1 addition & 1 deletion tools/make_patch.c
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ struct Buffer *process_template(const char *template_filename, const char *patch
int compare_patch(const void *patch1, const void *patch2) {
unsigned int offset1 = ((const struct Patch *)patch1)->offset;
unsigned int offset2 = ((const struct Patch *)patch2)->offset;
return offset1 > offset2 ? 1 : offset1 < offset2 ? -1 : 0;
return (offset1 > offset2) - (offset1 < offset2);
}

bool verify_completeness(FILE *restrict orig_rom, FILE *restrict new_rom, struct Buffer *patches) {
Expand Down
40 changes: 40 additions & 0 deletions tools/revpals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Usage: python revpals.py
List Pokemon and trainer sprites with reversed palettes (darker color first).
"""

import sys
import glob

import png

def luminance(c):
r, g, b = c
return 0.299 * r**2 + 0.587 * g**2 + 0.114 * b**2

def is_reversed(filename):
with open(filename, 'rb') as file:
reader = png.Reader(file)
reader.preamble()
try:
palette = [c[:3] for c in reader.palette()]
except:
return False
if len(palette) != 4 or palette[0] != (255, 255, 255) or palette[3] != (0, 0, 0):
return False
return luminance(palette[1]) < luminance(palette[2])

def main():
for filename in sorted(glob.glob('gfx/pokemon/*/front.png')):
if is_reversed(filename):
print(filename)
for filename in sorted(glob.glob('gfx/trainers/*.png')):
if is_reversed(filename):
print(filename)

if __name__ == '__main__':
main()

0 comments on commit 91c0898

Please sign in to comment.