90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Texture contact sheet: all textures of a level WAD (or only those used by the world
|
|
geometry) as a grid labelled with their index -> for picking textures for levelbuild.py.
|
|
|
|
texsheet.py level.wad out.png [--all] [--size 64] [--cols 16] [--only 12,40,77]
|
|
|
|
Without --all: only textures that occur in TRAK (world tiles), sorted by frequency.
|
|
Label: index, below it usage O=top (floor) / S=side (wall) / U=bottom, size in px.
|
|
Flat colours (flag bit 0) are shown as a colour swatch ("F").
|
|
"""
|
|
import argparse
|
|
import collections
|
|
import os
|
|
import sys
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import geomlib as G # noqa: E402
|
|
|
|
|
|
def tex_image(pages_rgba, t, size):
|
|
if t['flags'] & 1 or t['page'] < 0:
|
|
im = Image.new('RGBA', (size, size), (t['u0'], t['u1'], t['v0'], 255))
|
|
return im
|
|
pg = pages_rgba[t['page']]
|
|
crop = pg.crop((t['u0'], t['v0'], t['u1'] + 1, t['v1'] + 1))
|
|
return crop.resize((size, size), Image.NEAREST)
|
|
|
|
|
|
def usage(trak):
|
|
up, side, down = collections.Counter(), collections.Counter(), collections.Counter()
|
|
for m in trak:
|
|
for f in m.faces:
|
|
ny = f['plane'][1]
|
|
(up if ny > 0.7 else down if ny < -0.7 else side)[f['tex']] += 1
|
|
return up, side, down
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument('wad'); ap.add_argument('out')
|
|
ap.add_argument('--all', action='store_true')
|
|
ap.add_argument('--size', type=int, default=64)
|
|
ap.add_argument('--cols', type=int, default=16)
|
|
ap.add_argument('--only', help='comma-separated indices')
|
|
a = ap.parse_args()
|
|
ch = G.wad_read(a.wad)
|
|
pages, texs = G.parse_text(G.wad_get(ch, 'TEXT'))
|
|
prgba = [Image.fromarray(G.page_rgba(p), 'RGBA') for p in pages]
|
|
up, side, down = usage(G.parse_trak(G.wad_get(ch, 'TRAK')))
|
|
tot = up + side + down
|
|
if a.only:
|
|
idx = [int(x) for x in a.only.split(',')]
|
|
elif a.all:
|
|
idx = list(range(len(texs)))
|
|
else:
|
|
idx = [i for i, _ in tot.most_common()]
|
|
S, C = a.size, a.cols
|
|
cw, chh = S + 8, S + 30
|
|
rows = (len(idx) + C - 1) // C
|
|
sheet = Image.new('RGB', (C * cw, rows * chh), (40, 40, 48))
|
|
d = ImageDraw.Draw(sheet)
|
|
for k, i in enumerate(idx):
|
|
t = texs[i]
|
|
x, y = (k % C) * cw + 4, (k // C) * chh + 2
|
|
# checkerboard for transparency
|
|
bg = Image.new('RGB', (S, S), (90, 90, 90))
|
|
bd = ImageDraw.Draw(bg)
|
|
for yy in range(0, S, 8):
|
|
for xx in range(0, S, 8):
|
|
if (xx + yy) // 8 % 2:
|
|
bd.rectangle((xx, yy, xx + 7, yy + 7), fill=(130, 130, 130))
|
|
im = tex_image(prgba, t, S)
|
|
bg.paste(im, (0, 0), im)
|
|
sheet.paste(bg, (x, y))
|
|
w, h = t['u1'] - t['u0'] + 1, t['v1'] - t['v0'] + 1
|
|
tag = 'F' if t['flags'] & 1 else ''
|
|
if up[i] or side[i] or down[i]:
|
|
tag += 'O' if up[i] >= max(side[i], down[i]) else ('S' if side[i] >= down[i] else 'U')
|
|
d.text((x, y + S + 1), f'{i}', fill=(255, 255, 120))
|
|
d.text((x + S // 2 + 4, y + S + 1), tag, fill=(140, 220, 255))
|
|
d.text((x, y + S + 14), f'{w}x{h}', fill=(170, 170, 170))
|
|
sheet.save(a.out)
|
|
print(f'{a.out}: {len(idx)} textures, {sheet.size[0]}x{sheet.size[1]}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|