1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-08-23 23:41:24 +00:00

C build_from_png

This commit is contained in:
Dragorn421 2025-02-18 09:21:48 +01:00
parent f1987ded85
commit 72c566a7fa
No known key found for this signature in database
GPG key ID: 381AEBAF3D429335
20 changed files with 615 additions and 1946 deletions

View file

@ -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 $< $@

View file

@ -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

View file

@ -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()

View file

@ -0,0 +1 @@
build_from_png

View file

@ -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

View file

@ -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 <assert.h>
#include <dirent.h>
#include <errno.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#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.<u32|u64>.png (non-ci formats or ci formats with a non-shared tlut)\n");
fprintf(stderr, " - texName.ci<4|8>.tlut_tlutName_<u32|u64>.<u32|u64>.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;
}

View file

@ -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):

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -1,5 +0,0 @@
__pycache__/
test_out_folder/
png2raw
png2raw_main
raw2png

View file

@ -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)

View file

@ -1,678 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <png.h>
#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] <path/to/image.png>\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;
}

View file

@ -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

View file

@ -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()

View file

@ -1,160 +0,0 @@
#include <stdlib.h>
#include <stdint.h>
#include <png.h>
#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;
}

View file

@ -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

View file

@ -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()

View file

@ -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")

View file

@ -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()