Spaces:
Sleeping
Sleeping
File size: 6,103 Bytes
85c18a5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
"""
Avatar Manager Module
=====================
Handles avatar discovery, creation, and management.
Functions:
- ensure_sample_avatar: Create default sample avatar
- list_avatars: Get list of available avatars
- get_avatar_preview: Get preview image of an avatar
"""
from PIL import Image, ImageDraw
from pathlib import Path
from typing import List, Optional
import numpy as np
def ensure_sample_avatar(avatars_dir: Path) -> None:
"""
Create a sample avatar if none exists.
Generates a simple animated avatar with:
- Base face image
- Three mouth positions (closed, medium, open)
Args:
avatars_dir: Base directory for avatars
Example:
>>> ensure_sample_avatar(Path("./avatars"))
# Creates ./avatars/sample/ with base.png and mouth_*.png
Note:
This creates a basic placeholder avatar. For better results,
create custom avatars with proper artwork.
"""
sample_dir = avatars_dir / "sample"
# Check if sample already exists with content
if sample_dir.exists() and any(sample_dir.iterdir()):
return
# Create directory
sample_dir.mkdir(parents=True, exist_ok=True)
# Image dimensions
width, height = 512, 512
# Create base image (simple face background)
base = Image.new("RGBA", (width, height), (255, 220, 200, 255))
draw_base = ImageDraw.Draw(base)
# Draw simple face features on base
# Face circle
draw_base.ellipse([56, 56, 456, 456], fill=(255, 230, 210, 255), outline=(200, 150, 130, 255), width=3)
# Eyes
draw_base.ellipse([150, 180, 200, 230], fill=(255, 255, 255, 255), outline=(0, 0, 0, 255), width=2)
draw_base.ellipse([312, 180, 362, 230], fill=(255, 255, 255, 255), outline=(0, 0, 0, 255), width=2)
# Pupils
draw_base.ellipse([165, 195, 185, 215], fill=(50, 50, 50, 255))
draw_base.ellipse([327, 195, 347, 215], fill=(50, 50, 50, 255))
# Eyebrows
draw_base.arc([140, 150, 210, 190], start=200, end=340, fill=(100, 70, 50, 255), width=3)
draw_base.arc([302, 150, 372, 190], start=200, end=340, fill=(100, 70, 50, 255), width=3)
# Nose
draw_base.polygon([(256, 250), (240, 310), (272, 310)], fill=(240, 200, 180, 255))
# Hair (simple)
draw_base.arc([40, 20, 472, 300], start=180, end=360, fill=(80, 50, 30, 255), width=30)
base.save(sample_dir / "base.png")
# Create mouth frames (transparent overlays)
mouth_positions = [
# (y_offset, height) - Mouth closed to open
(0, 8), # mouth_0: Nearly closed
(0, 20), # mouth_1: Slightly open
(0, 35), # mouth_2: Wide open
]
for i, (y_off, mouth_height) in enumerate(mouth_positions):
# Create transparent image for mouth overlay
mouth_img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
draw_mouth = ImageDraw.Draw(mouth_img)
# Calculate mouth position
mouth_y = 340 + y_off
mouth_left = 200
mouth_right = 312
# Draw mouth (ellipse shape)
draw_mouth.ellipse(
[mouth_left, mouth_y, mouth_right, mouth_y + mouth_height],
fill=(180, 80, 80, 255),
outline=(120, 50, 50, 255),
width=2
)
# Add inner mouth detail for open mouths
if mouth_height > 15:
inner_offset = 5
draw_mouth.ellipse(
[mouth_left + inner_offset, mouth_y + inner_offset,
mouth_right - inner_offset, mouth_y + mouth_height - inner_offset],
fill=(100, 40, 40, 255)
)
mouth_img.save(sample_dir / f"mouth_{i}.png")
def list_avatars(avatars_dir: Path) -> List[str]:
"""
Get list of available avatar names.
Scans the avatars directory for valid avatar folders
(containing base.png and mouth_*.png files).
Args:
avatars_dir: Base directory containing avatar folders
Returns:
List of avatar folder names
Example:
>>> avatars = list_avatars(Path("./avatars"))
>>> print(avatars)
['sample', 'anime_girl', 'anime_boy']
"""
# Ensure sample avatar exists
ensure_sample_avatar(avatars_dir)
# Find all valid avatar directories
avatars = []
if avatars_dir.exists():
for path in avatars_dir.iterdir():
if path.is_dir():
# Check for required files
has_base = (path / "base.png").exists()
has_mouth = any(path.glob("mouth_*.png"))
if has_base and has_mouth:
avatars.append(path.name)
return sorted(avatars)
def get_avatar_preview(avatar_name: str, avatars_dir: Path) -> Optional[Image.Image]:
"""
Get a preview image of an avatar.
Composites the base image with the first mouth frame
to show what the avatar looks like.
Args:
avatar_name: Name of the avatar folder
avatars_dir: Base directory containing avatar folders
Returns:
PIL Image object or None if avatar not found
Example:
>>> preview = get_avatar_preview("sample", Path("./avatars"))
>>> preview.show()
"""
avatar_folder = avatars_dir / avatar_name
base_path = avatar_folder / "base.png"
if not base_path.exists():
return None
# Load base image
base = Image.open(base_path).convert("RGBA")
# Find first mouth frame
mouth_frames = sorted(avatar_folder.glob("mouth_*.png"))
if mouth_frames:
mouth = Image.open(mouth_frames[0]).convert("RGBA").resize(base.size)
# Composite mouth onto base
preview = Image.alpha_composite(base, mouth)
else:
preview = base
return preview |