codepet 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/scripts/polaroid_image.py +116 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codepet",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "在编程软件里养电子宠物 — 支持 Claude Code / Codex / Cursor / VS Code 等 10+ 平台",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"scripts/img2ascii.py",
|
|
48
48
|
"scripts/img2emoji.py",
|
|
49
49
|
"scripts/polaroid.py",
|
|
50
|
+
"scripts/polaroid_image.py",
|
|
50
51
|
"scripts/pet_card.py",
|
|
51
52
|
"scripts/render_to_image.py",
|
|
52
53
|
"scripts/show_all.py",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""拍立得相框 — 像素风 PNG 图片版"""
|
|
3
|
+
|
|
4
|
+
import sys, os, json, platform
|
|
5
|
+
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
8
|
+
SPRITE_DIR = os.path.join(SCRIPT_DIR, "..", "sprites")
|
|
9
|
+
PET_JSON = os.path.join(os.path.expanduser("~"), ".codepet", "pet.json")
|
|
10
|
+
OUTPUT = os.path.join(os.path.expanduser("~"), ".codepet", "photo.png")
|
|
11
|
+
|
|
12
|
+
SCENE_ZH = {
|
|
13
|
+
'normal': '日常', 'happy': '开心的瞬间', 'sleep': '睡着了',
|
|
14
|
+
'eat': '在吃东西', 'pet': '被摸摸的样子', 'worry': '有点紧张',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def load_font(size):
|
|
18
|
+
system = platform.system()
|
|
19
|
+
candidates = []
|
|
20
|
+
if system == "Darwin":
|
|
21
|
+
candidates = ["/System/Library/Fonts/PingFang.ttc", "/System/Library/Fonts/Hiragino Sans GB.ttc"]
|
|
22
|
+
elif system == "Windows":
|
|
23
|
+
windir = os.environ.get("WINDIR", "C:\\Windows")
|
|
24
|
+
candidates = [os.path.join(windir, "Fonts", "msyh.ttc"), os.path.join(windir, "Fonts", "simhei.ttf"), os.path.join(windir, "Fonts", "arial.ttf")]
|
|
25
|
+
else:
|
|
26
|
+
candidates = ["/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"]
|
|
27
|
+
for fp in candidates:
|
|
28
|
+
if os.path.exists(fp):
|
|
29
|
+
try:
|
|
30
|
+
return ImageFont.truetype(fp, size)
|
|
31
|
+
except:
|
|
32
|
+
continue
|
|
33
|
+
return ImageFont.load_default()
|
|
34
|
+
|
|
35
|
+
def pixelate(img, pixel_size=8):
|
|
36
|
+
"""把图片像素化 — 缩小再放大,产生像素颗粒感"""
|
|
37
|
+
w, h = img.size
|
|
38
|
+
small = img.resize((w // pixel_size, h // pixel_size), Image.NEAREST)
|
|
39
|
+
return small.resize((w, h), Image.NEAREST)
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
character = sys.argv[1] if len(sys.argv) > 1 else 'bagayalu'
|
|
43
|
+
scene = sys.argv[2] if len(sys.argv) > 2 else 'normal'
|
|
44
|
+
|
|
45
|
+
img_path = os.path.join(SPRITE_DIR, character, f"{scene}.png")
|
|
46
|
+
if not os.path.exists(img_path):
|
|
47
|
+
img_path = os.path.join(SPRITE_DIR, f"{character}.png")
|
|
48
|
+
|
|
49
|
+
name = character
|
|
50
|
+
try:
|
|
51
|
+
with open(PET_JSON, 'r', encoding='utf-8') as f:
|
|
52
|
+
pet = json.load(f)
|
|
53
|
+
name = pet.get('nickname', pet.get('name', character))
|
|
54
|
+
except:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
scene_zh = SCENE_ZH.get(scene, '一个瞬间')
|
|
58
|
+
|
|
59
|
+
# 加载并像素化角色图片
|
|
60
|
+
sprite = Image.open(img_path).convert("RGBA")
|
|
61
|
+
|
|
62
|
+
# 统一缩放到 400px 宽
|
|
63
|
+
target_w = 400
|
|
64
|
+
ratio = target_w / sprite.width
|
|
65
|
+
target_h = int(sprite.height * ratio)
|
|
66
|
+
sprite = sprite.resize((target_w, target_h), Image.LANCZOS)
|
|
67
|
+
|
|
68
|
+
# 像素化处理 — 产生复古像素风
|
|
69
|
+
sprite = pixelate(sprite, pixel_size=6)
|
|
70
|
+
|
|
71
|
+
# 拍立得尺寸
|
|
72
|
+
padding = 50
|
|
73
|
+
bottom_area = 140
|
|
74
|
+
photo_w = sprite.width + padding * 2
|
|
75
|
+
photo_h = sprite.height + padding + bottom_area
|
|
76
|
+
|
|
77
|
+
# 奶白色拍立得底板
|
|
78
|
+
card = Image.new("RGBA", (photo_w, photo_h), (252, 250, 245, 255))
|
|
79
|
+
|
|
80
|
+
# 贴像素化的角色图片
|
|
81
|
+
card.paste(sprite, (padding, padding), sprite)
|
|
82
|
+
|
|
83
|
+
draw = ImageDraw.Draw(card)
|
|
84
|
+
|
|
85
|
+
# 图片区域细边框
|
|
86
|
+
draw.rectangle(
|
|
87
|
+
[padding - 2, padding - 2, padding + sprite.width + 1, padding + sprite.height + 1],
|
|
88
|
+
outline=(220, 218, 210), width=1
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# 外边框阴影
|
|
92
|
+
draw.rectangle([0, 0, photo_w - 1, photo_h - 1], outline=(210, 208, 200), width=2)
|
|
93
|
+
|
|
94
|
+
# 底部文字
|
|
95
|
+
font_caption = load_font(20)
|
|
96
|
+
font_studio = load_font(15)
|
|
97
|
+
|
|
98
|
+
caption = f"📸 刚才抓拍到了{name}{scene_zh}..."
|
|
99
|
+
studio = "🐋 Ai小蓝鲸照相馆"
|
|
100
|
+
|
|
101
|
+
caption_y = sprite.height + padding + 20
|
|
102
|
+
studio_y = caption_y + 45
|
|
103
|
+
|
|
104
|
+
draw.text((padding, caption_y), caption, fill=(80, 80, 80), font=font_caption)
|
|
105
|
+
draw.text((padding, studio_y), studio, fill=(160, 158, 150), font=font_studio)
|
|
106
|
+
|
|
107
|
+
# 保存
|
|
108
|
+
os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
|
|
109
|
+
card_rgb = Image.new("RGB", card.size, (252, 250, 245))
|
|
110
|
+
card_rgb.paste(card, mask=card.split()[3])
|
|
111
|
+
card_rgb.save(OUTPUT, "PNG", quality=95)
|
|
112
|
+
|
|
113
|
+
print(OUTPUT)
|
|
114
|
+
|
|
115
|
+
if __name__ == '__main__':
|
|
116
|
+
main()
|