Skip to content

Commit f93f02a

Browse files
author
Ubuntu
committed
feat(web): #72 multi-state animation spritesheets
- Rewrite generate_jimeng_spritesheet.py to generate 3 distinct poses - Each pose (idle/work/sleep) is resized to 64x64 and tiled 4x per row - All 5 agents regenerated with new multi-state spritesheets - Part 1 of #72 complete
1 parent 07eab6f commit f93f02a

6 files changed

Lines changed: 29 additions & 31 deletions

File tree

-27.1 KB
Loading
-22 KB
Loading
-36.4 KB
Loading
-51.5 KB
Loading
-36.8 KB
Loading

packages/web/scripts/generate_jimeng_spritesheet.py

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
Usage:
66
python3 generate_jimeng_spritesheet.py <agent_key> "<prompt>" [output_path]
77
8-
Examples:
9-
python3 generate_jimeng_spritesheet.py xiaoai "A cute pixel art chibi AI agent..."
8+
The script generates 3 images (one per animation state), resizes each to 64x64,
9+
and tiles them into a 256x192 spritesheet (4 cols x 3 rows).
10+
Row 0 = idle, Row 1 = work, Row 2 = sleep/error
1011
"""
1112
import subprocess
1213
import urllib.request
@@ -16,10 +17,8 @@
1617
from PIL import Image
1718

1819
OUT_DIR = "/home/ubuntu/claw-visual/packages/web/public/sprites"
19-
MCPORTER_BIN = "/home/ubuntu/.local/share/pnpm/mcporter"
2020
NODE_PATH = "/home/ubuntu/.local/share/pnpm/global/5/.pnpm/mcporter@0.7.3_hono@4.11.7/node_modules"
2121
BUN_BIN = "/home/ubuntu/.bun/bin/bun"
22-
NVM_SHELL = "/home/ubuntu/.nvm/nvm.sh"
2322

2423
TARGET_W, TARGET_H = 256, 192
2524
FRAME_W, FRAME_H = 64, 64
@@ -53,7 +52,6 @@ def call_jimeng(prompt: str, aspect_ratio: str = "3:2") -> list[str]:
5352
print(f"STDERR: {result.stderr}", file=sys.stderr)
5453
raise RuntimeError(f"mcporter call failed with code {result.returncode}")
5554

56-
# Parse URLs from text output
5755
urls = []
5856
for line in result.stdout.split("\n"):
5957
line = line.strip()
@@ -78,26 +76,22 @@ def download_image(url: str) -> Image.Image:
7876
def make_spritesheet(images: list[Image.Image], output_path: str):
7977
"""
8078
Combine up to 3 images into a 256x192 spritesheet (4 cols x 3 rows).
81-
82-
Row 0 (idle): image[0] — extract 4 horizontal 64x64 tiles
83-
Row 1 (work): image[1] — same
84-
Row 2 (sleep): image[2] — same
85-
86-
If fewer images available, pad with last image.
79+
Each source image = one animation state (idle/work/sleep), resized to 64x64.
80+
Row 0 (idle): frames[0] tiled 4x
81+
Row 1 (work): frames[1] tiled 4x
82+
Row 2 (sleep): frames[2] tiled 4x
8783
"""
8884
result = Image.new("RGBA", (TARGET_W, TARGET_H), (0, 0, 0, 0))
8985

90-
# Resize each source to 256x192
91-
resized = [img.resize((TARGET_W, TARGET_H), Image.LANCZOS) for img in images]
92-
# Pad to 3
93-
while len(resized) < 3:
94-
resized.append(resized[-1])
86+
# Resize each source to 64x64 (single frame)
87+
frames = [img.resize((FRAME_W, FRAME_H), Image.LANCZOS) for img in images]
88+
while len(frames) < 3:
89+
frames.append(frames[-1] if frames else Image.new("RGBA", (FRAME_W, FRAME_H), (0, 0, 0, 0)))
9590

9691
for row_idx in range(ROWS):
97-
src = resized[row_idx]
92+
frame = frames[row_idx]
9893
for col in range(COLS):
99-
tile = src.crop((col * FRAME_W, 0, (col + 1) * FRAME_W, FRAME_H))
100-
result.paste(tile, (col * FRAME_W, row_idx * FRAME_H))
94+
result.paste(frame, (col * FRAME_W, row_idx * FRAME_H))
10195

10296
result.save(output_path)
10397
print(f"Saved: {output_path} ({result.size})")
@@ -107,9 +101,8 @@ def main():
107101
if len(sys.argv) < 3:
108102
agent_key = "xiaoai"
109103
prompt = (
110-
"A cute pixel art chibi AI agent avatar, 256x192 pixels total, "
111-
"64x64 per frame, 4 frames in a row, clean pixel art style, "
112-
"bright cheerful expression, simple clean background"
104+
"pixel art chibi AI agent character, solid sky blue background #87CEEB, "
105+
"limited color palette NES style, crisp pixel edges"
113106
)
114107
else:
115108
agent_key = sys.argv[1]
@@ -120,17 +113,22 @@ def main():
120113
print(f"=== Generating spritesheet for: {agent_key} ===")
121114
print(f"Output: {output_path}")
122115

123-
urls = call_jimeng(prompt)
124-
if not urls:
125-
print("No URLs returned, exiting.")
126-
sys.exit(1)
116+
# Generate 3 images for 3 states
117+
state_prompts = [
118+
f"{prompt} - idle pose, standing forward, friendly neutral expression",
119+
f"{prompt} - working pose, hands on keyboard, focused expression",
120+
f"{prompt} - resting or sleeping pose, eyes closed, calm relaxed expression",
121+
]
127122

128123
images = []
129-
for i, url in enumerate(urls[:4]):
130-
try:
131-
images.append(download_image(url))
132-
except Exception as e:
133-
print(f" Download failed for image {i}: {e}", file=sys.stderr)
124+
for i, sp in enumerate(state_prompts):
125+
print(f" State {i+1}/3: {sp[:70]}...")
126+
urls = call_jimeng(sp)
127+
if urls:
128+
try:
129+
images.append(download_image(urls[0]))
130+
except Exception as e:
131+
print(f" Download failed: {e}", file=sys.stderr)
134132

135133
if not images:
136134
print("No images downloaded.", file=sys.stderr)

0 commit comments

Comments
 (0)