papermario/tools/build/img/build.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

265 lines
9.3 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2020-10-31 19:30:16 +01:00
from sys import argv, stderr
from math import floor, ceil
2021-02-06 23:26:35 +01:00
from glob import glob
import png # type: ignore
2020-10-31 19:30:16 +01:00
def unpack_color(s):
r = (s >> 11) & 0x1F
g = (s >> 6) & 0x1F
b = (s >> 1) & 0x1F
a = (s & 1) * 0xFF
2020-10-31 19:30:16 +01:00
r = ceil(0xFF * (r / 31))
g = ceil(0xFF * (g / 31))
b = ceil(0xFF * (b / 31))
return r, g, b, a
2020-10-31 19:30:16 +01:00
def pack_color(r, g, b, a):
r = r >> 3
g = g >> 3
b = b >> 3
a = a >> 7
2020-10-31 19:30:16 +01:00
return (r << 11) | (g << 6) | (b << 1) | a
2020-10-31 19:30:16 +01:00
2020-10-31 19:30:16 +01:00
def rgb_to_intensity(r, g, b):
return round(r * 0.2126 + g * 0.7152 + 0.0722 * b)
2020-10-31 19:30:16 +01:00
def iter_in_groups(iterable, n, fillvalue=None):
Splat refactor (#257) * all non-world rodata migrated * data disasm * kinda working * updated yaml * bloop * linker header * configure 2.0 * bin * mass rename to remove code_ * pause rename * battle partner stuff * whew * more renames * more renames * more renaming * it builds! * updates * remove main prefix * one more thing * crc, yay0 * .data, .rodata, .bss * img * dead_atan2 * it buildsgit add -A * split battle/partner/6FAD10 * rm &s on sleepy_sheep syms * sha1sum ninja rule description * OK but commented out PaperMarioMapFS and PaperMarioNpcSprites * uncomment * fix mapfs * match func_8003CFB4 * . * clean up and name npc_iter_no_op * npc.c * enable cc warnings * name npc_find_near * use singular options.asset_path * smores * cc_dsl only when needed * kinda fix configure for splat refactor2 * ok! * new msg format * remove old msg format docs * slight bug fixes, splat adjustment * git subrepo pull (merge) --force tools/splat subrepo: subdir: "tools/splat" merged: "cfc140bb76" upstream: origin: "https://github.com/ethteck/splat.git" branch: "master" commit: "cfc140bb76" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" * git subrepo pull (merge) --force tools/splat subrepo: subdir: "tools/splat" merged: "85349befcd" upstream: origin: "https://github.com/ethteck/splat.git" branch: "master" commit: "85349befcd" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" * Update symbol addrs * git subrepo pull tools/splat subrepo: subdir: "tools/splat" merged: "a44631e194" upstream: origin: "https://github.com/ethteck/splat.git" branch: "master" commit: "a44631e194" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" Co-authored-by: Alex Bates <hi@imalex.xyz>
2021-04-13 09:47:52 +02:00
from itertools import zip_longest
2020-10-31 19:30:16 +01:00
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
def reversed_if(iterator, cond):
if cond:
return reversed(list(iterator))
else:
return iterator
class Converter:
def __init__(self, mode, infile, flip_x: bool = False, flip_y: bool = False):
2020-10-31 19:30:16 +01:00
self.mode = mode
self.infile = infile
self.flip_x = flip_x
self.flip_y = flip_y
2020-10-31 19:30:16 +01:00
assert self.flip_x == False, "flip_x is not supported"
2020-10-31 19:30:16 +01:00
self.warned = False
def warn(self, msg):
if not self.warned:
self.warned = True
print(self.infile + ": warning: " + msg, file=stderr)
def convert(self):
out_bytes = bytearray()
out_width = 0
out_height = 0
2020-10-31 19:30:16 +01:00
img = png.Reader(self.infile)
if self.mode == "rgba32":
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
out_bytes += row
2020-10-31 19:30:16 +01:00
elif self.mode == "rgba16":
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
2020-10-31 19:30:16 +01:00
elif self.mode == "ci8":
(out_width, out_height, data, info) = img.read()
for row in reversed_if(data, self.flip_y):
out_bytes += row
2020-10-31 19:30:16 +01:00
elif self.mode == "ci4":
(out_width, out_height, data, info) = img.read()
for row in reversed_if(data, self.flip_y):
for a, b in iter_in_groups(row, 2):
byte = (a << 4) | b
byte = byte & 0xFF
out_bytes += byte.to_bytes(1, byteorder="big")
2020-11-02 17:39:29 +01:00
elif self.mode == "palette":
2020-10-31 19:30:16 +01:00
img.preamble(True)
palette = img.palette(alpha="force")
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
2020-10-31 19:30:16 +01:00
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
2020-10-31 19:30:16 +01:00
elif self.mode == "ia4":
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2):
i1 = rgb_to_intensity(*c1[:3])
a1 = c1[3]
2020-10-31 19:30:16 +01:00
i2 = rgb_to_intensity(*c2[:3])
a2 = c2[3]
2020-10-31 19:30:16 +01:00
i1 = i1 >> 5
i2 = i2 >> 5
2020-10-31 19:30:16 +01:00
if a1 not in (0, 0xFF) or a2 not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
if c1[0] != c1[1] != c1[2]:
self.warn("grayscale mode but image is not")
if c2[0] != c2[1] != c2[2]:
self.warn("grayscale mode but image is not")
2020-10-31 19:30:16 +01:00
a1 = 1 if a1 > 128 else 0
a2 = 1 if a2 > 128 else 0
2020-10-31 19:30:16 +01:00
h = (i1 << 1) | a1
l = (i2 << 1) | a2
2020-10-31 19:30:16 +01:00
byte = (h << 4) | l
out_bytes += byte.to_bytes(1, byteorder="big")
2020-10-31 19:30:16 +01:00
elif self.mode == "ia8":
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
i = rgb_to_intensity(*rgba[:3])
a = rgba[3]
2020-10-31 19:30:16 +01:00
i = floor(15 * (i / 0xFF))
a = floor(15 * (a / 0xFF))
2020-10-31 19:30:16 +01:00
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
2020-10-31 19:30:16 +01:00
byte = (i << 4) | a
out_bytes += byte.to_bytes(1, byteorder="big")
2020-10-31 19:30:16 +01:00
elif self.mode == "ia16":
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
i = rgb_to_intensity(*rgba[:3])
a = rgba[3]
2020-10-31 19:30:16 +01:00
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
2020-10-31 19:30:16 +01:00
out_bytes += bytes((i, a))
2020-10-31 19:30:16 +01:00
elif self.mode == "i4":
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2):
if c1[3] != 0xFF or c2[3] != 0xFF:
self.warn("discarding alpha channel")
2020-10-31 19:30:16 +01:00
i1 = rgb_to_intensity(*c1[:3])
i2 = rgb_to_intensity(*c2[:3])
2020-10-31 19:30:16 +01:00
i1 = floor(15 * (i1 / 0xFF))
i2 = floor(15 * (i2 / 0xFF))
2020-10-31 19:30:16 +01:00
if c1[0] != c1[1] != c1[2]:
self.warn("grayscale mode but image is not")
if c2[0] != c2[1] != c2[2]:
self.warn("grayscale mode but image is not")
2020-10-31 19:30:16 +01:00
byte = (i1 << 4) | i2
out_bytes += byte.to_bytes(1, byteorder="big")
2020-10-31 19:30:16 +01:00
elif self.mode == "i8":
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
if rgba[3] != 0xFF or rgba[3] != 0xFF:
self.warn("discarding alpha channel")
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
i = rgb_to_intensity(*rgba[:3])
out_bytes += i.to_bytes(1, byteorder="big")
2021-02-06 23:26:35 +01:00
elif self.mode == "party":
(out_width, out_height, data, info) = img.read()
2021-02-06 23:26:35 +01:00
img.preamble(True)
palette = img.palette(alpha="force")
# palette
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
2021-02-06 23:26:35 +01:00
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
2021-02-06 23:26:35 +01:00
# ci 8
for row in reversed_if(data, self.flip_y):
out_bytes += row
2021-02-06 23:26:35 +01:00
out_bytes += b"\0\0\0\0\0\0\0\0\0\0" # padding
2021-02-06 23:26:35 +01:00
elif self.mode == "bg":
(out_width, out_height, data, info) = img.read()
2021-02-06 23:26:35 +01:00
img.preamble(True)
palettes = [img.palette(alpha="force")]
for palettepath in glob(self.infile.split(".")[0] + ".*.png"):
pal = png.Reader(palettepath)
pal.preamble(True)
palettes.append(pal.palette(alpha="force"))
baseaddr = 0x80200000 # gBackgroundImage
headers_len = 0x10 * len(palettes)
palettes_len = 0x200 * len(palettes)
# header (struct BackgroundHeader)
for i, palette in enumerate(palettes):
out_bytes += (baseaddr + palettes_len + headers_len).to_bytes(4, byteorder="big") # raster offset
out_bytes += (baseaddr + headers_len + 0x200 * i).to_bytes(4, byteorder="big") # palette offset
out_bytes += (12).to_bytes(2, byteorder="big") # startX
out_bytes += (20).to_bytes(2, byteorder="big") # startY
out_bytes += (out_width).to_bytes(2, byteorder="big") # width
out_bytes += (out_height).to_bytes(2, byteorder="big") # height
for palette in palettes:
# palette
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
# ci 8
for row in reversed_if(data, self.flip_y):
out_bytes += row
2020-10-31 19:30:16 +01:00
else:
print("unsupported mode", file=stderr)
exit(1)
return (out_bytes, out_width, out_height)
2020-10-31 19:30:16 +01:00
if __name__ == "__main__":
if len(argv) < 4:
print("usage: build.py MODE INFILE OUTFILE [--flip-x] [--flip-y]")
2020-10-31 19:30:16 +01:00
exit(1)
mode = argv[1]
infile = argv[2]
outfile = argv[3]
flip_x = "--flip-x" in argv
flip_y = "--flip-y" in argv
(out_bytes, out_width, out_height) = Converter(mode, infile, flip_x, flip_y).convert()
with open(argv[3], "wb") as f:
f.write(out_bytes)