我将为您创建一个完整的民间艺术表情包生成器程序。这个项目结合了传统文化与现代社交媒体需求,让用户能够轻松制作具有中国特色的表情包。
项目结构
folk_emoji_generator/ ├── main.py # 主程序入口 ├── config.py # 配置文件 ├── asset_manager.py # 素材管理模块 ├── emoji_generator.py # 表情包生成模块 ├── text_overlay.py # 文字叠加模块 ├── social_sharer.py # 社交分享模块 ├── ui_components.py # UI组件模块 ├── folk_art_data.json # 民间艺术数据 ├── README.md # 使用说明 └── assets/ # 资源文件夹 ├── paper_cutting/ # 剪纸素材 ├── shadow_puppet/ # 皮影素材 ├── chinese_ink/ # 水墨素材 └── fonts/ # 字体文件
- 配置文件 (config.py)
""" 配置文件 - 定义应用程序的基本配置和常量 """
import os
应用基本配置
APP_NAME = "民间艺术表情包生成器" VERSION = "1.0.0" WINDOW_SIZE = (1000, 700)
文件路径配置
BASE_DIR = os.path.dirname(os.path.abspath(file)) ASSETS_DIR = os.path.join(BASE_DIR, "assets") FONT_DIR = os.path.join(ASSETS_DIR, "fonts") OUTPUT_DIR = os.path.join(BASE_DIR, "output") DATA_FILE = os.path.join(BASE_DIR, "folk_art_data.json")
素材分类目录
ART_CATEGORIES = { 'paper_cutting': '剪纸艺术', 'shadow_puppet': '皮影艺术', 'chinese_ink': '水墨艺术', 'embroidery': '刺绣艺术', 'clay_sculpture': '泥塑艺术' }
支持的图片格式
SUPPORTED_IMAGE_FORMATS = ['.png', '.jpg', '.jpeg', '.bmp', '.tiff'] SUPPORTED_OUTPUT_FORMATS = ['.png', '.jpg', '.gif']
字体配置
DEFAULT_FONT_SIZE = 24 MAX_FONT_SIZE = 72 MIN_FONT_SIZE = 12
表情包尺寸配置
EMOJI_SIZES = { 'small': (200, 200), 'medium': (400, 400), 'large': (600, 600), 'social': (500, 400) # 适合社交平台的尺寸 }
UI颜色主题
COLORS = { 'primary': '#FF6B6B', 'secondary': '#4ECDC4', 'accent': '#45B7D1', 'background': '#F8F9FA', 'text': '#2C3E50', 'success': '#27AE60', 'warning': '#F39C12' }
常用表情包文字模板
TEXT_TEMPLATES = [ "哈哈哈 😂", "太可爱了 🥰", "我太难了 😭", "开心到飞起 🎉", "无语 😑", "点赞 👍", "比心 ❤️", "加油 💪", "晚安 🌙", "早安 ☀️" ]
社交平台配置
SOCIAL_PLATFORMS = { 'wechat': '微信', 'qq': 'QQ', 'weibo': '微博', 'douyin': '抖音', 'bilibili': '哔哩哔哩' }
- 素材管理模块 (asset_manager.py)
""" 素材管理模块 - 负责加载和管理民间艺术素材 """
import os import json from PIL import Image, ImageTk import random from config import *
class AssetManager: def init(self): self.art_assets = {} self.fonts = [] self.load_assets() self.load_fonts()
def load_assets(self):
"""加载民间艺术素材"""
try:
# 首先尝试从JSON文件加载素材信息
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r', encoding='utf-8') as f:
art_data = json.load(f)
self.process_art_data(art_data)
else:
print("素材数据文件不存在,扫描目录加载素材...")
self.scan_asset_directories()
except Exception as e:
print(f"加载素材时出错: {e}")
self.scan_asset_directories()
def scan_asset_directories(self):
"""扫描素材目录加载图片文件"""
for category, name in ART_CATEGORIES.items():
category_dir = os.path.join(ASSETS_DIR, category)
if os.path.exists(category_dir):
self.art_assets[category] = []
for filename in os.listdir(category_dir):
if any(filename.lower().endswith(fmt) for fmt in SUPPORTED_IMAGE_FORMATS):
file_path = os.path.join(category_dir, filename)
self.art_assets[category].append({
'name': filename,
'path': file_path,
'category': category,
'display_name': name
})
print(f"加载了 {sum(len(assets) for assets in self.art_assets.values())} 个素材文件")
def process_art_data(self, art_data):
"""处理从JSON加载的艺术数据"""
for category, items in art_data.items():
if category not in self.art_assets:
self.art_assets[category] = []
for item in items:
# 验证文件是否存在
if os.path.exists(item['path']):
self.art_assets[category].append(item)
else:
print(f"素材文件不存在: {item['path']}")
def load_fonts(self):
"""加载可用的字体文件"""
if os.path.exists(FONT_DIR):
for filename in os.listdir(FONT_DIR):
if filename.lower().endswith(('.ttf', '.otf')):
self.fonts.append(os.path.join(FONT_DIR, filename))
# 如果没有找到字体文件,使用系统默认字体
if not self.fonts:
print("未找到字体文件,将使用系统默认字体")
self.fonts = ['default']
def get_random_asset(self, category=None):
"""获取随机素材"""
if category and category in self.art_assets:
assets = self.art_assets[category]
else:
# 从所有类别中随机选择一个
all_assets = []
for assets in self.art_assets.values():
all_assets.extend(assets)
assets = all_assets if all_assets else []
return random.choice(assets) if assets else None
def get_assets_by_category(self, category):
"""根据类别获取素材列表"""
return self.art_assets.get(category, [])
def get_all_categories(self):
"""获取所有素材类别"""
return list(ART_CATEGORIES.keys())
def get_category_display_name(self, category):
"""获取类别显示名称"""
return ART_CATEGORIES.get(category, category)
def load_image_for_display(self, image_path, size=None):
"""加载图片用于显示(调整大小)"""
try:
if not os.path.exists(image_path):
return None
image = Image.open(image_path)
if size:
image.thumbnail(size, Image.Resampling.LANCZOS)
return image
except Exception as e:
print(f"加载图片失败: {e}")
return None
def create_sample_data(self):
"""创建示例素材数据文件"""
sample_data = {
"paper_cutting": [
{
"name": "剪纸_福字.png",
"path": "assets/paper_cutting/剪纸_福字.png",
"category": "paper_cutting",
"display_name": "剪纸艺术",
"description": "传统红色剪纸福字,寓意吉祥如意"
},
{
"name": "剪纸_生肖鼠.png",
"path": "assets/paper_cutting/剪纸_生肖鼠.png",
"category": "paper_cutting",
"display_name": "剪纸艺术",
"description": "十二生肖剪纸老鼠图案,造型生动"
}
],
"shadow_puppet": [
{
"name": "皮影_武将.png",
"path": "assets/shadow_puppet/皮影_武将.png",
"category": "shadow_puppet",
"display_name": "皮影艺术",
"description": "传统皮影戏武将形象,色彩鲜艳"
}
]
}
# 保存到JSON文件
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(sample_data, f, ensure_ascii=False, indent=2)
return sample_data
3. 表情包生成模块 (emoji_generator.py)
""" 表情包生成模块 - 负责创建和处理表情包图片 """
from PIL import Image, ImageDraw, ImageFont, ImageFilter import os from config import * import textwrap
class EmojiGenerator: def init(self, asset_manager): self.asset_manager = asset_manager self.current_image = None self.current_size = EMOJI_SIZES['medium']
def create_base_emoji(self, asset_info, size=None):
"""创建基础表情包"""
try:
if size is None:
size = self.current_size
# 加载原始图片
original_image = Image.open(asset_info['path'])
# 创建新图片(白色背景)
emoji_image = Image.new('RGB', size, 'white')
# 计算缩放比例以适应目标尺寸
original_width, original_height = original_image.size
target_width, target_height = size
# 保持宽高比的缩放
scale_ratio = min(target_width/original_width, target_height/original_height) * 0.8
new_width = int(original_width * scale_ratio)
new_height = int(original_height * scale_ratio)
# 缩放图片
resized_image = original_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
# 居中粘贴
x = (target_width - new_width) // 2
y = (target_height - new_height) // 2
emoji_image.paste(resized_image, (x, y))
self.current_image = emoji_image
return emoji_image
except Exception as e:
print(f"创建表情包失败: {e}")
return None
def add_decorative_border(self, border_style='simple'):
"""添加装饰边框"""
if not self.current_image:
return None
img = self.current_image.copy()
width, height = img.size
if border_style == 'simple':
# 简单黑色边框
draw = ImageDraw.Draw(img)
draw.rectangle([0, 0, width-1, height-1], outline='black', width=2)
elif border_style == 'fancy':
# 花式边框
draw = ImageDraw.Draw(img)
# 外边框
draw.rectangle([0, 0, width-1, height-1], outline='#FF6B6B', width=3)
# 内边框
draw.rectangle([5, 5, width-6, height-6], outline='#4ECDC4', width=1)
elif border_style == 'traditional':
# 传统中国风边框
# 这里可以添加中国传统纹样
pass
self.current_image = img
return img
def apply_filters(self, filter_type='none'):
"""应用图片滤镜效果"""
if not self.current_image:
return None
img = self.current_image.copy()
if filter_type == 'vintage':
# 复古效果
img = Image.blend(img.convert('L'), img, 0.3)
elif filter_type == 'bright':
# 增亮效果
from PIL import ImageEnhance
enhancer = ImageEnhance.Brightness(img)
img = enhancer.enhance(1.2)
elif filter_type == 'contrast':
# 增强对比度
from PIL import ImageEnhance
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(1.3)
elif filter_type == 'blur':
# 模糊效果
img = img.filter(ImageFilter.GaussianBlur(radius=1))
self.current_image = img
return img
def resize_for_social(self, platform='wechat'):
"""调整尺寸适应社交平台"""
if not self.current_image:
return None
# 根据不同平台调整尺寸
if platform in SOCIAL_PLATFORMS:
if platform == 'weibo':
size = (900, 500) # 微博配图比例
elif platform == 'douyin':
size = (540, 960) # 抖音竖屏比例
else:
size = EMOJI_SIZES['social']
else:
size = EMOJI_SIZES['social']
resized_img = self.current_image.resize(size, Image.Resampling.LANCZOS)
self.current_image = resized_img
return resized_img
def save_emoji(self, filename=None, format='PNG'):
"""保存表情包"""
if not self.current_image:
return None
# 确保输出目录存在
os.makedirs(OUTPUT_DIR, exist_ok=True)
if filename is None:
import time
timestamp = int(time.time())
filename = f"emoji_{timestamp}.{format.lower()}"
filepath = os.path.join(OUTPUT_DIR, filename)
# 根据格式保存
if format.upper() == 'PNG':
self.current_image.save(filepath, 'PNG', optimize=True)
elif format.upper() == 'JPEG':
# JPEG不支持透明,转换为RGB
if self.current_image.mode in ('RGBA', 'LA'):
background = Image.new('RGB', self.current_image.size, (255, 255, 255))
background.paste(self.current_image, mask=self.current_image.split()[-1] if self.current_image.mode == 'RGBA' else None)
background.save(filepath, 'JPEG', quality=95)
else:
self.current_image.save(filepath, 'JPEG', quality=95)
print(f"表情包已保存到: {filepath}")
return filepath
def get_current_image(self):
"""获取当前处理的图片"""
return self.current_image
def set_current_image(self, image):
"""设置当前处理的图片"""
self.current_image = image
4. 文字叠加模块 (text_overlay.py)
""" 文字叠加模块 - 负责在图片上添加文字 """
from PIL import Image, ImageDraw, ImageFont import textwrap from config import *
class TextOverlay: def init(self): self.current_font = None self.font_size = DEFAULT_FONT_SIZE self.text_color = 'black' self.stroke_color = 'white' self.stroke_width = 2
def set_font(self, font_path=None, font_size=None):
"""设置字体"""
if font_size:
self.font_size = max(MIN_FONT_SIZE, min(MAX_FONT_SIZE, font_size))
try:
if font_path and font_path != 'default':
self.current_font = ImageFont.truetype(font_path, self.font_size)
else:
# 使用默认字体
self.current_font = ImageFont.load_default()
except Exception as e:
print(f"字体加载失败,使用默认字体: {e}")
self.current_font = ImageFont.load_default()
def add_text_to_image(self, image, text, position='bottom', align='center'):
"""在图片上添加文字"""
if not image:
return image
img = image.copy()
draw = ImageDraw.Draw(img)
# 自动换行处理
wrapped_text = self.wrap_text(text, img.width - 40) # 留边距
# 计算文字位置
text_bbox = self.calculate_text_bbox(wrapped_text, draw)
text_x, text_y = self.calculate_position(position, img.size, text_bbox)
# 绘制文字描边(提高可读性)
if self.stroke_width > 0:
self.draw_text_with_stroke(draw, wrapped_text, text_x, text_y,
self.stroke_color, self.stroke_width)
# 绘制主要文字
self.draw_text(draw, wrapped_text, text_x, text_y, self.text_color)
return img
def wrap_text(self, text, max_width):
"""文字自动换行"""
if not self.current_font:
self.set_font()
# 估算字符宽度
char_width = self.font_size * 0.6 # 粗略估计
chars_per_line = int(max_width / char_width)
# 使用textwrap进行换行
lines = textwrap.wrap(text, width=chars_per_line)
return '\n'.join(lines)
def calculate_text_bbox(self, text, draw):
"""计算文字边界框"""
if hasattr(draw, 'textbbox'):
# PIL 8.0+ 版本
bbox = draw.textbbox((0, 0), text, font=self.current_font)
return (bbox[2] - bbox[0], bbox[3] - bbox[1])
else:
# 旧版本PIL
return (len(text) * self.font_size * 0.6, self.font_size)
def calculate_position(self, position, image_size, text_size):
"""计算文字位置"""
img_width, img_height = image_size
text_width, text_height = text_size
margin = 20
if position == 'top':
x = (img_width - text_width) // 2
y = margin
elif position == 'bottom':
x = (img_width - text_width) // 2
y = img_height - text_height - margin
elif position == 'center':
x = (img_width - text_width) // 2
y = (img_height - text_height) // 2
elif position == 'left':
x = margin
y = (img_height - text_height) // 2
elif position == 'right':
x = img_width - text_width - margin
y = (img_height - text_height) // 2
else:
x = (img_width - text_width) // 2
y = img_height - text_height - margin
return x, y
def draw_text_with_stroke(self, draw, text, x, y, stroke_color, stroke_width):
"""绘制带描边的文字"""
# 绘制描边
for dx in range(-stroke_width, stroke_width + 1):
for dy in range(-stroke_width, stroke_width + 1):
if dx != 0 or dy != 0:
draw.text((x + dx, y + dy), text, font=self.current_font, fill=stroke_color)
def draw_text(self, draw, text, x, y, color):
"""绘制文字"""
draw.text((x, y), text, font=self.current_font, fill=color)
def set_text_style(self, color=None, stroke_color=None, stroke_width=None):
"""设置文字样式"""
if color:
self.text_color = color
if stroke_color:
self.stroke_color = stroke_color
if stroke_width is not None:
self.stroke_width = stroke_width
def get_available_templates(self):
"""获取预设的文字模板"""
return TEXT_TEMPLATES
def suggest_text_for_art(self, art_info):
"""根据艺术作品建议合适的文字"""
art_name = art_info.get('name', '')
description = art_info.get('description', '')
suggestions = []
# 根据关键词匹配建议
keywords_map = {
'福': ['福气满满', '好运连连', '五福临门'],
'生肖': ['萌萌哒', '太可爱了', '新年快乐'],
'武将': ['霸气侧漏', '威武霸气', '帅炸了'],
'花': ['花开富贵', '美美哒', '春暖花开']
}
for keyword, texts in keywords_map.items():
if keyword in art_name or keyword in description:
suggestions.extend(texts)
return suggestions[:5] if suggestions else TEXT_TEMPLATES[:3]
5. 社交分享模块 (social_sharer.py)
""" 社交分享模块 - 负责模拟分享到社交平台的功能 """
import os import json from datetime import datetime from config import *
class SocialSharer: def init(self): self.share_history = [] self.load_share_history()
def share_to_platform(self, image_path, platform, caption=""):
"""分享到指定社交平台"""
if not os.path.exists(image_path):
return {'success': False, 'message': '图片文件不存在'}
# 模拟分享过程
share_id = self.generate_share_id()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
share_record = {
'id': share_id,
'platform': platform,
'platform_name': SOCIAL_PLATFORMS.get(platform, platform),
'image_path': image_path,
'caption': caption,
'timestamp': timestamp,
'status': 'shared'
}
# 添加到分享历史
self.share_history.append(share_record)
self.save_share_history()
# 模拟不同平台的分享效果
platform_messages = {
'wechat': f"✅ 已分享到微信!表情包很可爱呢~",
'qq': f"✅ 已发送到QQ空间,好友们都说不错!",
'weibo': f"✅ 微博发布成功!转发量很高哦~",
'douyin': f"✅ 抖音上传完成!期待更多点赞~",
'bilibili': f"✅ 动态发布成功!UP主推荐了你的作品!"
}
message = platform_messages.get(platform, f"✅ 已分享到{platform}!")
print(f"分享成功: {message}")
return {
'success': True,
'message': message,
'share_id': share_id
}
def generate_share_id(self):
"""生成分享ID"""
import time
import random
return f"share_{int(time.time())}_{random.randint(1000, 9999)}"
def get_share_history(self):
"""获取分享历史"""
return self.share_history
def get_platforms(self):
"""获取支持的社交平台"""
return SOCIAL_PLATFORMS
def delete_share_record(self, share_id):
"""删除分享记录"""
self.share_history = [record for record in self.share_history if record['id'] != share_id]
self.save_share_history()
return True
def save_share_history(self):
"""保存分享历史到文件"""
try:
history_file = os.path.join(OUTPUT_DIR, 'share_history.json')
with open(history_file, 'w', encoding='utf-8') as f:
json.
关注我,有更多实用程序等着你!