From 6b7c0a894fe57dc3855bfa4a923f04011554a6a0 Mon Sep 17 00:00:00 2001 From: Dragorn421 Date: Tue, 4 Aug 2026 06:50:24 +0200 Subject: [PATCH] Add support for IA palettes (#2789) --- docs/assets/images.md | 7 ++- tools/assets/build_from_png/build_from_png.c | 51 +++++++++++--------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/docs/assets/images.md b/docs/assets/images.md index 2e95e06668..42cc6769b4 100644 --- a/docs/assets/images.md +++ b/docs/assets/images.md @@ -8,7 +8,7 @@ The build system will also pick up images in `assets/`, allowing modders to add PNG files have suffixes indicating how they are to be converted. For example, a `gDekuStickTex.i8.png` file will be converted to `i8`. -The valid formats are `rgba32`, `rgba16`, `i4`, `i8`, `ia4`, `ia8`, `ia16`, `ci4` and `ci8`. +The valid formats are `rgba32`, `rgba16`, `i4`, `i8`, `ia4`, `ia8`, `ia16`, `ci4`, `ci4_rgba16`, `ci4_ia16`, `ci8`, `ci8_rgba16`, `ci8_ia16`. An optional suffix can be used to indicate the element type of the C array written out, for example `.u32` in `gHylianMan1ShirtTex.i8.u32.png`. The valid array element types are `u64` and `u32`. If omitted, the element type defaults to `u64`. `u32` is only used for unaligned textures. @@ -38,6 +38,8 @@ make VERSION=gc-eu-mq-dbg CI (Color Indexed) images also have a palette or TLUT (Texture Look-Up Table). +The CI formats are `ci4`, `ci4_rgba16`, `ci4_ia16`, `ci8`, `ci8_rgba16`, `ci8_ia16`. The first three indicate a CI4 image. The last three indicate a CI8 image. The `_rgba16` and `_ia16` suffixes indicate that the palette is to be in RGBA16 or IA16 format respectively. By default (if using plain `ci4` or `ci8`) the palette is RGBA16. + PNG images to be converted to CI formats may have a `.tlut_gNameTLUT[_]` suffix indicating the name and element type (optional, defaults to u64) of the TLUT `gNameTLUT.tlut.rgba16[.].inc.c` file to write the palette to. If this suffix is omitted, the TLUT will be written to a `gNameTex.tlut.rgba16.inc.c` file named after the CI image. @@ -60,9 +62,6 @@ The build system (`build_from_png`) will find images sharing the same palette by In the matching case of shared palettes, all png files have the same palette, which is written out. Otherwise the images are automatically co-quantized and the resulting images and palette are written out. -Note the N64 supports CI images with IA16 palettes instead of RGBA16 palettes, but OoT doesn't have such textures. -For simplicity, CI images with IA16 palettes are not supported in the build system, and all CI images are assumed to use RGBA16 palettes. - ### Automatic Palette Generation `n64texconv` supports automatically generating palettes when converting a png without a stored palette to a CI formatted image. However note that this process makes a number of assumptions about alpha channel handling for RGBA16 palettes. If using a CI image with an alpha channel it is strongly recommended to pre-generate a palette externally to ensure the results are optimal for the particular use. `n64texconv` will convert the alpha channel to single-bit with a fixed threshold of 128 and set the rgb content of all transparent pixels to transparent black, prioritizing filling the generated palette with visible pixels. This comes at the cost of black texture samples bleeding into visible pixels when filtered. Especially for CI4, since there are only 16 channels it is inadvisable to rely on correct bilinear sampling adjacent to alpha pixels, as storing multiple transparent pixels with different rgb contents will starve the palette of visible colors. diff --git a/tools/assets/build_from_png/build_from_png.c b/tools/assets/build_from_png/build_from_png.c index 1bd21ab367..894127fbbf 100644 --- a/tools/assets/build_from_png/build_from_png.c +++ b/tools/assets/build_from_png/build_from_png.c @@ -16,23 +16,28 @@ #include "../n64texconv/src/libn64texconv/bin2c.h" #include "../n64texconv/src/libn64texconv/n64texconv.h" -#define NUM_FORMATS 9 +#define NUM_FORMATS 13 #define MAX_N_SUFFIXES 5 static const struct fmt_info { const char* name; int fmt; int siz; + int tlut_fmt; // for CI formats, the format of the tlut (RGBA or IA) } fmt_map[NUM_FORMATS] = { // clang-format off - { "i4", G_IM_FMT_I, G_IM_SIZ_4b, }, - { "i8", G_IM_FMT_I, G_IM_SIZ_8b, }, - { "ci4", G_IM_FMT_CI, G_IM_SIZ_4b, }, - { "ci8", G_IM_FMT_CI, G_IM_SIZ_8b, }, - { "ia4", G_IM_FMT_IA, G_IM_SIZ_4b, }, - { "ia8", G_IM_FMT_IA, G_IM_SIZ_8b, }, - { "ia16", G_IM_FMT_IA, G_IM_SIZ_16b, }, - { "rgba16", G_IM_FMT_RGBA, G_IM_SIZ_16b, }, - { "rgba32", G_IM_FMT_RGBA, G_IM_SIZ_32b, }, + { "i4", G_IM_FMT_I, G_IM_SIZ_4b, 0, }, + { "i8", G_IM_FMT_I, G_IM_SIZ_8b, 0, }, + { "ci4", G_IM_FMT_CI, G_IM_SIZ_4b, G_IM_FMT_RGBA, }, + { "ci4_rgba16", G_IM_FMT_CI, G_IM_SIZ_4b, G_IM_FMT_RGBA, }, + { "ci4_ia16", G_IM_FMT_CI, G_IM_SIZ_4b, G_IM_FMT_IA, }, + { "ci8", G_IM_FMT_CI, G_IM_SIZ_8b, G_IM_FMT_RGBA, }, + { "ci8_rgba16", G_IM_FMT_CI, G_IM_SIZ_8b, G_IM_FMT_RGBA, }, + { "ci8_ia16", G_IM_FMT_CI, G_IM_SIZ_8b, G_IM_FMT_IA, }, + { "ia4", G_IM_FMT_IA, G_IM_SIZ_4b, 0, }, + { "ia8", G_IM_FMT_IA, G_IM_SIZ_8b, 0, }, + { "ia16", G_IM_FMT_IA, G_IM_SIZ_16b, 0, }, + { "rgba16", G_IM_FMT_RGBA, G_IM_SIZ_16b, 0, }, + { "rgba32", G_IM_FMT_RGBA, G_IM_SIZ_32b, 0, }, // clang-format on }; @@ -255,7 +260,7 @@ static bool handle_non_ci(const char* png_p, const struct fmt_info* fmt, int ele bool success = true; // read png - struct n64_image* img = n64texconv_image_from_png(png_p, fmt->fmt, fmt->siz, G_IM_FMT_RGBA); + struct n64_image* img = n64texconv_image_from_png(png_p, fmt->fmt, fmt->siz, 0); if (img == NULL) { fprintf(stderr, "Could not read png %s\n", png_p); success = false; @@ -292,7 +297,7 @@ static bool handle_ci_single(const char* png_p, const struct fmt_info* fmt, enum } // read png - struct n64_image* img = n64texconv_image_from_png(png_p, fmt->fmt, fmt->siz, G_IM_FMT_RGBA); + struct n64_image* img = n64texconv_image_from_png(png_p, fmt->fmt, fmt->siz, fmt->tlut_fmt); if (img == NULL) { fprintf(stderr, "Could not read png %s\n", png_p); success = false; @@ -314,8 +319,9 @@ static bool handle_ci_single(const char* png_p, const struct fmt_info* fmt, enum png_p_buf[len_png_p_prefix] = '\0'; // cut off the suffixes char* png_stem = basename(png_p_buf); char* pal_inc_c_p = - malloc(strlen(out_dir_p) + strlen("/") + strlen(png_stem) + strlen(".tlut.rgba16.inc.c") + 1); - sprintf(pal_inc_c_p, "%s/%s.tlut.rgba16.inc.c", out_dir_p, png_stem); + malloc(strlen(out_dir_p) + strlen("/") + strlen(png_stem) + strlen(".tlut.xxxx16.inc.c") + 1); + sprintf(pal_inc_c_p, "%s/%s.tlut.%s16.inc.c", out_dir_p, png_stem, + fmt->tlut_fmt == G_IM_FMT_RGBA ? "rgba" : "ia"); free(png_p_buf); success = n64texconv_palette_to_c_file(pal_inc_c_p, img->pal, true, tlut_elem_size) == 0; @@ -369,8 +375,8 @@ static bool handle_ci_shared_tlut(const char* png_p, const struct fmt_info* fmt, if (direntry_fmt->fmt == G_IM_FMT_CI && direntry_tlut_name != NULL) { if (strequ(tlut_name, direntry_tlut_name)) { // TODO mismatching sub-format could be supported - if (direntry_fmt != fmt || direntry_subfmt != subfmt || - direntry_tlut_elem_size != tlut_elem_size) { + if (direntry_fmt->siz != fmt->siz || direntry_fmt->tlut_fmt != fmt->tlut_fmt || + direntry_subfmt != subfmt || direntry_tlut_elem_size != tlut_elem_size) { fprintf( stderr, "Images sharing TLUT \"%s\" have mismatching format, sub-format or tlut elem size:\n" @@ -419,7 +425,7 @@ static bool handle_ci_shared_tlut(const char* png_p, const struct fmt_info* fmt, bool success = true; // read "reference" png - struct n64_image* ref_img = n64texconv_image_from_png(png_p, G_IM_FMT_CI, fmt->siz, G_IM_FMT_RGBA); + struct n64_image* ref_img = n64texconv_image_from_png(png_p, G_IM_FMT_CI, fmt->siz, fmt->tlut_fmt); if (ref_img == NULL) { fprintf(stderr, "Could not read png %s\n", png_p); success = false; @@ -429,7 +435,7 @@ static bool handle_ci_shared_tlut(const char* png_p, const struct fmt_info* fmt, if (success) { for (size_t i = 0; i < len_pngs_with_tlut; i++) { struct n64_image* other_img = - n64texconv_image_from_png(pngs_with_tlut[i].png_p, G_IM_FMT_CI, fmt->siz, G_IM_FMT_RGBA); + n64texconv_image_from_png(pngs_with_tlut[i].png_p, G_IM_FMT_CI, fmt->siz, fmt->tlut_fmt); pngs_with_tlut[i].img = other_img; if (other_img == NULL) { fprintf(stderr, "Could not read other png %s\n", pngs_with_tlut[i].png_p); @@ -459,9 +465,10 @@ static bool handle_ci_shared_tlut(const char* png_p, const struct fmt_info* fmt, } char* pal_inc_c_p = - malloc(len_out_dir_p + strlen("/") + strlen(tlut_name) + strlen(".tlut.rgba16.uXX.inc.c") + 1); + malloc(len_out_dir_p + strlen("/") + strlen(tlut_name) + strlen(".tlut.xxxx16.uXX.inc.c") + 1); assert(tlut_elem_size == 8 || tlut_elem_size == 4); - sprintf(pal_inc_c_p, "%s/%s.tlut.rgba16%s.inc.c", out_dir_p, tlut_name, tlut_elem_size == 8 ? "" : ".u32"); + sprintf(pal_inc_c_p, "%s/%s.tlut.%s16%s.inc.c", out_dir_p, tlut_name, + fmt->tlut_fmt == G_IM_FMT_RGBA ? "rgba" : "ia", tlut_elem_size == 8 ? "" : ".u32"); const bool is_split_palette = subfmt == SUBFMT_SPLIT_LO || subfmt == SUBFMT_SPLIT_HI; const unsigned int max_colors = fmt->siz == G_IM_SIZ_4b ? 16 : is_split_palette ? 128 : 256; @@ -539,7 +546,7 @@ static bool handle_ci_shared_tlut(const char* png_p, const struct fmt_info* fmt, const float dither_level = 0.5f; success = n64texconv_quantize_shared(out_indices, out_pal, &out_pal_count, texels, widths, heights, - num_images, max_colors, dither_level, G_IM_FMT_RGBA) == 0; + num_images, max_colors, dither_level, fmt->tlut_fmt) == 0; if (!success) { fprintf(stderr, "Could not co-palettize images\n"); } @@ -552,7 +559,7 @@ static bool handle_ci_shared_tlut(const char* png_p, const struct fmt_info* fmt, } // write palette to .inc.c - struct n64_palette pal = { out_pal, G_IM_FMT_RGBA, out_pal_count }; + struct n64_palette pal = { out_pal, fmt->tlut_fmt, out_pal_count }; if (success) { int ret = n64texconv_palette_to_c_file(pal_inc_c_p, &pal, true, tlut_elem_size); success = ret == 0;