mirror of
https://github.com/zeldaret/oot.git
synced 2025-08-24 16:01:26 +00:00
C build_from_png
This commit is contained in:
parent
f1987ded85
commit
72c566a7fa
20 changed files with 615 additions and 1946 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
1
tools/assets/build_from_png/.gitignore
vendored
Normal file
1
tools/assets/build_from_png/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
build_from_png
|
17
tools/assets/build_from_png/Makefile
Normal file
17
tools/assets/build_from_png/Makefile
Normal 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
|
554
tools/assets/build_from_png/build_from_png.c
Normal file
554
tools/assets/build_from_png/build_from_png.c
Normal 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;
|
||||
}
|
|
@ -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):
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
5
tools/assets/png2raw/.gitignore
vendored
5
tools/assets/png2raw/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
|||
__pycache__/
|
||||
test_out_folder/
|
||||
png2raw
|
||||
png2raw_main
|
||||
raw2png
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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()
|
|
@ -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")
|
|
@ -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()
|
Loading…
Add table
Add a link
Reference in a new issue