Files
kira/scripts/gen_outfits.py
T
hobokenchicken b7edf6a82d feat: Live2D outfit textures + expression system + canvas tweaks
- Generated 5 outfit texture variants via HSL recolor (saved skin tones)
- Dynamic texture_02 swapping when outfit changes
- Expression buttons (Normal, Smile, Sad, Angry, Surprised, Blushing)
- Random idle expression changes every 8-15s
- Responsive canvas sizing with devicePixelRatio support
- Outfit generation script in scripts/gen_outfits.py
- Smoother lip-sync with phase-based mouth animation
2026-06-04 11:40:10 -04:00

89 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Generate recolored outfit textures for Kira's Live2D model.
Takes the base texture_02.png (clothing layer) and creates
color variants for each outfit using HSL shifts on non-skin areas.
"""
from PIL import Image
import colorsys
import os
BASE_DIR = os.path.expanduser(
"~/Projects/ai-body-double/frontend/public/live2d/models/kira"
)
TEXTURE_DIR = os.path.join(BASE_DIR, "Epsilon.1024")
OUT_DIR = os.path.join(BASE_DIR, "outfits")
# Outfit → HSL shift mapping (hue_shift_deg, sat_mult, light_mult)
# Applied to non-white, non-skin pixels
OUTFITS = {
"cozy-hoodie": (10, 1.1, 1.0), # warm pink shift
"girly-dress": (240, 1.2, 0.95), # lavender/purple
"pajama-set": (140, 0.8, 1.1), # minty green
"study-sweater": (30, 1.1, 1.0), # warm orange/amber
"going-out": (320, 1.3, 1.05), # bright pink/magenta
}
def is_skin(r, g, b):
"""Roughly detect skin tones (avoid recoloring skin)."""
return 180 < r < 250 and 120 < g < 200 and 80 < b < 170
def is_white_or_void(r, g, b, a):
"""Detect transparent or near-white pixels."""
if a < 10:
return True
return r > 240 and g > 240 and b > 240
def recolor_texture(src_path, dst_path, hue_shift, sat_mult, light_mult):
"""Recolor a texture by shifting HSL on non-skin, non-white pixels."""
img = Image.open(src_path).convert("RGBA")
pixels = img.load()
w, h = img.size
for y in range(h):
for x in range(w):
r, g, b, a = pixels[x, y]
if is_white_or_void(r, g, b, a) or is_skin(r, g, b):
continue
# Convert to HSL
h_val, l_val, s_val = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
# Apply shifts
h_val = (h_val + hue_shift / 360) % 1.0
s_val = min(1.0, s_val * sat_mult)
l_val = max(0, min(1.0, l_val * light_mult))
# Convert back to RGB
nr, ng, nb = colorsys.hls_to_rgb(h_val, l_val, s_val)
pixels[x, y] = (int(nr * 255), int(ng * 255), int(nb * 255), a)
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
img.save(dst_path)
size = os.path.getsize(dst_path)
print(f" Created {os.path.basename(dst_path)} ({size//1024}KB)")
def main():
src = os.path.join(TEXTURE_DIR, "texture_02.png")
if not os.path.exists(src):
print(f"Source not found: {src}")
return 1
print(f"Generating outfit textures from {src}")
print()
for outfit_name, (hue, sat, light) in OUTFITS.items():
dst = os.path.join(OUT_DIR, f"{outfit_name}.png")
print(f" {outfit_name}: hue={hue}° sat={sat:.1f}x light={light:.2f}x")
recolor_texture(src, dst, hue, sat, light)
print()
print("Done! Texture variants ready.")
return 0
if __name__ == "__main__":
exit(main())