diff --git a/Makefile b/Makefile index 7cc47d6c3e..89cbb325c8 100644 --- a/Makefile +++ b/Makefile @@ -989,16 +989,8 @@ $(BUILD_DIR)/src/overlays/%_reloc.o: $(BUILD_DIR)/spec # Assets from assets/ -$(BUILD_DIR)/assets/%.u64.inc.c: assets/%.u64.png - $(PYTHON) tools/assets/build_from_png.py $< $(@:.inc.c=.bin) - @echo // From file://`realpath $<` >$@ - tools/assets/bin2c/bin2c u64 <$(@:.inc.c=.bin) >>$@ - -# same as above rule but u32 -$(BUILD_DIR)/assets/%.u32.inc.c: assets/%.u32.png - $(PYTHON) tools/assets/build_from_png.py $< $(@:.inc.c=.bin) - @echo // From file://`realpath $<` >$@ - tools/assets/bin2c/bin2c u32 <$(@:.inc.c=.bin) >>$@ +$(BUILD_DIR)/assets/%.inc.c: assets/%.png + tools/assets/build_from_png/build_from_png $< $(dir $@) assets/$(dir $*) $(wildcard $(EXTRACTED_DIR)/assets/$(dir $*)) $(BUILD_DIR)/assets/%.u8.inc.c: assets/%.u8.bin $(BIN2C) -t 1 $< $@ @@ -1008,16 +1000,8 @@ $(BUILD_DIR)/assets/%.u64.jpg.inc.c: assets/%.u64.jpg # Assets from extracted/ -$(BUILD_DIR)/assets/%.u64.inc.c: $(EXTRACTED_DIR)/assets/%.u64.png - $(PYTHON) tools/assets/build_from_png.py $< $(@:.inc.c=.bin) - @echo // From file://`realpath $<` >$@ - tools/assets/bin2c/bin2c u64 <$(@:.inc.c=.bin) >>$@ - -# same as above rule but u32 -$(BUILD_DIR)/assets/%.u32.inc.c: $(EXTRACTED_DIR)/assets/%.u32.png - $(PYTHON) tools/assets/build_from_png.py $< $(@:.inc.c=.bin) - @echo // From file://`realpath $<` >$@ - tools/assets/bin2c/bin2c u32 <$(@:.inc.c=.bin) >>$@ +$(BUILD_DIR)/assets/%.inc.c: $(EXTRACTED_DIR)/assets/%.png + tools/assets/build_from_png/build_from_png $< $(dir $@) $(wildcard assets/$(dir $*)) $(EXTRACTED_DIR)/assets/$(dir $*) $(BUILD_DIR)/assets/%.u8.inc.c: $(EXTRACTED_DIR)/assets/%.u8.bin $(BIN2C) -t 1 $< $@ diff --git a/tools/assets/Makefile b/tools/assets/Makefile index 88c0891bdf..b1c4afb156 100644 --- a/tools/assets/Makefile +++ b/tools/assets/Makefile @@ -1,12 +1,13 @@ all: +# must build n64texconv before build_from_png $(MAKE) -C n64texconv + $(MAKE) -C build_from_png $(MAKE) -C bin2c - $(MAKE) -C png2raw clean: $(MAKE) -C n64texconv clean + $(MAKE) -C build_from_png clean $(MAKE) -C bin2c clean - $(MAKE) -C png2raw clean distclean: clean $(MAKE) -C n64texconv distclean diff --git a/tools/assets/build_from_png.py b/tools/assets/build_from_png.py deleted file mode 100755 index 28e0a1c0c5..0000000000 --- a/tools/assets/build_from_png.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from pathlib import Path - - -VERBOSE = False - -from n64 import G_IM_FMT, G_IM_SIZ -import n64yatc -from png2raw import png2raw - - -def main(): - png_path = Path(sys.argv[1]) - out_bin_path = Path(sys.argv[2]) - - suffixes = png_path.suffixes - assert len(suffixes) >= 2 - assert suffixes[-1] == ".png" - suffixes.pop() - if suffixes[-1] in {".u64", ".u32"}: - suffixes.pop() - assert len(suffixes) > 0 - if suffixes[-1].startswith(".tlut_"): - tlut_info = suffixes.pop().removeprefix(".") - else: - tlut_info = None - assert len(suffixes) > 0 - fmtsiz_str = suffixes[-1].removeprefix(".") - - fmt, siz = None, None - for candidate_fmt in G_IM_FMT: - for candidate_siz in G_IM_SIZ: - candidate_fmtsiz_str = f"{candidate_fmt.name.lower()}{candidate_siz.bpp}" - if candidate_fmtsiz_str == fmtsiz_str: - fmt = candidate_fmt - siz = candidate_siz - - assert fmt is not None and siz is not None, fmtsiz_str - - if fmt != G_IM_FMT.CI: - with png2raw.Instance(png_path) as png: - data_rgba32 = png.read_to_rgba32() - tex_bin = n64yatc.convert(data_rgba32, G_IM_FMT.RGBA, G_IM_SIZ._32b, fmt, siz) - # print(len(tex_bin), tex_bin[:0x10], tex_bin[-0x10:], file=sys.stderr) - # sys.stdout.buffer.write(tex_bin) # for some reason the *string* "None." is also written to stdout??? - out_bin_path.write_bytes(tex_bin) - else: - # TODO probably move tlut_info and overall tex file suffix construction/parsing to its own library - - if tlut_info is None: - tlut_elem_type = "u64" - tlut_out_bin_path_base_str = str(out_bin_path) - tlut_out_bin_path_base_str = tlut_out_bin_path_base_str.removesuffix(".bin") - if tlut_out_bin_path_base_str.endswith(".u64"): - tlut_out_bin_path_base_str = tlut_out_bin_path_base_str.removesuffix( - ".u64" - ) - all_pngs_using_tlut = [png_path] - else: - tlut_elem_type = "u64" - if tlut_info.endswith("_u64"): - tlut_elem_type = "u64" - tlut_name = tlut_info.removeprefix("tlut_").removesuffix("_u64") - elif tlut_info.endswith("_u32"): - tlut_elem_type = "u32" - tlut_name = tlut_info.removeprefix("tlut_").removesuffix("_u32") - else: - tlut_name = tlut_info.removeprefix("tlut_") - tlut_out_bin_path_base_str = str(out_bin_path.parent / tlut_name) - - # TODO this is far from perfect. - # what if a tlut_name is included in another - # what if not in the same folder (just don't support that) - # does the same png get built several times - all_pngs_using_tlut = list(png_path.parent.glob(f"*.tlut_{tlut_name}*.png")) - assert png_path in all_pngs_using_tlut - tlut_out_bin_path = Path( - f"{tlut_out_bin_path_base_str}.tlut.rgba16.{tlut_elem_type}.bin" - ) - - if VERBOSE: - print(all_pngs_using_tlut) - - with png2raw.Instance(png_path) as png: - palette_rgba32 = png.get_palette_rgba32() - data_ci8 = png.read_palette_indices() - tex_bin = n64yatc.convert(data_ci8, G_IM_FMT.CI, G_IM_SIZ._8b, fmt, siz) - tlut_bin = n64yatc.convert( - palette_rgba32, G_IM_FMT.RGBA, G_IM_SIZ._32b, G_IM_FMT.RGBA, G_IM_SIZ._16b - ) - out_bin_path.write_bytes(tex_bin) - tlut_out_bin_path.write_bytes(tlut_bin) - - import subprocess - - # HACK since the makefile doesn't know the tlut file should be built (bin2c'd), build it here - cmd = [ - "tools/assets/bin2c/bin2c", - tlut_elem_type, - ] - with tlut_out_bin_path.open("rb") as fin: - with tlut_out_bin_path.with_suffix(".inc.c").open("w") as fout: - subprocess.check_call(cmd, stdin=fin, stdout=fout) - - -if __name__ == "__main__": - main() diff --git a/tools/assets/build_from_png/.gitignore b/tools/assets/build_from_png/.gitignore new file mode 100644 index 0000000000..e0d6a78806 --- /dev/null +++ b/tools/assets/build_from_png/.gitignore @@ -0,0 +1 @@ +build_from_png diff --git a/tools/assets/build_from_png/Makefile b/tools/assets/build_from_png/Makefile new file mode 100644 index 0000000000..4a74a0d6e8 --- /dev/null +++ b/tools/assets/build_from_png/Makefile @@ -0,0 +1,17 @@ +CFLAGS := -Wall -Werror -O3 + +ifeq ($(shell $(CC) --version | grep clang),) + OMPFLAGS := -fopenmp +else + OMPFLAGS := +endif + +default: build_from_png + +clean: + $(RM) build_from_png + +.PHONY: default clean + +build_from_png: build_from_png.c ../n64texconv/libn64texconv.a + $(CC) $(CFLAGS) -o $@ $^ $(OMPFLAGS) -lz -lm diff --git a/tools/assets/build_from_png/build_from_png.c b/tools/assets/build_from_png/build_from_png.c new file mode 100644 index 0000000000..4ce790d04c --- /dev/null +++ b/tools/assets/build_from_png/build_from_png.c @@ -0,0 +1,554 @@ +// SPDX-FileCopyrightText: © 2024 ZeldaRET +// SPDX-License-Identifier: MIT + +// Note: this is statically linked with GPL binaries, making the binary GPL-licensed + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../n64texconv/src/libn64texconv/n64texconv.h" + +#define NUM_FORMATS 9 +static const struct fmt_info { + const char* name; + int fmt; + int siz; +} 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, }, + // clang-format on +}; + +#define strequ(s1, s2) (strcmp(s1, s2) == 0) +#define strstartswith(s, prefix) (strncmp(s, prefix, strlen(prefix)) == 0) +static bool strendswith(const char* s, const char* suffix) { + size_t len_s = strlen(s); + size_t len_suffix = strlen(suffix); + return (len_s >= len_suffix) && (strncmp(s + len_s - len_suffix, suffix, len_suffix) == 0); +} + +static bool parse_png_p(char* png_p_buf, const struct fmt_info** fmtp, int* elem_sizep, char** tlut_namep, + int* tlut_elem_sizep, bool print_err) { + // The last 4 (or less) suffixes, without the '.' + const int max_n_suffixes = 4; + char* png_p_suffixes[max_n_suffixes]; + int n_suffixes_found = 0; + size_t i = strlen(png_p_buf); + while (i != 0) { + i--; + if (png_p_buf[i] == '.') { + png_p_suffixes[n_suffixes_found] = &png_p_buf[i + 1]; + n_suffixes_found++; + if (n_suffixes_found >= max_n_suffixes) { + break; + } + png_p_buf[i] = '\0'; + } + } + + if (n_suffixes_found == 0 || !strequ(png_p_suffixes[0], "png")) { + if (print_err) { + fprintf(stderr, "png path doesn't end with .png\n"); + } + return false; + } + int i_suffix = 1; + int i_suffix_elemtype = -1; + int i_suffix_tlut = -1; + int i_suffix_fmt = -1; + if (i_suffix < n_suffixes_found && + (strequ(png_p_suffixes[i_suffix], "u32") || strequ(png_p_suffixes[i_suffix], "u64"))) { + i_suffix_elemtype = i_suffix; + i_suffix++; + } + if (i_suffix < n_suffixes_found && strstartswith(png_p_suffixes[i_suffix], "tlut_")) { + i_suffix_tlut = i_suffix; + i_suffix++; + } + if (i_suffix >= n_suffixes_found) { + if (print_err) { + fprintf(stderr, "png path is missing a .format suffix\n"); + } + return false; + } + i_suffix_fmt = i_suffix; + + if (i_suffix_elemtype < 0) { + if (print_err) { + fprintf(stderr, "png path is missing a .u32 or .u64 suffix\n"); + } + return false; + } else { + if (strequ(png_p_suffixes[i_suffix_elemtype], "u64")) { + *elem_sizep = 8; + } else if (strequ(png_p_suffixes[i_suffix_elemtype], "u32")) { + *elem_sizep = 4; + } else { + // unreachable + assert(false); + } + } + + const struct fmt_info* fmt = NULL; + + for (size_t i = 0; i < NUM_FORMATS; i++) { + if (strequ(fmt_map[i].name, png_p_suffixes[i_suffix_fmt])) { + fmt = &fmt_map[i]; + break; + } + } + + if (fmt == NULL) { + if (print_err) { + fprintf(stderr, "png path is missing a .format suffix\n"); + } + return false; + } + + if (fmt->fmt == G_IM_FMT_CI && i_suffix_tlut >= 0) { + if (strendswith(png_p_suffixes[i_suffix_tlut], "_u64")) { + *tlut_elem_sizep = 8; + } else if (strendswith(png_p_suffixes[i_suffix_tlut], "_u32")) { + *tlut_elem_sizep = 4; + } else { + if (print_err) { + fprintf(stderr, "png path with ci format has a .tlut_ suffix without a _u32 or _u64 suffix\n"); + } + return false; + } + // extract "ABC" from the "tlut_ABC_uXX" suffix + if (strlen(png_p_suffixes[i_suffix_tlut]) <= strlen("tlut__uXX")) { + if (print_err) { + fprintf(stderr, "png path with ci format has a bad .tlut_ suffix\n"); + } + return false; + } + png_p_suffixes[i_suffix_tlut][strlen(png_p_suffixes[i_suffix_tlut]) - strlen("_uXX")] = '\0'; + *tlut_namep = strdup(png_p_suffixes[i_suffix_tlut] + strlen("tlut_")); + } + + *fmtp = fmt; + + return true; +} + +static void free_dir_list(char** dir_list) { + for (size_t i = 0; dir_list[i] != NULL; i++) { + free(dir_list[i]); + } + free(dir_list); +} + +/** + * Returns a NULL-terminated array of filenames in `dir_p`, + * or NULL if an error happens. + */ +static char** new_dir_list(const char* dir_p) { + DIR* dir = opendir(dir_p); + if (dir == NULL) { + perror("opendir"); + return NULL; + } + + size_t len_dir_list = 0; + size_t maxlen_dir_list = 16; + char** dir_list = malloc(sizeof(char* [maxlen_dir_list])); + + while (true) { + errno = 0; + struct dirent* dirent = readdir(dir); + if (dirent == NULL) { + if (errno != 0) { + perror("readdir"); + assert(len_dir_list < maxlen_dir_list); + dir_list[len_dir_list] = NULL; + free_dir_list(dir_list); + return NULL; + } + break; + } + assert(len_dir_list < maxlen_dir_list); + dir_list[len_dir_list] = strdup(dirent->d_name); + len_dir_list++; + + if (len_dir_list >= maxlen_dir_list) { + maxlen_dir_list *= 2; + dir_list = realloc(dir_list, sizeof(char* [maxlen_dir_list])); + } + } + + assert(len_dir_list < maxlen_dir_list); + dir_list[len_dir_list] = NULL; + + if (closedir(dir) != 0) { + perror("closedir"); + free_dir_list(dir_list); + return NULL; + } + return dir_list; +} + +static char* make_inc_c_p(const char* png_p, const char* out_dir_p) { + char* png_p_buf = strdup(png_p); + char* png_stem = basename(png_p_buf); + assert(strendswith(png_stem, ".png")); // checked by parse_png_p + png_stem[strlen(png_stem) - strlen(".png")] = '\0'; // cut off .png suffix + char* inc_c_p = malloc(strlen(out_dir_p) + strlen("/") + strlen(png_stem) + strlen(".inc.c") + 1); + sprintf(inc_c_p, "%s/%s.inc.c", out_dir_p, png_stem); + free(png_p_buf); + return inc_c_p; +} + +static bool handle_non_ci(const char* png_p, const struct fmt_info* fmt, int elem_size, const char* out_dir_p) { + char* inc_c_p = make_inc_c_p(png_p, out_dir_p); + + struct n64_image* img = n64texconv_image_from_png(png_p, fmt->fmt, fmt->siz, G_IM_FMT_RGBA); + bool success = n64texconv_image_to_c_file(inc_c_p, img, false, false, elem_size) == 0; + n64texconv_image_free(img); + free(inc_c_p); + return success; +} + +static bool handle_ci_single(const char* png_p, const struct fmt_info* fmt, int elem_size, const char* out_dir_p) { + const int tlut_elem_size = 8; + char* inc_c_p = make_inc_c_p(png_p, out_dir_p); + + char* png_p_buf = strdup(png_p); + char* png_stem = basename(png_p_buf); + // parse_png_p ensured these suffixes + assert(strlen(png_stem) >= strlen(".ciX.uXX.png")); + png_stem[strlen(png_stem) - strlen(".ciX.uXX.png")] = '\0'; + char* pal_inc_c_p = + malloc(strlen(out_dir_p) + strlen("/") + strlen(png_stem) + strlen(".tlut.rgba16.u64.inc.c") + 1); + sprintf(pal_inc_c_p, "%s/%s.tlut.rgba16.u64.inc.c", out_dir_p, png_stem); + free(png_p_buf); + + struct n64_image* img = n64texconv_image_from_png(png_p, fmt->fmt, fmt->siz, G_IM_FMT_RGBA); + bool success = n64texconv_image_to_c_file(inc_c_p, img, false, false, elem_size) == 0; + if (!success) { + fprintf(stderr, "Could not write image to %s\n", inc_c_p); + } + if (success) { + success = n64texconv_palette_to_c_file(pal_inc_c_p, img->pal, true, tlut_elem_size) == 0; + if (!success) { + fprintf(stderr, "Could not write palette to %s\n", pal_inc_c_p); + } + } + n64texconv_palette_free(img->pal); + n64texconv_image_free(img); + free(inc_c_p); + free(pal_inc_c_p); + return success; +} + +static bool handle_ci_shared_tlut(const char* png_p, const struct fmt_info* fmt, const char* out_dir_p, char** in_dirs, + int num_in_dirs, char* tlut_name, int tlut_elem_size) { + const size_t len_out_dir_p = strlen(out_dir_p); + + size_t len_pngs_with_tlut = 0; + size_t maxlen_pngs_with_tlut = 16; + struct other_png_info { + char* png_p; + char* name; // pointer into png_p; don't free + int elem_size; + struct n64_image* img; + }* pngs_with_tlut = malloc(sizeof(struct other_png_info[maxlen_pngs_with_tlut])); + + for (int j = 0; j < num_in_dirs; j++) { + const char* in_dir_p = in_dirs[j]; + const size_t len_in_dir_p = strlen(in_dir_p); + + char** in_dir_list = new_dir_list(in_dir_p); + if (in_dir_list == NULL) { + fprintf(stderr, "Couldn't list files in %s\n", in_dir_p); + return false; + } + + for (size_t i = 0; in_dir_list[i] != NULL; i++) { + char* direntry_name_buf = strdup(in_dir_list[i]); + const struct fmt_info* direntry_fmt; + int direntry_elem_size; + char* direntry_tlut_name = NULL; + int direntry_tlut_elem_size = -1; + if (parse_png_p(direntry_name_buf, &direntry_fmt, &direntry_elem_size, &direntry_tlut_name, + &direntry_tlut_elem_size, false)) { + if (direntry_fmt->fmt == G_IM_FMT_CI && direntry_tlut_name != NULL) { + if (strequ(tlut_name, direntry_tlut_name)) { + // TODO change asserts to errors and fail + assert(direntry_fmt == fmt); + assert(direntry_tlut_elem_size == tlut_elem_size); + + bool png_name_already_found = false; + for (size_t k = 0; k < len_pngs_with_tlut; k++) { + if (strequ(pngs_with_tlut[k].name, in_dir_list[i])) { + png_name_already_found = true; + break; + } + } + + if (!png_name_already_found) { + if (len_pngs_with_tlut == maxlen_pngs_with_tlut) { + maxlen_pngs_with_tlut *= 2; + pngs_with_tlut = + realloc(pngs_with_tlut, sizeof(struct other_png_info[maxlen_pngs_with_tlut])); + } + assert(len_pngs_with_tlut < maxlen_pngs_with_tlut); + char* other_png_p = malloc(len_in_dir_p + strlen("/") + strlen(in_dir_list[i]) + 1); + sprintf(other_png_p, "%s/%s", in_dir_p, in_dir_list[i]); + pngs_with_tlut[len_pngs_with_tlut].png_p = other_png_p; + pngs_with_tlut[len_pngs_with_tlut].name = other_png_p + len_in_dir_p + strlen("/"); + pngs_with_tlut[len_pngs_with_tlut].elem_size = direntry_elem_size; + pngs_with_tlut[len_pngs_with_tlut].img = NULL; + len_pngs_with_tlut++; + } + } + } + } + free(direntry_name_buf); + } + assert(len_pngs_with_tlut <= maxlen_pngs_with_tlut); + + free_dir_list(in_dir_list); + } + + struct n64_image* ref_img = n64texconv_image_from_png(png_p, G_IM_FMT_CI, fmt->siz, G_IM_FMT_RGBA); + bool all_other_pngs_match_ref_img_pal = true; + + bool success = true; + + 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); + pngs_with_tlut[i].img = other_img; + if (other_img == NULL) { + fprintf(stderr, "Could not read png %s\n", pngs_with_tlut[i].png_p); + success = false; + break; + } + if (all_other_pngs_match_ref_img_pal) { + bool pal_matches_ref = + other_img->pal->count == ref_img->pal->count && + memcmp(other_img->pal->texels, ref_img->pal->texels, sizeof(struct color[ref_img->pal->count])) == 0; + if (!pal_matches_ref) { + all_other_pngs_match_ref_img_pal = false; + } + } + } + + if (success) { + for (size_t i = 0; i < len_pngs_with_tlut; i++) { + assert(pngs_with_tlut[i].img != NULL); +#ifdef VERBOSE + fprintf(stderr, "%s %s\n", pngs_with_tlut[i].name, pngs_with_tlut[i].png_p); +#endif + } + + char* pal_inc_c_p = + malloc(len_out_dir_p + strlen("/") + strlen(tlut_name) + strlen(".tlut.rgba16.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 ? "u64" : "u32"); + + if (all_other_pngs_match_ref_img_pal) { + // write matching palette, and matching color indices for all pngs +#ifdef VERBOSE + fprintf(stderr, "Matching data detected!\n"); +#endif + + // pass pad_to_8b=true to account for the case where this is in fact not matching data + // (the png was silently palettized by n64texconv) + // Note: this forces all palette sizes to be 8-aligned, even u32-element-typed ones + int ret = n64texconv_palette_to_c_file(pal_inc_c_p, ref_img->pal, true, tlut_elem_size); + success = ret == 0; + if (!success) { + fprintf(stderr, "Could not write palette to %s (%d)\n", pal_inc_c_p, ret); + } + + if (success) { + for (size_t i = 0; i < len_pngs_with_tlut; i++) { + char* inc_c_p = make_inc_c_p(pngs_with_tlut[i].png_p, out_dir_p); + + success = n64texconv_image_to_c_file(inc_c_p, pngs_with_tlut[i].img, false, false, + pngs_with_tlut[i].elem_size) == 0; + if (!success) { + fprintf(stderr, "Could not write image to %s\n", inc_c_p); + } + free(inc_c_p); + if (!success) { + break; + } + } + } + } else { + // co-palettize all pngs +#ifdef VERBOSE + fprintf(stderr, "Non-matching data detected!\n"); +#endif + const bool copaletize_write_out_pngs = true; + + const size_t num_images = len_pngs_with_tlut; + uint8_t* out_indices[num_images]; + struct color* texels[num_images]; + size_t widths[num_images]; + size_t heights[num_images]; + for (size_t i = 0; i < len_pngs_with_tlut; i++) { + assert(pngs_with_tlut[i].img != NULL); + out_indices[i] = malloc(pngs_with_tlut[i].img->width * pngs_with_tlut[i].img->height); + texels[i] = pngs_with_tlut[i].img->texels; + widths[i] = pngs_with_tlut[i].img->width; + heights[i] = pngs_with_tlut[i].img->height; + } + const unsigned int max_colors = fmt->siz == G_IM_SIZ_8b ? 256 : 16; + struct color out_pal[max_colors]; + size_t out_pal_count; + 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) == 0; + if (!success) { + fprintf(stderr, "Could not co-palettize images\n"); + } + + struct n64_palette pal = { out_pal, G_IM_FMT_RGBA, out_pal_count }; + if (success) { + int ret = n64texconv_palette_to_c_file(pal_inc_c_p, &pal, true, tlut_elem_size); + success = ret == 0; + if (!success) { + fprintf(stderr, "Could not write generated palette to %s (%d)\n", pal_inc_c_p, ret); + } + } + if (success) { + if (copaletize_write_out_pngs) { + char* pal_out_png_p = strdup(pal_inc_c_p); + assert(strendswith(pal_inc_c_p, ".inc.c")); + pal_out_png_p[strlen(pal_out_png_p) - strlen(".inc.c")] = '\0'; + strcat(pal_out_png_p, ".png"); + success = n64texconv_palette_to_png(pal_out_png_p, &pal) == 0; + if (!success) { + fprintf(stderr, "Could not write generated palette to png %s\n", pal_out_png_p); + } + free(pal_out_png_p); + } + } + if (success) { + for (size_t i = 0; i < len_pngs_with_tlut; i++) { + char* other_png_p_buf = strdup(pngs_with_tlut[i].png_p); + char* other_png_stem = basename(other_png_p_buf); + assert(strendswith(other_png_stem, ".png")); // checked by parse_png_p + other_png_stem[strlen(other_png_stem) - strlen(".png")] = '\0'; // cut off .png suffix + char* inc_c_p = malloc(len_out_dir_p + strlen("/") + strlen(other_png_stem) + strlen(".inc.c") + 1); + sprintf(inc_c_p, "%s/%s.inc.c", out_dir_p, other_png_stem); + free(other_png_p_buf); + + struct n64_image img = { + pngs_with_tlut[i].img->width, + pngs_with_tlut[i].img->height, + G_IM_FMT_CI, + fmt->siz, + &pal, + pngs_with_tlut[i].img->texels, + out_indices[i], + }; + + success = n64texconv_image_to_c_file(inc_c_p, &img, false, false, pngs_with_tlut[i].elem_size) == 0; + if (!success) { + fprintf(stderr, "Could not write palettized image to %s\n", inc_c_p); + break; + } + + if (copaletize_write_out_pngs) { + char* out_png_p = strdup(inc_c_p); + assert(strendswith(inc_c_p, ".inc.c")); + out_png_p[strlen(out_png_p) - strlen(".inc.c")] = '\0'; + strcat(out_png_p, ".png"); + success = n64texconv_image_to_png(out_png_p, &img, false) == 0; + if (!success) { + fprintf(stderr, "Could not write palettized image to png %s\n", out_png_p); + } + free(out_png_p); + if (!success) { + break; + } + } + } + } + + for (size_t i = 0; i < num_images; i++) { + free(out_indices[i]); + } + } + + free(pal_inc_c_p); + } + + n64texconv_image_free(ref_img); + + for (size_t i = 0; i < len_pngs_with_tlut; i++) { + free(pngs_with_tlut[i].png_p); + if (pngs_with_tlut[i].img != NULL) { + n64texconv_palette_free(pngs_with_tlut[i].img->pal); + n64texconv_image_free(pngs_with_tlut[i].img); + } + } + free(pngs_with_tlut); + + return success; +} + +int main(int argc, char** argv) { + if (argc < 3) { + usage: + fprintf(stderr, "Usage: build_from_png path/to/file.png path/to/out/folder/ [path/to/input/folder1/ ...]\n"); + fprintf(stderr, "The png file should be named like:\n"); + fprintf(stderr, " - texName.format..png (non-ci formats or ci formats with a non-shared tlut)\n"); + fprintf(stderr, " - texName.ci<4|8>.tlut_tlutName_..png (ci formats with a shared tlut)\n"); + return EXIT_FAILURE; + } + const char* png_p = argv[1]; + const char* out_dir_p = argv[2]; + char** in_dirs = argv + 3; + const int num_in_dirs = argc - 3; + + const struct fmt_info* fmt; + int elem_size; + char* tlut_name = NULL; + int tlut_elem_size = -1; + + { + char* png_p_buf = strdup(png_p); + bool success = parse_png_p(png_p_buf, &fmt, &elem_size, &tlut_name, &tlut_elem_size, true); + free(png_p_buf); + if (!success) { + goto usage; + } + } + + bool success; + + if (fmt->fmt != G_IM_FMT_CI) { + success = handle_non_ci(png_p, fmt, elem_size, out_dir_p); + } else { + if (tlut_name == NULL) { + success = handle_ci_single(png_p, fmt, elem_size, out_dir_p); + } else { + success = handle_ci_shared_tlut(png_p, fmt, out_dir_p, in_dirs, num_in_dirs, tlut_name, tlut_elem_size); + free(tlut_name); + } + } + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tools/assets/extract/extase_oot64/dlist_resources.py b/tools/assets/extract/extase_oot64/dlist_resources.py index 626125174c..f2b111a70c 100644 --- a/tools/assets/extract/extase_oot64/dlist_resources.py +++ b/tools/assets/extract/extase_oot64/dlist_resources.py @@ -152,17 +152,33 @@ class VtxArrayResource(CDataResource): from ...n64 import G_IM_FMT, G_IM_SIZ, G_TT, G_MDSFT_TEXTLUT +from ... import n64texconv -from tools.assets.png2raw import raw2png -from tools.assets import n64yatc +G_IM_FMT_n64texconv_by_n64 = { + G_IM_FMT.RGBA: n64texconv.G_IM_FMT_RGBA, + G_IM_FMT.YUV: n64texconv.G_IM_FMT_YUV, + G_IM_FMT.CI: n64texconv.G_IM_FMT_CI, + G_IM_FMT.IA: n64texconv.G_IM_FMT_IA, + G_IM_FMT.I: n64texconv.G_IM_FMT_I, +} +G_IM_SIZ_n64texconv_by_n64 = { + G_IM_SIZ._4b: n64texconv.G_IM_SIZ_4b, + G_IM_SIZ._8b: n64texconv.G_IM_SIZ_8b, + G_IM_SIZ._16b: n64texconv.G_IM_SIZ_16b, + G_IM_SIZ._32b: n64texconv.G_IM_SIZ_32b, +} def write_n64_image_to_png( path: Path, width: int, height: int, fmt: G_IM_FMT, siz: G_IM_SIZ, data: memoryview ): - # TODO replace n64yatc, don't copy to bytearray - image_data_rgba32 = n64yatc.convert(data, fmt, siz, G_IM_FMT.RGBA, G_IM_SIZ._32b) - raw2png.write(path, width, height, bytearray(image_data_rgba32)) + n64texconv.N64Image.from_bin( + data, + width, + height, + G_IM_FMT_n64texconv_by_n64[fmt], + G_IM_SIZ_n64texconv_by_n64[siz], + ).to_png(str(path), False) def write_n64_image_to_png_color_indexed( @@ -176,19 +192,15 @@ def write_n64_image_to_png_color_indexed( tlut_count: int, tlut_fmt: G_IM_FMT, ): - palette_data_rgba32 = n64yatc.convert( - tlut_data, tlut_fmt, G_IM_SIZ._16b, G_IM_FMT.RGBA, G_IM_SIZ._32b - ) - num_palette = tlut_count - image_data_ci8 = n64yatc.convert(data, fmt, siz, G_IM_FMT.CI, G_IM_SIZ._8b) - raw2png.write_paletted( - path, + assert tlut_count * 2 == len(tlut_data) + n64texconv.N64Image.from_bin( + data, width, height, - bytearray(palette_data_rgba32), - num_palette, - bytearray(image_data_ci8), - ) + G_IM_FMT_n64texconv_by_n64[fmt], + G_IM_SIZ_n64texconv_by_n64[siz], + n64texconv.N64Palette.from_bin(tlut_data, G_IM_FMT_n64texconv_by_n64[tlut_fmt]), + ).to_png(str(path), False) class TextureResource(Resource): diff --git a/tools/assets/n64texconv/src/libn64texconv/n64texconv.c b/tools/assets/n64texconv/src/libn64texconv/n64texconv.c index 06caf36f60..61d7fbb8eb 100644 --- a/tools/assets/n64texconv/src/libn64texconv/n64texconv.c +++ b/tools/assets/n64texconv/src/libn64texconv/n64texconv.c @@ -119,7 +119,7 @@ n64texconv_quantize(uint8_t *out_indices, struct color *out_pal, size_t *out_pal * out_indices, out_pal, out_pal_count, texels, widths, heights are all arrays of size num_images * texels[i] and out_indices[i] are arrays of size widths[i] * heights[i] */ -UNUSED static int +int n64texconv_quantize_shared(uint8_t **out_indices, struct color *out_pal, size_t *out_pal_count, struct color **texels, size_t *widths, size_t *heights, size_t num_images, unsigned int max_colors, float dither_level) @@ -880,13 +880,10 @@ n64texconv_image_from_png(const char *path, int fmt, int siz, int pal_fmt) if (plte.n_entries == 0) goto error_post_create_img; - // TODO ZAPD always writes 256-color palettes which breaks this, enable it when we can -#if 0 // Palette must have sufficiently few colors for the target format. If there are too // many, requantize to the maximum amount for the target format. if (plte.n_entries > max_colors) goto requantize; -#endif pal = n64texconv_palette_new(plte.n_entries, pal_fmt); if (pal == NULL) @@ -922,9 +919,9 @@ n64texconv_image_from_png(const char *path, int fmt, int siz, int pal_fmt) assert(rv == 0); } else { // Input is not an indexed png, quantize and generate palette -#if 0 + requantize: // Input is an indexed png but has too many colors, requantize with new palette -#endif + rv = spng_decode_image(ctx, (void *)img->texels, nbytes, SPNG_FMT_RGBA8, 0); assert(rv == 0); diff --git a/tools/assets/n64texconv/src/libn64texconv/n64texconv.h b/tools/assets/n64texconv/src/libn64texconv/n64texconv.h index 1c2dd98fcc..0c6476e3aa 100644 --- a/tools/assets/n64texconv/src/libn64texconv/n64texconv.h +++ b/tools/assets/n64texconv/src/libn64texconv/n64texconv.h @@ -119,4 +119,9 @@ n64texconv_image_to_c_file(const char *out_path, struct n64_image *img, bool pad const char * n64texconv_png_extension(struct n64_image *img); +int +n64texconv_quantize_shared(uint8_t **out_indices, struct color *out_pal, size_t *out_pal_count, struct color **texels, + size_t *widths, size_t *heights, size_t num_images, unsigned int max_colors, + float dither_level); + #endif diff --git a/tools/assets/n64yatc.py b/tools/assets/n64yatc.py deleted file mode 100644 index 8376efde67..0000000000 --- a/tools/assets/n64yatc.py +++ /dev/null @@ -1,203 +0,0 @@ -# n64 yet another texture converter -# TODO replace with a C implementation - - -# FIXME use all tools as packages or idk -try: - from .n64 import G_IM_FMT, G_IM_SIZ -except ImportError: - from n64 import G_IM_FMT, G_IM_SIZ - - -def convert( - data: bytes, - from_fmt: G_IM_FMT, - from_siz: G_IM_SIZ, - to_fmt: G_IM_FMT, - to_siz: G_IM_SIZ, -): - if from_fmt == to_fmt == G_IM_FMT.CI: - if from_siz == to_siz: - return bytes(data) - elif from_siz == G_IM_SIZ._4b and to_siz == G_IM_SIZ._8b: - data_ci4 = data - data_ci8 = bytearray(len(data_ci4) * 2) - i = 0 - for d in data_ci4: - data_ci8[i] = d >> 4 - i += 1 - data_ci8[i] = d & 0xF - i += 1 - return data_ci8 - elif from_siz == G_IM_SIZ._8b and to_siz == G_IM_SIZ._4b: - data_ci8 = data - assert len(data_ci8) % 2 == 0 - data_ci4 = bytearray(len(data_ci8) // 2) - i = 0 - while i < len(data_ci8): - d1, d2 = data_ci8[i : i + 2] - assert d1 < 16 - assert d2 < 16 - data_ci4[i // 2] = (d1 << 4) | d2 - i += 2 - return data_ci4 - else: - raise NotImplementedError("ci", from_siz, to_siz) - else: - assert G_IM_FMT.CI not in ( - from_fmt, - to_fmt, - ), "Can't convert between a CI format and a non-CI format" - assert ( - len(data) * 8 % from_siz.bpp == 0 - ), "data size is not a multiple of pixel size" - n_pixels = len(data) * 8 // from_siz.bpp - data_rgba32 = bytearray(4 * n_pixels) - _i = [0] - - def push(): - i = _i[0] - data_rgba32[4 * i : 4 * i + 4] = (r, g, b, a) - i += 1 - _i[0] = i - - f = (from_fmt, from_siz) - if f == (G_IM_FMT.RGBA, G_IM_SIZ._32b): - data_rgba32[:] = data - elif f == (G_IM_FMT.RGBA, G_IM_SIZ._16b): - while (i := _i[0]) < n_pixels: - d1, d2 = data[i * 2 : i * 2 + 2] - - r = (d1 & 0b1111_1000) >> 3 - g = ((d1 & 0b0000_0111) << 2) | ((d2 & 0b1100_0000) >> 6) - b = (d2 & 0b0011_1110) >> 1 - a = d2 & 1 - - r = (r << 3) | (r >> 2) - g = (g << 3) | (g >> 2) - b = (b << 3) | (b >> 2) - a = a * 255 - push() - elif f == (G_IM_FMT.IA, G_IM_SIZ._16b): - while (i := _i[0]) < n_pixels: - d1, d2 = data[i * 2 : i * 2 + 2] - - i = d1 - a = d2 - - r = i - g = i - b = i - a = a - push() - elif f == (G_IM_FMT.IA, G_IM_SIZ._8b): - while (i := _i[0]) < n_pixels: - d = data[i] - - i = d >> 4 - a = d & 0x0F - - i = (i << 4) | i - a = (a << 4) | a - - r = i - g = i - b = i - a = a - push() - elif f == (G_IM_FMT.IA, G_IM_SIZ._4b): - while (i := _i[0]) < n_pixels: - assert i % 2 == 0 - d = data[i // 2] - for dh in (d >> 4, d & 0xF): - i = dh >> 1 - a = dh & 1 - - i = (i << 5) | (i << 2) | (i >> 1) - a = a * 255 - - r = i - g = i - b = i - a = a - push() - elif f == (G_IM_FMT.I, G_IM_SIZ._8b): - while (i := _i[0]) < n_pixels: - d = data[i] - i = d - - r = i - g = i - b = i - a = i - push() - elif f == (G_IM_FMT.I, G_IM_SIZ._4b): - while (i := _i[0]) < n_pixels: - assert i % 2 == 0 - d = data[i // 2] - for dh in (d >> 4, d & 0xF): - i = dh - - i = (i << 4) | i - - r = i - g = i - b = i - a = i - push() - else: - raise NotImplementedError("from", from_fmt, from_siz) - - assert n_pixels * to_siz.bpp % 8 == 0 - data_to = bytearray(n_pixels * to_siz.bpp // 8) - - for i in range(n_pixels): - r, g, b, a = data_rgba32[i * 4 : i * 4 + 4] - y = round(sum((r, g, b)) / 3) # bad but w/e - t = (to_fmt, to_siz) - if t == (G_IM_FMT.RGBA, G_IM_SIZ._32b): - data_to[4 * i : 4 * i + 4] = r, g, b, a - elif t == (G_IM_FMT.RGBA, G_IM_SIZ._16b): - r = r >> 3 - g = g >> 3 - b = b >> 3 - a = round(a / 255) - - d1 = (r << 3) | (g >> 2) - d2 = ((g & 0b11) << 6) | (b << 1) | a - data_to[2 * i : 2 * i + 2] = d1, d2 - elif t == (G_IM_FMT.IA, G_IM_SIZ._16b): - a = a - - d1 = y - d2 = a - - data_to[i * 2 : i * 2 + 2] = d1, d2 - elif t == (G_IM_FMT.IA, G_IM_SIZ._8b): - y = y >> 4 - a = a >> 4 - - d = (y << 4) | a - - data_to[i] = d - elif t == (G_IM_FMT.IA, G_IM_SIZ._4b): - y = y >> 5 - a = round(a / 255) - - d = (y << 1) | a - - data_to[i // 2] |= (d << 4) if i % 2 == 0 else d - elif t == (G_IM_FMT.I, G_IM_SIZ._8b): - d = y - - data_to[i] = d - elif t == (G_IM_FMT.I, G_IM_SIZ._4b): - y = y >> 4 - - d = y - - data_to[i // 2] |= (d << 4) if i % 2 == 0 else d - else: - raise NotImplementedError("to", to_fmt, to_siz) - - return data_to diff --git a/tools/assets/png2raw/.gitignore b/tools/assets/png2raw/.gitignore deleted file mode 100644 index e9da9bbbcf..0000000000 --- a/tools/assets/png2raw/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -__pycache__/ -test_out_folder/ -png2raw -png2raw_main -raw2png diff --git a/tools/assets/png2raw/Makefile b/tools/assets/png2raw/Makefile deleted file mode 100644 index cc9a7b7f31..0000000000 --- a/tools/assets/png2raw/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -CFLAGS := -Wall -O2 -g - -default: png2raw raw2png - -clean: - $(RM) png2raw raw2png png2raw_main - -.PHONY: default clean - -png2raw: png2raw.c - $(CC) -shared -fPIC $(CFLAGS) -o $@ $< `pkg-config --libs --cflags libpng` - -raw2png: raw2png.c - $(CC) -shared -fPIC $(CFLAGS) -o $@ $< `pkg-config --libs --cflags libpng` - -png2raw_main: png2raw.c - $(CC) $(CFLAGS) -o $@ $< `pkg-config --libs --cflags libpng` - -raw2terminal := python3 raw2terminal.py -png := /home/dragorn421/Documents/oot/extracted/gc-eu-mq-dbg/assets/objects/object_link_boy/gLinkAdultEyesWideTex.ci8.tlut_gLinkAdultHeadTLUT_u64.u64.png - -test: png2raw_main - ./png2raw_main $(png) | $(raw2terminal) - ./png2raw_main --rgba32 $(png) | $(raw2terminal) - ./png2raw_main --palette-rgba32 $(png) | $(raw2terminal) - ./png2raw_main --palette-indices $(png) | $(raw2terminal) - ./png2raw_main --palette-indices $(png) | $(raw2terminal) --format=i8 - ./png2raw_main --dimensions $(png) diff --git a/tools/assets/png2raw/png2raw.c b/tools/assets/png2raw/png2raw.c deleted file mode 100644 index c0b2478941..0000000000 --- a/tools/assets/png2raw/png2raw.c +++ /dev/null @@ -1,678 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#include "png2raw.h" - -void user_error_fn(png_structp png_ptr, png_const_charp error_msg) { - fprintf(stderr, "[png2raw] error: %s\n", error_msg); - abort(); //! TODO ... I guess don't do this (bad for use as library) but idk what, then -} - -void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { - fprintf(stderr, "[png2raw] warning: %s\n", warning_msg); -} - -#define RGBA32_PIXEL_SIZE 4 - -enum png2raw_instance_state { - PNG2RAW_INSTANCE_STATE_CLEARED, // brand new - PNG2RAW_INSTANCE_STATE_FILE_SET, // fp is set - PNG2RAW_INSTANCE_STATE_BAD_FILE, // fp is set but couldn't be read or not a png - PNG2RAW_INSTANCE_STATE_HEADER_READ, // header has been read from fp - PNG2RAW_INSTANCE_STATE_LIBPNG_STRUCTS_ALLOCATED, // png_ptr and info_ptr are set - PNG2RAW_INSTANCE_STATE_INFO_READ, // png_read_info called - PNG2RAW_INSTANCE_STATE_READ_RGBA32_INITIALIZED, // setup for reading rgba32 has happened - PNG2RAW_INSTANCE_STATE_READ_PALETTED_INITIALIZED, // png is paletted, and setup for reading as paletted has happened - PNG2RAW_INSTANCE_STATE_INVALID // can't be used for anything more (besides freeing) -}; - -struct png2raw_instance { - enum png2raw_instance_state state; - FILE* fp; - png_structp png_ptr; - png_infop info_ptr; -}; - -typedef struct png2raw_instance* png2raw_instancep; - -png2raw_instancep png2raw_instance_new() { - png2raw_instancep inst = malloc(sizeof(struct png2raw_instance)); - - if (inst == NULL) { - return NULL; - } - - inst->state = PNG2RAW_INSTANCE_STATE_CLEARED; - inst->png_ptr = NULL; - inst->info_ptr = NULL; - - return inst; -} - -void png2raw_instance_free(png2raw_instancep inst) { - if (inst == NULL) { - return; - } - - png_destroy_read_struct(&inst->png_ptr, &inst->info_ptr, (png_infopp)NULL); - - fclose(inst->fp); - - free(inst); -} - -const char* error_messages[] = { - [PNG2RAW_ERR_OK] = "OK", - [PNG2RAW_ERR_INST_NULL] = "inst == NULL", - [PNG2RAW_ERR_INST_BAD_STATE] = "Bad inst state", - [PNG2RAW_ERR_CANT_OPEN_FOR_READING] = "Can't open the file for reading", - [PNG2RAW_ERR_CANT_READ_HEADER_BYTES] = "Can't read header bytes from the file", - [PNG2RAW_ERR_NOT_A_PNG] = "File is not a png", - [PNG2RAW_ERR_LIBPNG_PNG_PTR_NULL] = "png_create_read_struct returned NULL", - [PNG2RAW_ERR_LIBPNG_INFO_PTR_NULL] = "png_create_info_struct returned NULL", - [PNG2RAW_ERR_BUFFER_SIZE_P_NULL] = "pointer argument to buffer size is NULL", - [PNG2RAW_ERR_BAD_BUFFER_SIZE] = "buffer size argument is not the required size" - " (the value pointed to by the pointed argument has been set to the required size)", - [PNG2RAW_ERR_BUFFER_NULL] = "buffer pointer argument is NULL", - [PNG2RAW_ERR_PNG_IS_NOT_PALETTED] = "image is not paletted", -}; -const char* png2raw_get_error_message(enum png2raw_error err) { - if (err < 0 || err >= PNG2RAW_ERR_MAX) { - return "png2raw_get_error_message: bad err value"; - } - const char* msg = error_messages[err]; - if (msg == NULL) { - return "png2raw_get_error_message: missing message for err value"; - } - return msg; -} - -enum png2raw_error png2raw_set_file_name(png2raw_instancep inst, const char* file_name) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_CLEARED) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - FILE* fp = fopen(file_name, "rb"); - if (fp == NULL) { - return PNG2RAW_ERR_CANT_OPEN_FOR_READING; - } - - inst->fp = fp; - inst->state = PNG2RAW_INSTANCE_STATE_FILE_SET; - - return PNG2RAW_ERR_OK; -} - -enum png2raw_error png2raw_read_info(png2raw_instancep inst) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_FILE_SET) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - FILE* fp = inst->fp; - - // Check png signature - - int num_header_bytes = 8; - uint8_t header[num_header_bytes]; - if (fread(header, 1, num_header_bytes, fp) != num_header_bytes) { - inst->state = PNG2RAW_INSTANCE_STATE_BAD_FILE; - return PNG2RAW_ERR_CANT_READ_HEADER_BYTES; - } - - bool is_png = !png_sig_cmp(header, 0, num_header_bytes); - if (!is_png) { - inst->state = PNG2RAW_INSTANCE_STATE_BAD_FILE; - return PNG2RAW_ERR_NOT_A_PNG; - } - - inst->state = PNG2RAW_INSTANCE_STATE_HEADER_READ; - - // Initialize png reading - - // Set our own error callback, to avoid having to use the longjmp mechanism libpng uses by default. - //! TODO this is actually very bad behavior as a utility/library function - // While we are at it, also set the warning callback. - void* user_error_ptr = NULL; - png_structp png_ptr = - png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr, user_error_fn, user_warning_fn); - - if (png_ptr == NULL) { - return PNG2RAW_ERR_LIBPNG_PNG_PTR_NULL; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - - if (info_ptr == NULL) { - png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); - return PNG2RAW_ERR_LIBPNG_INFO_PTR_NULL; - } - - inst->png_ptr = png_ptr; - inst->info_ptr = info_ptr; - inst->state = PNG2RAW_INSTANCE_STATE_LIBPNG_STRUCTS_ALLOCATED; - - png_init_io(png_ptr, fp); - - // Indicate the stream was read for the signature bytes - // (for passing to png_sig_cmp above) - png_set_sig_bytes(png_ptr, num_header_bytes); - - png_read_info(png_ptr, info_ptr); - - inst->state = PNG2RAW_INSTANCE_STATE_INFO_READ; - - return PNG2RAW_ERR_OK; -} - -enum png2raw_error png2raw_get_dimensions(png2raw_instancep inst, uint32_t* widthp, uint32_t* heightp) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - // This may be done in more states but whatever - if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - png_structp png_ptr = inst->png_ptr; - png_infop info_ptr = inst->info_ptr; - - png_uint_32 width = png_get_image_width(png_ptr, info_ptr); - png_uint_32 height = png_get_image_height(png_ptr, info_ptr); - - if (widthp != NULL) { - *widthp = width; - } - if (heightp != NULL) { - *heightp = height; - } - - return PNG2RAW_ERR_OK; -} - -enum png2raw_error png2raw_read_rgba32_init(png2raw_instancep inst) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - png_structp png_ptr = inst->png_ptr; - png_infop info_ptr = inst->info_ptr; - - // gamma / alpha stuff : no idea - // (would be before or after png_read_info : idk) - // PNG_ALPHA_PNG - // png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_GAMMA_LINEAR); // idk - // not sure where png_set_alpha_mode should be anyway (probably with all the other png_set_ s) - /* - Based on: -1) The PNG file gamma from the gAMA chunk. This overwrites the default value -provided by an earlier call to png_set_gamma or png_set_alpha_mode. - calling set_alpha_mode may do more harm than good idk - */ - - // Indicate transform operations to be performed - // (it is unclear if the order matters) - - png_set_palette_to_rgb(png_ptr); // Convert paletted to RGB if needed - png_set_gray_to_rgb(png_ptr); // Convert grayscale to RGB if needed - png_set_expand(png_ptr); // Convert to 24-bit RGB if needed - png_set_scale_16(png_ptr); // Convert from 16 bits per channel to 8 if needed - png_set_add_alpha(png_ptr, 255, PNG_FILLER_AFTER); // Ensure there is a alpha channel, defaulting to opaque - - /* - * From the manual: - * - * > if you are going to call png_read_update_info() you must call png_set_interlace_handling() before it unless you - * > want to receive interlaced output - */ - png_set_interlace_handling(png_ptr); - - // (I don't know if I need to call this) - png_read_update_info(png_ptr, info_ptr); - - inst->state = PNG2RAW_INSTANCE_STATE_READ_RGBA32_INITIALIZED; - - return PNG2RAW_ERR_OK; -} - -enum png2raw_error png2raw_read_rgba32(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_READ_RGBA32_INITIALIZED) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - png_structp png_ptr = inst->png_ptr; - png_infop info_ptr = inst->info_ptr; - - png_uint_32 height = png_get_image_height(png_ptr, info_ptr); - png_uint_32 width = png_get_image_width(png_ptr, info_ptr); - - // libpng allows the buffer to be allocated row-by-row, just use one whole block for simplicity - size_t required_image_data_size = height * width * RGBA32_PIXEL_SIZE; - - if (image_data_size_p == NULL) { - return PNG2RAW_ERR_BUFFER_SIZE_P_NULL; - } - - if (*image_data_size_p != required_image_data_size) { - // Pass the required buffer size to the caller - *image_data_size_p = required_image_data_size; - - return PNG2RAW_ERR_BAD_BUFFER_SIZE; - } - - if (image_data == NULL) { - return PNG2RAW_ERR_BUFFER_NULL; - } - - // Set the row pointers to the right locations in the image_data buffer - png_bytep row_pointers[height]; - for (uint32_t row = 0; row < height; row++) { - row_pointers[row] = image_data + row * width * RGBA32_PIXEL_SIZE; - } - - // Read png image - - png_read_image(png_ptr, row_pointers); - - png_read_end(png_ptr, (png_infop)NULL); - - inst->state = PNG2RAW_INSTANCE_STATE_INVALID; - - return PNG2RAW_ERR_OK; -} - -enum png2raw_error png2raw_is_paletted(png2raw_instancep inst, bool* is_paletted_p) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - png_structp png_ptr = inst->png_ptr; - png_infop info_ptr = inst->info_ptr; - - png_byte color_type = png_get_color_type(png_ptr, info_ptr); - - *is_paletted_p = color_type == PNG_COLOR_TYPE_PALETTE; - - return PNG2RAW_ERR_OK; -} - -/* - * Some information on paletted png files: - * - * - They are at most 8 bits per pixel. The pixel data is a palette index starting at 0. - * - * - The palette is stored in a mandatory "PLTE chunk", holding an array of 24-bits RRGGBB colors, indexed by - * palette index. - * - * - Transparency is optional. If any, it is stored in a "tRNS chunk", holding an array of 8-bits AA values, indexed - * by palette index. - * - * Note the following from the PNG specification: - * - * > tRNS can contain fewer values than there are palette entries. - * > In this case, the alpha value for all remaining palette entries is assumed to be 255. - * - * - Additional notes: - * - sPLT "Suggested palette" is unrelated to paletted png files - */ - -enum png2raw_error png2raw_get_palette_colors_rgba32(png2raw_instancep inst, size_t* palette_data_size_p, - uint8_t* palette_data) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) { - // Palette colors may very well be read later, after the image data (palette indices) were read, but just don't - // support that. - return PNG2RAW_ERR_INST_BAD_STATE; - } - - png_structp png_ptr = inst->png_ptr; - png_infop info_ptr = inst->info_ptr; - - png_byte color_type = png_get_color_type(png_ptr, info_ptr); - if (color_type != PNG_COLOR_TYPE_PALETTE) { - return PNG2RAW_ERR_PNG_IS_NOT_PALETTED; - } - - png_colorp palette; - int num_palette; - - if (!png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) { - // No PLTE chunk. - - // I think this is not supposed to happen, since at this point the image is confirmed to be of the paletted - // type, and the specification says the PLTE chunk must come before IDAT, and the manual says png_read_info - // processes all chunk up to the image data (IDAT). - - png_error(png_ptr, "No PLTE chunk in palette type png"); - } - - png_bytep trans_alpha; - int num_trans; - png_color_16p trans_color; - - if (!png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color)) { - // No tRNS chunk. - // Palette indices past the tRNS length are opaque, so making it "0-length" here is equivalent to having all - // palette indices opaque. - num_trans = 0; - } - - size_t required_palette_data_size = num_palette * RGBA32_PIXEL_SIZE; - - if (palette_data_size_p == NULL) { - return PNG2RAW_ERR_BUFFER_SIZE_P_NULL; //! TODO rename error or add new enum values (this and below) - } - - if (*palette_data_size_p != required_palette_data_size) { - // Pass the required buffer size to the caller - *palette_data_size_p = required_palette_data_size; - - return PNG2RAW_ERR_BAD_BUFFER_SIZE; - } - - if (palette_data == NULL) { - return PNG2RAW_ERR_BUFFER_NULL; - } - - for (int palette_index = 0; palette_index < num_palette; palette_index++) { - png_colorp rgb = &palette[palette_index]; - png_byte a; - - if (palette_index < num_trans) { - a = trans_alpha[palette_index]; - } else { - // Palette indices past the tRNS length are opaque. - a = 255; - } - - uint8_t* out_rgba = palette_data + palette_index * RGBA32_PIXEL_SIZE; - out_rgba[0] = rgb->red; - out_rgba[1] = rgb->green; - out_rgba[2] = rgb->blue; - out_rgba[3] = a; - } - - return PNG2RAW_ERR_OK; -} - -enum png2raw_error png2raw_read_palette_indices_init(png2raw_instancep inst) { - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - png_structp png_ptr = inst->png_ptr; - png_infop info_ptr = inst->info_ptr; - - png_byte color_type = png_get_color_type(png_ptr, info_ptr); - if (color_type != PNG_COLOR_TYPE_PALETTE) { - return PNG2RAW_ERR_PNG_IS_NOT_PALETTED; - } - - // Pack one pixel per byte, instead of possibly several pixels per byte. - // This effectively means converting from however-many bits per pixel, to 8 bits per pixel. - png_set_packing(png_ptr); - - png_set_interlace_handling(png_ptr); - - png_read_update_info(png_ptr, info_ptr); - - inst->state = PNG2RAW_INSTANCE_STATE_READ_PALETTED_INITIALIZED; - - return PNG2RAW_ERR_OK; -} - -enum png2raw_error png2raw_read_palette_indices(png2raw_instancep inst, size_t* image_data_size_p, - uint8_t* image_data) { - // This function is mostly a copy-paste from png2raw_read_rgba32 - - if (inst == NULL) { - return PNG2RAW_ERR_INST_NULL; - } - if (inst->state != PNG2RAW_INSTANCE_STATE_READ_PALETTED_INITIALIZED) { - return PNG2RAW_ERR_INST_BAD_STATE; - } - - png_structp png_ptr = inst->png_ptr; - png_infop info_ptr = inst->info_ptr; - - png_uint_32 height = png_get_image_height(png_ptr, info_ptr); - png_uint_32 width = png_get_image_width(png_ptr, info_ptr); - - // One byte per pixel - size_t required_image_data_size = height * width; - - if (image_data_size_p == NULL) { - return PNG2RAW_ERR_BUFFER_SIZE_P_NULL; - } - - if (*image_data_size_p != required_image_data_size) { - *image_data_size_p = required_image_data_size; - - return PNG2RAW_ERR_BAD_BUFFER_SIZE; - } - - if (image_data == NULL) { - return PNG2RAW_ERR_BUFFER_NULL; - } - - png_bytep row_pointers[height]; - for (uint32_t row = 0; row < height; row++) { - row_pointers[row] = image_data + row * width; - } - - png_read_image(png_ptr, row_pointers); - - png_read_end(png_ptr, (png_infop)NULL); - - inst->state = PNG2RAW_INSTANCE_STATE_INVALID; - - return PNG2RAW_ERR_OK; -} - -// main (example usage) - -enum png2raw_main_mode { - /** - * Read a png and write its pixels to stdout in rgba32 format. - * - * For example if a png is one pixel high, three pixels wide, with - * - * - left pixel: opaque red - * - middle pixel: opaque yellow - * - right pixel: half-transparent blue - * - * the following bytes will be written to stdout (`RR GG BB AA` for each pixel): - * - * FF 00 00 FF FF FF 00 FF 00 00 FF 80 - * - * (as binary, not as this textual representation) - */ - PNG2RAW_MAIN_MODE_RGBA32, - - /** - * Read a paletted png and write its palette to stdout in rgba32 format, similarly to PNG2RAW_MAIN_MODE_RGBA32. - */ - PNG2RAW_MAIN_MODE_PALETTE_RGBA32, - - /** - * Read a paletted png and write its image data to stdout, with one palette index per byte for each pixel. - */ - PNG2RAW_MAIN_MODE_PALETTE_INDICES, - - /** - * Read a png and write its width and height to stdout (as text). - */ - PNG2RAW_MAIN_MODE_DIMENSIONS -}; - -int main(int argc, char** argv) { - if (!(argc == 2 || argc == 3)) { - bad_usage: - fprintf(stderr, - "Usage: %s [--rgba32 | --palette-rgba32 | --palette-indices | --dimensions] \n", - argv[0]); - return EXIT_FAILURE; - } - - enum png2raw_main_mode main_mode; - - if (argc == 2) { - main_mode = PNG2RAW_MAIN_MODE_RGBA32; - } else { - main_mode = -1; - - char* main_mode_arg = argv[1]; - - const char* modes_arg[] = { - [PNG2RAW_MAIN_MODE_RGBA32] = "--rgba32", - [PNG2RAW_MAIN_MODE_PALETTE_RGBA32] = "--palette-rgba32", - [PNG2RAW_MAIN_MODE_PALETTE_INDICES] = "--palette-indices", - [PNG2RAW_MAIN_MODE_DIMENSIONS] = "--dimensions", - }; - - for (int i = 0; i < 4; i++) { - if (strcmp(main_mode_arg, modes_arg[i]) == 0) { - main_mode = i; - break; - } - } - - if (main_mode < 0) { - goto bad_usage; - } - } - - char* file_name = argv[argc - 1]; - - png2raw_instancep inst = png2raw_instance_new(); - - enum png2raw_error err; - -#define HANDLE_ERR(err) \ - if (err != PNG2RAW_ERR_OK) { \ - fprintf(stderr, "png2raw main() %d err=%d %s\n", __LINE__, err, png2raw_get_error_message(err)); \ - return EXIT_FAILURE; \ - } - - err = png2raw_set_file_name(inst, file_name); - HANDLE_ERR(err) - - err = png2raw_read_info(inst); - HANDLE_ERR(err) - - switch (main_mode) { - case PNG2RAW_MAIN_MODE_RGBA32: { - err = png2raw_read_rgba32_init(inst); - HANDLE_ERR(err) - - size_t image_data_size = 0; - - err = png2raw_read_rgba32(inst, &image_data_size, NULL); - if (err != PNG2RAW_ERR_BAD_BUFFER_SIZE) { - HANDLE_ERR(err) - } - if (err == PNG2RAW_ERR_OK) { - fprintf(stderr, "Expected PNG2RAW_ERR_BAD_BUFFER_SIZE but got PNG2RAW_ERR_OK\n"); - return EXIT_FAILURE; - } - - uint8_t* image_data = malloc(image_data_size); - - err = png2raw_read_rgba32(inst, &image_data_size, image_data); - HANDLE_ERR(err) - - // Note png2raw_instance_free could be called here, as image_data is owned by us. - - fwrite(image_data, 1, image_data_size, stdout); - - free(image_data); - - } break; - - case PNG2RAW_MAIN_MODE_PALETTE_RGBA32: { - size_t palette_data_size = 0; - - err = png2raw_get_palette_colors_rgba32(inst, &palette_data_size, NULL); - if (err != PNG2RAW_ERR_BAD_BUFFER_SIZE) { - HANDLE_ERR(err) - } - if (err == PNG2RAW_ERR_OK) { - fprintf(stderr, "Expected PNG2RAW_ERR_BAD_BUFFER_SIZE but got PNG2RAW_ERR_OK\n"); - return EXIT_FAILURE; - } - - uint8_t* palette_data = malloc(palette_data_size); - - err = png2raw_get_palette_colors_rgba32(inst, &palette_data_size, palette_data); - HANDLE_ERR(err) - - fwrite(palette_data, 1, palette_data_size, stdout); - - free(palette_data); - - } break; - - case PNG2RAW_MAIN_MODE_PALETTE_INDICES: { - err = png2raw_read_palette_indices_init(inst); - HANDLE_ERR(err) - - size_t image_data_size = 0; - - err = png2raw_read_palette_indices(inst, &image_data_size, NULL); - if (err != PNG2RAW_ERR_BAD_BUFFER_SIZE) { - HANDLE_ERR(err) - } - if (err == PNG2RAW_ERR_OK) { - fprintf(stderr, "Expected PNG2RAW_ERR_BAD_BUFFER_SIZE but got PNG2RAW_ERR_OK\n"); - return EXIT_FAILURE; - } - - uint8_t* image_data = malloc(image_data_size); - - err = png2raw_read_palette_indices(inst, &image_data_size, image_data); - HANDLE_ERR(err) - - fwrite(image_data, 1, image_data_size, stdout); - - free(image_data); - - } break; - - case PNG2RAW_MAIN_MODE_DIMENSIONS: { - uint32_t width, height; - err = png2raw_get_dimensions(inst, &width, &height); - HANDLE_ERR(err) - - fprintf(stdout, "%d %d\n", width, height); - - } break; - } - - png2raw_instance_free(inst); - - return EXIT_SUCCESS; -} diff --git a/tools/assets/png2raw/png2raw.h b/tools/assets/png2raw/png2raw.h deleted file mode 100644 index 4055d85473..0000000000 --- a/tools/assets/png2raw/png2raw.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef PNG2RAW_H -#define PNG2RAW_H - -/** - * Most functions return a png2raw_error value. If that value is PNG2RAW_ERR_OK, it means the function completed - * successfully. Otherwise something went wrong (such as passing a wrong argument). See png2raw_get_error_message for - * getting a message from an error value. - */ -enum png2raw_error { - PNG2RAW_ERR_OK, // no error - PNG2RAW_ERR_INST_NULL, - PNG2RAW_ERR_INST_BAD_STATE, - PNG2RAW_ERR_CANT_OPEN_FOR_READING, - PNG2RAW_ERR_CANT_READ_HEADER_BYTES, - PNG2RAW_ERR_NOT_A_PNG, - PNG2RAW_ERR_LIBPNG_PNG_PTR_NULL, - PNG2RAW_ERR_LIBPNG_INFO_PTR_NULL, - PNG2RAW_ERR_BUFFER_SIZE_P_NULL, - PNG2RAW_ERR_BAD_BUFFER_SIZE, - PNG2RAW_ERR_BUFFER_NULL, - PNG2RAW_ERR_PNG_IS_NOT_PALETTED, - PNG2RAW_ERR_MAX -}; - -struct png2raw_instance; -/** - * Most functions take a png2raw_instancep argument. It stores the state of reading a single png file. - * See png2raw_instance_new and png2raw_instance_free for constructing/freeing a new instance. - */ -typedef struct png2raw_instance* png2raw_instancep; - -/** - * Should eventually be freed with png2raw_instance_free. - */ -png2raw_instancep png2raw_instance_new(); -void png2raw_instance_free(png2raw_instancep inst); - -const char* png2raw_get_error_message(enum png2raw_error err); - -// Call after creating an instance to set the file -enum png2raw_error png2raw_set_file_name(png2raw_instancep inst, const char* file_name); - -// Call after setting the file -enum png2raw_error png2raw_read_info(png2raw_instancep inst); - -// These three can only be called right after png2raw_read_info (before the png2raw_read_..._init functions) -enum png2raw_error png2raw_get_dimensions(png2raw_instancep inst, uint32_t* widthp, uint32_t* heightp); -enum png2raw_error png2raw_is_paletted(png2raw_instancep inst, bool* is_paletted_p); -// The last two arguments work the same as those of the png2raw_read_rgba32 function. -enum png2raw_error png2raw_get_palette_colors_rgba32(png2raw_instancep inst, size_t* palette_data_size_p, - uint8_t* palette_data); - -// After calling png2raw_read_info, the png2raw_read_..._init functions may be called - -enum png2raw_error png2raw_read_rgba32_init(png2raw_instancep inst); -/** - * Store the image pixels as rgba32 into `image_data`. - * - * png2raw_read_rgba32_init must have been called first. - * - * Call with `&image_data_size, NULL` to get the size of the buffer to allocate. - * Expect the error PNG2RAW_ERR_BAD_BUFFER_SIZE to be returned then. - * Then call again with `&image_data_size, image_data`. - */ -enum png2raw_error png2raw_read_rgba32(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data); - -enum png2raw_error png2raw_read_palette_indices_init(png2raw_instancep inst); -// The last two arguments work the same as those of the png2raw_read_rgba32 function. -// Can optionally be called after png2raw_read_palette_indices_init, but before png2raw_read_palette_indices -enum png2raw_error png2raw_read_palette_indices(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data); - -#endif diff --git a/tools/assets/png2raw/png2raw.py b/tools/assets/png2raw/png2raw.py deleted file mode 100644 index 8c4ff8ecd6..0000000000 --- a/tools/assets/png2raw/png2raw.py +++ /dev/null @@ -1,216 +0,0 @@ -from __future__ import annotations - -import ctypes -import enum -from pathlib import Path -from typing import Union - - -png2raw = ctypes.cdll.LoadLibrary(Path(__file__).parent / "png2raw") - -# enum png2raw_error { ... }; -png2raw_error = ctypes.c_int - - -class PNG2RAW_ERR(enum.IntEnum): - OK = 0 - INST_NULL = enum.auto() - INST_BAD_STATE = enum.auto() - CANT_OPEN_FOR_READING = enum.auto() - CANT_READ_HEADER_BYTES = enum.auto() - NOT_A_PNG = enum.auto() - LIBPNG_PNG_PTR_NULL = enum.auto() - LIBPNG_INFO_PTR_NULL = enum.auto() - BUFFER_SIZE_P_NULL = enum.auto() - BAD_BUFFER_SIZE = enum.auto() - BUFFER_NULL = enum.auto() - PNG_IS_NOT_PALETTED = enum.auto() - MAX = enum.auto() - - -# struct png2raw_instance; -class png2raw_instance(ctypes.Structure): - pass - - -# typedef struct png2raw_instance* png2raw_instancep; -png2raw_instancep = ctypes.POINTER(png2raw_instance) - -# png2raw_instancep png2raw_instance_new(); -png2raw.png2raw_instance_new.restype = png2raw_instancep -png2raw.png2raw_instance_new.argtypes = [] - -# void png2raw_instance_free(png2raw_instancep inst); -png2raw.png2raw_instance_free.restype = None -png2raw.png2raw_instance_free.argtypes = [png2raw_instancep] - -# const char* png2raw_get_error_message(enum png2raw_error err); -png2raw.png2raw_get_error_message.restype = ctypes.POINTER(ctypes.c_char) -png2raw.png2raw_get_error_message.argtypes = [png2raw_error] - -# enum png2raw_error png2raw_set_file_name(png2raw_instancep inst, const char* file_name); -png2raw.png2raw_set_file_name.restype = png2raw_error -png2raw.png2raw_set_file_name.argtypes = [ - png2raw_instancep, - ctypes.POINTER(ctypes.c_char), -] - -# enum png2raw_error png2raw_read_info(png2raw_instancep inst); -png2raw.png2raw_read_info.restype = png2raw_error -png2raw.png2raw_read_info.argtypes = [png2raw_instancep] - -# enum png2raw_error png2raw_get_dimensions(png2raw_instancep inst, uint32_t* widthp, uint32_t* heightp); -png2raw.png2raw_get_dimensions.restype = png2raw_error -png2raw.png2raw_get_dimensions.argtypes = [ - png2raw_instancep, - ctypes.POINTER(ctypes.c_uint32), - ctypes.POINTER(ctypes.c_uint32), -] -# enum png2raw_error png2raw_is_paletted(png2raw_instancep inst, bool* is_paletted_p); -png2raw.png2raw_is_paletted.restype = png2raw_error -png2raw.png2raw_is_paletted.argtypes = [ - png2raw_instancep, - ctypes.POINTER(ctypes.c_bool), -] -# enum png2raw_error png2raw_get_palette_colors_rgba32(png2raw_instancep inst, size_t* palette_data_size_p, uint8_t* palette_data); -png2raw.png2raw_get_palette_colors_rgba32.restype = png2raw_error -png2raw.png2raw_get_palette_colors_rgba32.argtypes = [ - png2raw_instancep, - ctypes.POINTER(ctypes.c_size_t), - ctypes.POINTER(ctypes.c_uint8), -] - -# enum png2raw_error png2raw_read_rgba32_init(png2raw_instancep inst); -png2raw.png2raw_read_rgba32_init.restype = png2raw_error -png2raw.png2raw_read_rgba32_init.argtypes = [png2raw_instancep] -# enum png2raw_error png2raw_read_rgba32(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data); -png2raw.png2raw_read_rgba32.restype = png2raw_error -png2raw.png2raw_read_rgba32.argtypes = [ - png2raw_instancep, - ctypes.POINTER(ctypes.c_size_t), - ctypes.POINTER(ctypes.c_uint8), -] - -# enum png2raw_error png2raw_read_palette_indices_init(png2raw_instancep inst); -png2raw.png2raw_read_palette_indices_init.restype = png2raw_error -png2raw.png2raw_read_palette_indices_init.argtypes = [png2raw_instancep] -# enum png2raw_error png2raw_read_palette_indices(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data); -png2raw.png2raw_read_palette_indices.restype = png2raw_error -png2raw.png2raw_read_palette_indices.argtypes = [ - png2raw_instancep, - ctypes.POINTER(ctypes.c_size_t), - ctypes.POINTER(ctypes.c_uint8), -] - - -class Instance: - def __init__(self, file_name: Union[Path, str]): - self.file_name = file_name - - def _check_err(self, err: png2raw_error, okay_errors: set[PNG2RAW_ERR] = set()): - if err == PNG2RAW_ERR.OK or err in okay_errors: - return - err_message = png2raw.png2raw_get_error_message(err) - try: - err_enum = PNG2RAW_ERR(err) - except ValueError: - err_enum = None - raise Exception(err, err_enum, ctypes.string_at(err_message).decode()) - - def __enter__(self): - self._inst = png2raw.png2raw_instance_new() - self._check_err( - png2raw.png2raw_set_file_name(self._inst, str(self.file_name).encode()) - ) - self._check_err(png2raw.png2raw_read_info(self._inst)) - return self - - def get_dimensions(self): - width = ctypes.c_uint32(0) - height = ctypes.c_uint32(0) - self._check_err( - png2raw.png2raw_get_dimensions( - self._inst, ctypes.byref(width), ctypes.byref(height) - ) - ) - return width.value, height.value - - def is_paletted(self): - is_paletted_bool = ctypes.c_bool() - self._check_err( - png2raw.png2raw_is_paletted(self._inst, ctypes.byref(is_paletted_bool)) - ) - return is_paletted_bool.value - - def get_palette_rgba32(self): - palette_data_size = ctypes.c_size_t(0) - self._check_err( - png2raw.png2raw_get_palette_colors_rgba32( - self._inst, ctypes.byref(palette_data_size), None - ), - okay_errors={PNG2RAW_ERR.BAD_BUFFER_SIZE, PNG2RAW_ERR.BUFFER_NULL}, - ) - palette_data = (ctypes.c_uint8 * palette_data_size.value)() - self._check_err( - png2raw.png2raw_get_palette_colors_rgba32( - self._inst, ctypes.byref(palette_data_size), palette_data - ) - ) - return palette_data - - def read_to_rgba32(self): - self._check_err(png2raw.png2raw_read_rgba32_init(self._inst)) - - image_data_size = ctypes.c_size_t(0) - self._check_err( - png2raw.png2raw_read_rgba32( - self._inst, ctypes.byref(image_data_size), None - ), - okay_errors={PNG2RAW_ERR.BAD_BUFFER_SIZE, PNG2RAW_ERR.BUFFER_NULL}, - ) - image_data = (ctypes.c_uint8 * image_data_size.value)() - self._check_err( - png2raw.png2raw_read_rgba32( - self._inst, ctypes.byref(image_data_size), image_data - ) - ) - return image_data - - def read_palette_indices(self): - self._check_err(png2raw.png2raw_read_palette_indices_init(self._inst)) - - image_data_size = ctypes.c_size_t(0) - self._check_err( - png2raw.png2raw_read_palette_indices( - self._inst, ctypes.byref(image_data_size), None - ), - okay_errors={PNG2RAW_ERR.BAD_BUFFER_SIZE, PNG2RAW_ERR.BUFFER_NULL}, - ) - image_data = (ctypes.c_uint8 * image_data_size.value)() - self._check_err( - png2raw.png2raw_read_palette_indices( - self._inst, ctypes.byref(image_data_size), image_data - ) - ) - return image_data - - def __exit__(self, exc_type, exc_val, exc_tb): - png2raw.png2raw_instance_free(self._inst) - - -def main(): - with Png2rawInstance( - Path( - "/home/dragorn421/Documents/oot/assets/objects/object_link_boy/eyes_shock.ci8.png" - ) - ) as inst: - pal_rgba32 = inst.get_palette_rgba32() - print(pal_rgba32[:16]) - rgba32 = inst.read_to_rgba32() - print(rgba32[:16]) - rgba32 = inst.read_palette_indices() - print(rgba32[:16]) - - -if __name__ == "__main__": - main() diff --git a/tools/assets/png2raw/raw2png.c b/tools/assets/png2raw/raw2png.c deleted file mode 100644 index 0df2b162c3..0000000000 --- a/tools/assets/png2raw/raw2png.c +++ /dev/null @@ -1,160 +0,0 @@ -#include -#include - -#include - -#include "raw2png.h" - -void user_error_fn(png_structp png_ptr, png_const_charp error_msg) { - fprintf(stderr, "[raw2png] error: %s\n", error_msg); - abort(); //! TODO ... I guess don't do this (bad for use as library) but idk what, then -} - -void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { - fprintf(stderr, "[raw2png] warning: %s\n", warning_msg); -} - -#define RGBA32_PIXEL_SIZE 4 - -const char* error_messages[] = { - [RAW2PNG_ERR_OK] = "OK", - [RAW2PNG_ERR_CANT_OPEN_FOR_WRITING] = "Can't open the file for writing", - [RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL] = "png_create_read_struct returned NULL", - [RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL] = "png_create_info_struct returned NULL", - [RAW2PNG_ERR_BAD_NUM_PALETTE] = "num_palette should be between 1 and 256 (inclusive)", - [RAW2PNG_ERR_DATA_EXCEEDS_NUM_PALETTE] = "image_data contains indices exceeding (>=) num_palette", -}; -const char* raw2png_get_error_message(enum raw2png_error err) { - if (err < 0 || err >= RAW2PNG_ERR_MAX) { - return "raw2png_get_error_message: bad err value"; - } - const char* msg = error_messages[err]; - if (msg == NULL) { - return "raw2png_get_error_message: missing message for err value"; - } - return msg; -} - -enum raw2png_error raw2png_write(const char* file_name, uint32_t width, uint32_t height, uint8_t* image_data) { - FILE* fp = fopen(file_name, "wb"); - - if (fp == NULL) { - return RAW2PNG_ERR_CANT_OPEN_FOR_WRITING; - } - - void* user_error_ptr = NULL; - png_structp png_ptr = - png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr, user_error_fn, user_warning_fn); - - if (png_ptr == NULL) { - fclose(fp); - return RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - - if (info_ptr == NULL) { - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); - fclose(fp); - return RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL; - } - - png_init_io(png_ptr, fp); - - // Write rgba32: 8 bits per pixel, RGBA - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - png_bytep row_pointers[height]; - for (uint32_t row = 0; row < height; row++) { - row_pointers[row] = image_data + row * width * RGBA32_PIXEL_SIZE; - } - - png_set_rows(png_ptr, info_ptr, row_pointers); - - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); - - // libpng does not close the stream by itself. (experimentally and from its source) - fclose(fp); - - return RAW2PNG_ERR_OK; -} - -enum raw2png_error raw2png_write_paletted(const char* file_name, uint32_t width, uint32_t height, uint8_t* palette_data, - int num_palette, uint8_t* image_data) { - if (num_palette < 1 || num_palette > 256) { - // Paletted png files are at most 8 bits per pixel, - // so the palette cannot (or doesn't need to?) be larger than 256 colors. - return RAW2PNG_ERR_BAD_NUM_PALETTE; - } - // Check image_data doesn't index outside the palette - // libpng also checks this and will error if needed, so we check ourselves preemptively. - for (uint32_t y = 0; y != height; y++) - for (uint32_t x = 0; x != width; x++) - if (image_data[y * width + x] >= num_palette) - return RAW2PNG_ERR_DATA_EXCEEDS_NUM_PALETTE; - - FILE* fp = fopen(file_name, "wb"); - - if (fp == NULL) { - return RAW2PNG_ERR_CANT_OPEN_FOR_WRITING; - } - - void* user_error_ptr = NULL; - png_structp png_ptr = - png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr, user_error_fn, user_warning_fn); - - if (png_ptr == NULL) { - fclose(fp); - return RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - - if (info_ptr == NULL) { - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); - fclose(fp); - return RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL; - } - - png_init_io(png_ptr, fp); - - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - // 1 <= num_palette <= 256 is enforced above - png_color palette[num_palette]; - png_byte trans_alpha[num_palette]; - - // Split the input rgba32 palette in palette_data between RGB (into palette) and A (into trans_alpha) - for (int palette_index = 0; palette_index < num_palette; palette_index++) { - png_color* rgb = &palette[palette_index]; - png_byte* a = &trans_alpha[palette_index]; - - uint8_t* in_rgba = palette_data + palette_index * RGBA32_PIXEL_SIZE; - rgb->red = in_rgba[0]; - rgb->green = in_rgba[1]; - rgb->blue = in_rgba[2]; - // The tRNS chunk could be shortened (or even missing) if the last alpha values are all opaque 255, - // but this storage optimization hinted at by the png specification is in my opinion not worth implementing. - *a = in_rgba[3]; - } - - png_set_PLTE(png_ptr, info_ptr, palette, num_palette); - - // The trans_color argument is only relevant for non-paletted images, pass NULL - png_set_tRNS(png_ptr, info_ptr, trans_alpha, num_palette, NULL); - - png_bytep row_pointers[height]; - for (uint32_t row = 0; row < height; row++) { - row_pointers[row] = image_data + row * width; - } - - png_set_rows(png_ptr, info_ptr, row_pointers); - - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); - - fclose(fp); - - return RAW2PNG_ERR_OK; -} diff --git a/tools/assets/png2raw/raw2png.h b/tools/assets/png2raw/raw2png.h deleted file mode 100644 index d2f6bd1b96..0000000000 --- a/tools/assets/png2raw/raw2png.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef RAW2PNG_H -#define RAW2PNG_H - -enum raw2png_error { - RAW2PNG_ERR_OK, // no error - RAW2PNG_ERR_CANT_OPEN_FOR_WRITING, - RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL, - RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL, - RAW2PNG_ERR_BAD_NUM_PALETTE, - RAW2PNG_ERR_DATA_EXCEEDS_NUM_PALETTE, - RAW2PNG_ERR_MAX -}; - -const char* raw2png_get_error_message(enum raw2png_error err); - -enum raw2png_error raw2png_write(const char* file_name, uint32_t width, uint32_t height, uint8_t* image_data); - -enum raw2png_error raw2png_write_paletted(const char* file_name, uint32_t width, uint32_t height, uint8_t* palette_data, - int num_palette, uint8_t* image_data); - -#endif diff --git a/tools/assets/png2raw/raw2png.py b/tools/assets/png2raw/raw2png.py deleted file mode 100644 index 83f4fb13ca..0000000000 --- a/tools/assets/png2raw/raw2png.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -import ctypes -import enum -from pathlib import Path -from typing import Union - - -raw2png = ctypes.cdll.LoadLibrary(Path(__file__).parent / "raw2png") - -# enum raw2png_error { ... }; -raw2png_error = ctypes.c_int - - -class RAW2PNG_ERR(enum.IntEnum): - OK = 0 - CANT_OPEN_FOR_WRITING = enum.auto() - LIBPNG_PNG_PTR_NULL = enum.auto() - LIBPNG_INFO_PTR_NULL = enum.auto() - BAD_NUM_PALETTE = enum.auto() - DATA_EXCEEDS_NUM_PALETTE = enum.auto() - MAX = enum.auto() - - -# const char* raw2png_get_error_message(enum raw2png_error err); -raw2png.raw2png_get_error_message.restype = ctypes.POINTER(ctypes.c_char) -raw2png.raw2png_get_error_message.argtypes = [raw2png_error] - -# enum raw2png_error raw2png_write(const char* file_name, uint32_t width, uint32_t height, uint8_t* image_data); -raw2png.raw2png_write.restype = raw2png_error -raw2png.raw2png_write.argtypes = [ - ctypes.POINTER(ctypes.c_char), - ctypes.c_uint32, - ctypes.c_uint32, - ctypes.POINTER(ctypes.c_uint8), -] - -# enum raw2png_error raw2png_write_paletted(const char* file_name, uint32_t width, uint32_t height, uint8_t* palette_data, int num_palette, uint8_t* image_data); -raw2png.raw2png_write_paletted.restype = raw2png_error -raw2png.raw2png_write_paletted.argtypes = [ - ctypes.POINTER(ctypes.c_char), - ctypes.c_uint32, - ctypes.c_uint32, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_int, - ctypes.POINTER(ctypes.c_uint8), -] - - -def _check_err(err: raw2png_error, okay_errors: set[RAW2PNG_ERR] = set()): - if err == RAW2PNG_ERR.OK or err in okay_errors: - return - err_message = raw2png.raw2png_get_error_message(err) - try: - err_enum = RAW2PNG_ERR(err) - except ValueError: - err_enum = None - raise Exception(err, err_enum, ctypes.string_at(err_message).decode()) - - -def write(file_name: Union[Path, str], width: int, height: int, image_data): - # About the type image_data should be: - # ctypes._CData.from_buffer docs: "must support the writeable buffer interface" - # - doesn't work with list() (not bytes-like) - # - doesn't work with bytes() (not writable) - # - bytearray() OK - # - (ctypes.c_uint8 * n)() OK - image_data_ctypes_type = (ctypes.c_uint8 * (height * width * 4)) - try: - image_data_ctypes = image_data_ctypes_type.from_buffer(image_data) - except TypeError: - print(type(image_data)) - raise - _check_err( - raw2png.raw2png_write(str(file_name).encode(), width, height, image_data_ctypes) - ) - - -def write_paletted( - file_name: Union[Path, str], - width: int, - height: int, - palette_data, - num_palette: int, - image_data, -): - # If those are not integers, there would be an error anyway from constructing the ctypes array types below, - # but this way with asserts the raised error is clearer. - assert isinstance(width, int) - assert isinstance(height, int) - assert isinstance(num_palette, int) - - palette_data_ctypes = (ctypes.c_uint8 * (num_palette * 4)).from_buffer(palette_data) - image_data_ctypes = (ctypes.c_uint8 * (height * width)).from_buffer(image_data) - _check_err( - raw2png.raw2png_write_paletted( - str(file_name).encode(), - width, - height, - palette_data_ctypes, - num_palette, - image_data_ctypes, - ) - ) - - -def main(): - write( - Path(__file__).parent / "my421tést.png", - 1, - 1, - (ctypes.c_uint8 * 4)(255, 0, 0, 120), - ) - - -if __name__ == "__main__": - main() diff --git a/tools/assets/png2raw/raw2terminal.py b/tools/assets/png2raw/raw2terminal.py deleted file mode 100755 index 30a7d7d6a4..0000000000 --- a/tools/assets/png2raw/raw2terminal.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 - -""" -Display rgba32 or i8 binary data read from stdin, by (ab)using ANSI color codes. -""" - -import argparse -import sys -import time - - -ESC = b"\x1B" # '\e' -RESET = ESC + b"[m" - - -def rgb(r, g, b): - return ESC + b"[48;2;" + ";".join((str(r), str(g), str(b))).encode("ascii") + b"m" - - -def lerp(a, b, t): - assert 0 <= t <= 1 - return a * (1 - t) + b * t - - -def handle_rgba32( - *, - width, - background=(lambda x, y: ((50, 50, 50) if (x + y) % 2 == 0 else (200, 200, 200))), -): - count = 0 - x = 0 - y = 0 - while True: - rgba32 = b"" - while len(rgba32) < 4: - if rgba32: - # Wait 10ms if wasn't able to fetch 4 bytes immediately - time.sleep(0.010) - data = sys.stdin.buffer.read(4 - len(rgba32)) - if not data: - return - rgba32 += data - assert len(rgba32) == 4 - - in_r, in_g, in_b, in_a = rgba32 - - bg_r, bg_g, bg_b = background(x, y) - - r = round(lerp(bg_r, in_r, in_a / 255)) - g = round(lerp(bg_g, in_g, in_a / 255)) - b = round(lerp(bg_b, in_b, in_a / 255)) - - sys.stdout.buffer.write(rgb(r, g, b)) - sys.stdout.buffer.write(b" ") - - count += 1 - x += 1 - if count % width == 0: - x = 0 - y += 1 - sys.stdout.buffer.write(RESET + b"\n") - - -def handle_rgba16( - *, - width, - background=(lambda x, y: ((50, 50, 50) if (x + y) % 2 == 0 else (200, 200, 200))), -): - count = 0 - x = 0 - y = 0 - while True: - rgba16 = b"" - while len(rgba16) < 2: - if rgba16: - time.sleep(0.010) - data = sys.stdin.buffer.read(2 - len(rgba16)) - if not data: - return - rgba16 += data - assert len(rgba16) == 2 - - rgba16_hi, rgba16_lo = rgba16 - rgba16 = (rgba16_hi << 8) | rgba16_lo - - def rgba16_component_expand(rgba16, shift): - v = (rgba16 >> shift) & 0x1F - return (v << 3) | (v >> 2) - - in_r = rgba16_component_expand(rgba16, 11) - in_g = rgba16_component_expand(rgba16, 6) - in_b = rgba16_component_expand(rgba16, 1) - in_a = 255 if (rgba16 & 1) else 0 - - bg_r, bg_g, bg_b = background(x, y) - - r = round(lerp(bg_r, in_r, in_a / 255)) - g = round(lerp(bg_g, in_g, in_a / 255)) - b = round(lerp(bg_b, in_b, in_a / 255)) - - sys.stdout.buffer.write(rgb(r, g, b)) - sys.stdout.buffer.write(b" ") - - count += 1 - x += 1 - if count % width == 0: - x = 0 - y += 1 - sys.stdout.buffer.write(RESET + b"\n") - - -def handle_i8( - *, - width, -): - count = 0 - while True: - i = sys.stdin.buffer.read(1) - if not i: - return - i = i[0] - - r = g = b = i - - sys.stdout.buffer.write(rgb(r, g, b)) - sys.stdout.buffer.write(b" ") - - count += 1 - if count % width == 0: - sys.stdout.buffer.write(RESET + b"\n") - - -handlers = { - "rgba32": handle_rgba32, - "i8": handle_i8, - "rgba16": handle_rgba16, -} - -argparser = argparse.ArgumentParser() -argparser.add_argument("--width", default=64, type=int) -argparser.add_argument("--format", default="rgba32", choices=handlers.keys()) -args = argparser.parse_args() - -try: - handlers[args.format]( - width=args.width, - ) -finally: - sys.stdout.buffer.write(RESET + b"\n") diff --git a/tools/assets/png2raw/test.py b/tools/assets/png2raw/test.py deleted file mode 100644 index caeede18c9..0000000000 --- a/tools/assets/png2raw/test.py +++ /dev/null @@ -1,144 +0,0 @@ -import unittest - -from pathlib import Path -import random - -import raw2png -import png2raw - -test_dir = Path(__file__).parent / "test_out_folder" -test_dir.mkdir(parents=True, exist_ok=True) - -test_pngs_dirs: list[Path] = [] - -# https://sourceforge.net/p/libpng/code/ci/master/tree/ -# git clone https://git.code.sf.net/p/libpng/code libpng-code -libpng_dir = Path("/home/dragorn421/Documents/libpng-code/") -# libpng_dir = None -if libpng_dir: - test_pngs_dirs.extend( - libpng_dir / libpng_pngs_dir - for libpng_pngs_dir in [ - Path("contrib/testpngs/"), - Path("contrib/pngsuite/"), - Path("contrib/pngsuite/interlaced/"), - ] - ) - -test_pngs: list[Path] = [] -for test_pngs_dir in test_pngs_dirs: - test_pngs.extend(test_pngs_dir.glob("*.png")) - -if libpng_dir: - # apparently ffmpeg fails to properly understand this one, so one test falsely fails - # The right pixel is transparent white, ffmpeg says opaque white - test_pngs.remove(libpng_dir / Path("contrib/testpngs/gray-1-linear-tRNS.png")) - - # I guess this one is 16 bits per pixel - # The 8-bit intensity of three pixels is off by 1, so it's probably just a rounding difference - test_pngs.remove(libpng_dir / Path("contrib/testpngs/gray-16-sRGB.png")) - - # TODO these false fails happen for a lot more pngs from libpng, from a quick look for similar reasons - # -> check and remove them - - -# test_pngs.extend(Path("/home/dragorn421/Documents/oot/assets").glob("**/*.png")) - -# Testing 100 pngs takes 5-6 seconds for me. Definitely need to reduce the amount of pngs being tested -test_pngs = random.sample(test_pngs, 100) - - -for p in test_pngs: - assert p.is_file(), p - - -class Tests(unittest.TestCase): - def test_random_rgba32(self): - for rand_i in range(10): - with self.subTest(rand_i=rand_i): - width = random.randint(10, 20) - height = random.randint(10, 20) - image_data = bytearray(random.randbytes(width * height * 4)) - file_name = test_dir / f"random_rgba32_{rand_i}.png" - raw2png.write(file_name, width, height, image_data) - with png2raw.Instance(file_name) as inst: - self.assertFalse(inst.is_paletted()) - self.assertEqual(inst.get_dimensions(), (width, height)) - self.assertEqual(inst.read_to_rgba32(), image_data) - - def test_random_paletted(self): - for rand_i in range(10): - with self.subTest(rand_i=rand_i): - width = random.randint(10, 20) - height = random.randint(10, 20) - num_palette = random.randint(1, 256) - palette_data = bytearray(random.randbytes(num_palette * 4)) - image_data = bytearray( - b % num_palette for b in random.randbytes(height * width) - ) - file_name = test_dir / f"random_paletted_{rand_i}.png" - raw2png.write_paletted( - file_name, width, height, palette_data, num_palette, image_data - ) - with png2raw.Instance(file_name) as inst: - self.assertTrue(inst.is_paletted()) - self.assertEqual(inst.get_dimensions(), (width, height)) - self.assertEqual(inst.get_palette_rgba32(), palette_data) - self.assertEqual(inst.read_palette_indices(), image_data) - - def test_rewrite(self): - for file_name in test_pngs: - with self.subTest(file_name=file_name): - # Copy original files to the test folder for easier visual comparison - og_out = test_dir / (file_name.stem + "#OG" + file_name.suffix) - og_out.write_bytes(file_name.read_bytes()) - rewrite_out = test_dir / file_name.name - with png2raw.Instance(file_name) as inst: - width, height = inst.get_dimensions() - if inst.is_paletted(): - palette_data = inst.get_palette_rgba32() - image_data = inst.read_palette_indices() - - self.assertLessEqual(len(palette_data), 256 * 4) - self.assertEqual(len(image_data), height * width) - - raw2png.write_paletted( - rewrite_out, - width, - height, - palette_data, - len(palette_data) // 4, - image_data, - ) - else: - image_data = inst.read_to_rgba32() - - self.assertEqual(len(image_data), height * width * 4) - - raw2png.write(rewrite_out, width, height, image_data) - self.assertTrue(rewrite_out.is_file()) - import subprocess - - og_out_rgba32_bin = og_out.with_suffix(".rgba32.bin") - rewrite_out_rgba32_bin = rewrite_out.with_suffix(".rgba32.bin") - subprocess.run( - "ffmpeg -y -i".split() - + [str(og_out)] - + "-f rawvideo -pix_fmt rgba".split() - + [str(og_out_rgba32_bin)], - capture_output=subprocess.DEVNULL, - ) - subprocess.run( - "ffmpeg -y -i".split() - + [str(rewrite_out)] - + "-f rawvideo -pix_fmt rgba".split() - + [str(rewrite_out_rgba32_bin)], - capture_output=subprocess.DEVNULL, - ) - self.assertEqual( - og_out_rgba32_bin.read_bytes(), rewrite_out_rgba32_bin.read_bytes() - ) - - -if __name__ == "__main__": - unittest.main()