Skip to content

Instantly share code, notes, and snippets.

@Tiryoh
Last active April 13, 2025 15:15
Show Gist options
  • Save Tiryoh/8267f745a2f7cdef6cc7283bfe2547e1 to your computer and use it in GitHub Desktop.
Save Tiryoh/8267f745a2f7cdef6cc7283bfe2547e1 to your computer and use it in GitHub Desktop.
mini-pupper-chan avatar
#!/usr/bin/env python3
"""
stack-chanのアバターを生成するスクリプト(Apache-2.0ライセンス, Copyright 2021 Shinya Ishikawa)を参考に作った
Mini Pupper用のアバターを生成するスクリプトです
瞬きの機能はありません
https://github.com/stack-chan/stack-chan/blob/dev/v1.0/firmware/stackchan/renderers/simple-face.ts
# (C) 2025 Daisuke Sato
# Released under the MIT License
# https://tiryoh.mit-license.org/2025-
"""
from MangDang.mini_pupper.display import Display
import time
import random
from PIL import Image, ImageDraw
def createEyelidPart(draw, cx, cy, width, height, side, eye_open, emotion):
"""
指定された感情(emotion)に基づいて眼瞼部分(アイリッド)の描画を行う関数
引数:
draw : PillowのImageDraw.Drawオブジェクト
cx, cy : 描画エリアの中心座標
width, height: 眼瞼部品の幅と高さ
side : 'left' または 'right'
eye_open : 目の開き具合 (0=完全に開, 1=完全に閉)
emotion : 'ANGRY', 'SAD', 'SLEEPY', 'HAPPY' などの文字列
"""
# 描画領域の基準計算
w = width
h = height * (1 - eye_open)
x = cx - width / 2
y = cy - height / 2
if emotion in ("ANGRY", "SAD"):
# 感情が ANGRY または SAD の場合、上下の頂点位置を計算
h1 = y + (height + h) / 2
h2 = y + h
# 左側の場合は h1 と h2 を入れ替える
if side == "left":
h1, h2 = h2, h1
# SAD の場合、再度交換
if emotion == "SAD":
h1, h2 = h2, h1
# ポリゴンとして塗りつぶしで描画
draw.polygon([(x, y), (x, h1), (x + w, h2), (x + w, y)], fill="black")
elif emotion == "SLEEPY":
# SLEEPY の場合は、矩形で上半分を塗りつぶしで描画
draw.rectangle([x, y, x + w, y + height * 0.5 + h * 0.5], fill="black")
elif emotion == "HAPPY":
# HAPPYの場合は目を丸く表示するため、何も描画しない
return
else:
# その他の場合は、単純に矩形を塗りつぶしで描画
draw.rectangle([x, y, x + w, y + h], fill="black")
def createEyePart(draw, cx, cy, radius, gaze=(0,0), fill="white"):
"""
指定された中心(cx,cy)・半径 radius で目(楕円)を描画する関数
gaze: (gazeX, gazeY) のタプル(任意、オフセットとして適用)
"""
offsetX = gaze[0] * 2 if gaze else 0
offsetY = gaze[1] * 2 if gaze else 0
x0 = cx + offsetX - radius
y0 = cy + offsetY - radius
x1 = cx + offsetX + radius
y1 = cy + offsetY + radius
draw.ellipse((x0, y0, x1, y1), fill=fill)
def createMouthPart(draw, cx, cy, minWidth=32, maxWidth=32, minHeight=8, maxHeight=8, open_ratio=0, fill="white"):
"""
TS の仕様に近い口を描画
open_ratio: 0(完全に閉)〜1(完全に開)
"""
h = minHeight + (maxHeight - minHeight) * open_ratio
w = minWidth + (maxWidth - minWidth) * (1 - open_ratio)
x = cx - w/2
y = cy - h/2
draw.rectangle((x, y, x+w, y+h), fill=fill)
def generate_avatar(eyelid_emotion):
# 画像設定・黒背景
width, height = 320, 240
img = Image.new('RGB', (width, height), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
# TS座標変換
base_scale = 0.8
enlarge_factor = 1.2
effective_scale = base_scale * enlarge_factor # 0.96
offset_x = 160 - int(round(160 * effective_scale))
offset_y = 120 - int(round(112 * effective_scale))
# 左目: TS座標 (90,93) -> effective_scaleとオフセットを適用
left_eye_cx = int(90 * effective_scale + offset_x)
left_eye_cy = int(93 * effective_scale + offset_y)
left_eye_radius = int(8 * effective_scale)
# 右目: TS座標 (230,96) -> effective_scaleとオフセットを適用
right_eye_cx = int(230 * effective_scale + offset_x)
right_eye_cy = int(96 * effective_scale + offset_y)
right_eye_radius = int(8 * effective_scale)
# 目は createEyePart を使用して描画
createEyePart(draw, left_eye_cx, left_eye_cy, left_eye_radius, fill="white")
createEyePart(draw, right_eye_cx, right_eye_cy, right_eye_radius, fill="white")
# 口: TS座標 (160,148) -> effective_scaleとオフセットを適用
# 口のサイズ: TS版のパラメータを effective_scale で拡大
mouth_cx = int(160 * effective_scale + offset_x)
mouth_cy = int(148 * effective_scale + offset_y)
createMouthPart(draw, mouth_cx, mouth_cy,
minWidth=int(50 * effective_scale), maxWidth=int(90 * effective_scale),
minHeight=int(8 * effective_scale), maxHeight=int(58 * effective_scale),
open_ratio=0, fill="white")
# eyelid 描画のためのパラメータ設定
eye_open = 0.5 # 目開閉
eyelid_width = int(24 * effective_scale)
eyelid_height = int(24 * effective_scale)
# 左側の eyelid は createEyelidPart で描画
createEyelidPart(draw, left_eye_cx, left_eye_cy, eyelid_width, eyelid_height,
"left", eye_open, eyelid_emotion)
# 右側の eyelid は createEyelidPart で描画
createEyelidPart(draw, right_eye_cx, right_eye_cy, eyelid_width, eyelid_height,
"right", eye_open, eyelid_emotion)
return img
def main():
disp = Display()
interval = 60
while True:
eyelid_emotion = random.choice(["ANGRY", "SAD", "SLEEPY", "HAPPY"]) # 感情をランダムに選択
face = generate_avatar(eyelid_emotion)
disp.disp.display(face.resize((320,240)))
time.sleep(interval)
if __name__ == '__main__':
main()
@Tiryoh
Copy link
Author

Tiryoh commented Feb 4, 2025

@Tiryoh
Copy link
Author

Tiryoh commented Apr 13, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment