404 lines
13 KiB
Python
404 lines
13 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
创建Material Design风格的图标
|
||
使用简单的几何图形模拟Material图标
|
||
"""
|
||
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
import os
|
||
|
||
# Material Design 颜色
|
||
COLORS = {
|
||
'primary': '#6200EE',
|
||
'primary_variant': '#3700B3',
|
||
'secondary': '#03DAC6',
|
||
'background': '#FFFFFF',
|
||
'surface': '#FFFFFF',
|
||
'error': '#B00020',
|
||
'on_primary': '#FFFFFF',
|
||
'on_secondary': '#000000',
|
||
'on_background': '#000000',
|
||
'on_surface': '#000000',
|
||
'on_error': '#FFFFFF'
|
||
}
|
||
|
||
# 图标配置
|
||
ICONS = {
|
||
'home': {
|
||
'outline': 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
|
||
'filled': 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'
|
||
},
|
||
'apps': {
|
||
'outline': 'M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z',
|
||
'filled': 'M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z'
|
||
},
|
||
'chat': {
|
||
'outline': 'M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z',
|
||
'filled': 'M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z'
|
||
},
|
||
'layers': {
|
||
'outline': 'M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z',
|
||
'filled': 'M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z'
|
||
},
|
||
'person': {
|
||
'outline': 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z',
|
||
'filled': 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'
|
||
}
|
||
}
|
||
|
||
def create_material_icon(name, style='outline', size=48):
|
||
"""创建Material Design图标"""
|
||
# 创建图像
|
||
img = Image.new('RGBA', (size, size), (255, 255, 255, 0))
|
||
draw = ImageDraw.Draw(img)
|
||
|
||
# 设置颜色
|
||
if style == 'outline':
|
||
color = COLORS['on_surface'] # 灰色
|
||
fill_color = None
|
||
else: # filled
|
||
color = COLORS['primary'] # 紫色
|
||
fill_color = COLORS['primary']
|
||
|
||
# 简化:使用几何图形代替SVG路径
|
||
center = size // 2
|
||
|
||
if name == 'home':
|
||
# 房子图标
|
||
if style == 'outline':
|
||
# 轮廓
|
||
draw.rectangle([center-10, center-6, center+10, center+6], outline=color, width=2)
|
||
draw.polygon([
|
||
(center-12, center-6),
|
||
(center, center-14),
|
||
(center+12, center-6)
|
||
], outline=color, width=2)
|
||
else:
|
||
# 填充
|
||
draw.rectangle([center-10, center-6, center+10, center+6], fill=fill_color)
|
||
draw.polygon([
|
||
(center-12, center-6),
|
||
(center, center-14),
|
||
(center+12, center-6)
|
||
], fill=fill_color)
|
||
|
||
elif name == 'apps':
|
||
# 九宫格图标
|
||
cell_size = size // 4
|
||
for i in range(3):
|
||
for j in range(3):
|
||
x1 = cell_size + i * cell_size - 4
|
||
y1 = cell_size + j * cell_size - 4
|
||
x2 = x1 + cell_size - 4
|
||
y2 = y1 + cell_size - 4
|
||
|
||
if style == 'outline':
|
||
draw.rectangle([x1, y1, x2, y2], outline=color, width=2)
|
||
else:
|
||
draw.rectangle([x1, y1, x2, y2], fill=fill_color)
|
||
|
||
elif name == 'chat':
|
||
# 聊天图标
|
||
if style == 'outline':
|
||
draw.rounded_rectangle([8, 8, size-8, size-8], radius=8, outline=color, width=2)
|
||
# 对话气泡尾巴
|
||
draw.polygon([
|
||
(size-20, size-8),
|
||
(size-12, size-8),
|
||
(size-16, size-2)
|
||
], outline=color, width=2)
|
||
else:
|
||
draw.rounded_rectangle([8, 8, size-8, size-8], radius=8, fill=fill_color)
|
||
# 对话气泡尾巴
|
||
draw.polygon([
|
||
(size-20, size-8),
|
||
(size-12, size-8),
|
||
(size-16, size-2)
|
||
], fill=fill_color)
|
||
|
||
elif name == 'layers':
|
||
# 层叠图标
|
||
if style == 'outline':
|
||
for i in range(3):
|
||
offset = i * 4
|
||
draw.rounded_rectangle(
|
||
[8+offset, 8+offset, size-8-offset, size-8-offset],
|
||
radius=6, outline=color, width=2
|
||
)
|
||
else:
|
||
for i in range(3):
|
||
offset = i * 4
|
||
alpha = 255 - i * 60
|
||
layer_color = fill_color + hex(alpha)[2:].zfill(2)
|
||
draw.rounded_rectangle(
|
||
[8+offset, 8+offset, size-8-offset, size-8-offset],
|
||
radius=6, fill=layer_color
|
||
)
|
||
|
||
elif name == 'person':
|
||
# 人物图标
|
||
if style == 'outline':
|
||
# 头部
|
||
draw.ellipse([center-8, center-12, center+8, center+4], outline=color, width=2)
|
||
# 身体
|
||
draw.rectangle([center-6, center+4, center+6, center+12], outline=color, width=2)
|
||
else:
|
||
# 头部
|
||
draw.ellipse([center-8, center-12, center+8, center+4], fill=fill_color)
|
||
# 身体
|
||
draw.rectangle([center-6, center+4, center+6, center+12], fill=fill_color)
|
||
|
||
return img
|
||
|
||
def main():
|
||
"""主函数"""
|
||
icon_dir = 'static/images/icons'
|
||
os.makedirs(icon_dir, exist_ok=True)
|
||
|
||
print("创建Material Design图标...")
|
||
|
||
for name in ICONS.keys():
|
||
# 创建轮廓图标
|
||
outline_img = create_material_icon(name, 'outline')
|
||
outline_path = os.path.join(icon_dir, f'{name}_outline.png')
|
||
outline_img.save(outline_path)
|
||
print(f"创建: {outline_path}")
|
||
|
||
# 创建填充图标
|
||
filled_img = create_material_icon(name, 'filled')
|
||
filled_path = os.path.join(icon_dir, f'{name}_filled.png')
|
||
filled_img.save(filled_path)
|
||
print(f"创建: {filled_path}")
|
||
|
||
print("✅ 图标创建完成!")
|
||
|
||
# 创建预览
|
||
create_preview(icon_dir)
|
||
|
||
def create_preview(icon_dir):
|
||
"""创建图标预览"""
|
||
html = """
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Material Design图标预览</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Roboto', sans-serif;
|
||
background: #F5F5F5;
|
||
color: #212121;
|
||
padding: 24px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
background: #FFFFFF;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
padding: 32px;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 32px;
|
||
padding-bottom: 24px;
|
||
border-bottom: 1px solid #EEEEEE;
|
||
}
|
||
|
||
.header h1 {
|
||
color: #6200EE;
|
||
font-size: 28px;
|
||
font-weight: 500;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.header p {
|
||
color: #757575;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.icon-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
gap: 24px;
|
||
}
|
||
|
||
.icon-card {
|
||
background: #FAFAFA;
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
text-align: center;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.icon-card:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.icon-preview {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.icon-outline, .icon-filled {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.icon-label {
|
||
font-size: 12px;
|
||
color: #757575;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.icon-name {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #212121;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.icon-desc {
|
||
font-size: 14px;
|
||
color: #616161;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.color-scheme {
|
||
display: flex;
|
||
gap: 16px;
|
||
margin-top: 32px;
|
||
padding-top: 24px;
|
||
border-top: 1px solid #EEEEEE;
|
||
}
|
||
|
||
.color-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.color-box {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.color-text {
|
||
font-size: 14px;
|
||
color: #616161;
|
||
}
|
||
|
||
.footer {
|
||
text-align: center;
|
||
margin-top: 32px;
|
||
padding-top: 24px;
|
||
border-top: 1px solid #EEEEEE;
|
||
color: #9E9E9E;
|
||
font-size: 14px;
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.container {
|
||
padding: 16px;
|
||
}
|
||
|
||
.icon-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>Material Design图标</h1>
|
||
<p>玩Android应用底部导航图标</p>
|
||
</div>
|
||
|
||
<div class="icon-grid">
|
||
"""
|
||
|
||
icon_names = {
|
||
'home': '首页',
|
||
'apps': '项目',
|
||
'chat': '公众号',
|
||
'layers': '体系',
|
||
'person': '我的'
|
||
}
|
||
|
||
icon_descs = {
|
||
'home': '应用首页,展示最新文章和Banner',
|
||
'apps': '开源项目浏览和分类查看',
|
||
'chat': '技术公众号文章订阅',
|
||
'layers': '技术知识体系结构',
|
||
'person': '用户中心和个人设置'
|
||
}
|
||
|
||
for name, chinese in icon_names.items():
|
||
html += f"""
|
||
<div class="icon-card">
|
||
<div class="icon-name">{chinese}</div>
|
||
<div class="icon-preview">
|
||
<div class="icon-outline">
|
||
<img src="{name}_outline.png" alt="{name} outline" width="48" height="48">
|
||
<div class="icon-label">未选中</div>
|
||
</div>
|
||
<div class="icon-filled">
|
||
<img src="{name}_filled.png" alt="{name} filled" width="48" height="48">
|
||
<div class="icon-label">选中</div>
|
||
</div>
|
||
</div>
|
||
<div class="icon-desc">{icon_descs[name]}</div>
|
||
</div>
|
||
"""
|
||
|
||
html += """
|
||
</div>
|
||
|
||
<div class="color-scheme">
|
||
<div class="color-item">
|
||
<div class="color-box" style="background: #6200EE;"></div>
|
||
<div class="color-text">主色 (Primary)</div>
|
||
</div>
|
||
<div class="color-item">
|
||
<div class="color-box" style="background: #3700B3;"></div>
|
||
<div class="color-text">主色变体</div>
|
||
</div>
|
||
<div class="color-item">
|
||
<div class="color-box" style="background: #03DAC6;"></div>
|
||
<div class="color-text">次要色</div>
|
||
</div>
|
||
<div class="color-item">
|
||
<div class="color-box" style="background: #757575;"></div>
|
||
<div class="color-text">未选中状态</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<p>图标尺寸: 48×48像素 | 生成时间: 2026-03-17</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
preview_path = os.path.join(icon_dir, 'preview.html')
|
||
with open(preview_path, 'w', encoding='utf-8') as f:
|
||
f.write(html)
|
||
|
||
print(f"预览文件: {preview_path}")
|
||
|
||
if __name__ == '__main__':
|
||
main() |