Files
animation/tools/tailor/textures/make_bottom_test1.py
T

79 lines
3.5 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
make_bottom_test1.py — flax-strand skirt texture for bottomTest1.
python tools/tailor/textures/make_bottom_test1.py
WHY THIS EXISTS
The first bottom_test1.png (kept as bottom_test1_prestrand.png) was authored for a
SOLID skirt panel and has two properties that fight the strand geometry:
1. It paints its own VERTICAL STRAND LINES. The garment is now 32 discrete
geometric strands, so those painted lines double up — strands-within-strands.
2. Its band region VARIES ALONG U (measured horizontal stdev 68-83 per row, vs
0-13 on the clean rows). Every geometric strand samples a different slice of
that region, so the horizontal bands do not line up strand-to-strand and the
skirt reads as broken noise instead of woven rows.
THE RULE, and it is the whole trick: for a strand garment the texture must be a
function of V ONLY. Constant along U means every strand samples the same colour at
the same height, so bands align across all 32 strands automatically — regardless of
each strand's UV position, its width, or how the drape stretched it. No UV surgery
needed, and it stays true if the strand count changes.
METHOD
Collapse the reference to V-only by taking each row's MEDIAN colour. That keeps the
original vertical rhythm (waistband, band groups, plain flax field) — the design
intent — and throws away only the horizontal variation that was causing the noise.
Then add back a very low-contrast vertical striation for fibre feel, small enough
(+/- STRIATION levels) that it cannot resurrect the misalignment.
Re-sampling happens through the EXISTING UVs, so this needs no MD session: rebuild
the PNG, copy it next to the exported glTF, reimport the texture.
"""
import os
import statistics
from PIL import Image
HERE = os.path.dirname(os.path.abspath(__file__))
REF = os.path.join(HERE, "bottom_test1_prestrand.png") # the pre-strand original
OUT = os.path.join(HERE, "bottom_test1.png")
SIZE = 1024
STRIATION = 6 # +/- levels of vertical fibre variation. Keep SMALL.
STRIATION_PERIOD = 7 # px between fibre lines
DPI = 43.3 # matches the original: 1024 px at 43.3 dpi = 600 mm of cloth
def main():
ref = Image.open(REF).convert("RGB").resize((SIZE, SIZE), Image.LANCZOS)
px = ref.load()
out = Image.new("RGB", (SIZE, SIZE))
op = out.load()
row_var_before, row_var_after = [], []
for y in range(SIZE):
row = [px[x, y] for x in range(SIZE)]
med = tuple(int(statistics.median(c[i] for c in row)) for i in range(3))
row_var_before.append(statistics.pstdev([sum(c) / 3 for c in row]))
for x in range(SIZE):
# deterministic, seed-free striation: a fixed comb, not noise, so the
# result is byte-identical run to run (Date/random are avoided on
# purpose — this file is a build input).
d = STRIATION if (x % STRIATION_PERIOD) < STRIATION_PERIOD // 2 else -STRIATION
op[x, y] = tuple(max(0, min(255, med[i] + d)) for i in range(3))
row_var_after.append(
statistics.pstdev([sum(op[x, y]) / 3 for x in range(0, SIZE, 4)]))
out.save(OUT, dpi=(DPI, DPI))
print("wrote %s (%dx%d, dpi %.1f)" % (OUT, SIZE, SIZE, DPI))
print("mean horizontal stdev per row: %.1f -> %.1f (lower = bands align)"
% (sum(row_var_before) / SIZE, sum(row_var_after) / SIZE))
print("worst row stdev: %.1f -> %.1f"
% (max(row_var_before), max(row_var_after)))
if __name__ == "__main__":
main()