Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

398 lines
14 KiB
Python

"""
图片生成技能 - 完整版本(支持中文)
整合多种图片生成功能
"""
import os
import tempfile
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import matplotlib.pyplot as plt
import seaborn as sns
import qrcode
from wordcloud import WordCloud
# 导入字体工具
try:
# 尝试不同的导入方式
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from font_utils import get_chinese_font, setup_matplotlib_chinese
CHINESE_SUPPORT = True
except ImportError as e:
CHINESE_SUPPORT = False
print(f"⚠️ 字体工具导入失败,中文支持可能受限: {e}")
# 设置matplotlib为非交互模式
plt.switch_backend('Agg')
sns.set_style("whitegrid")
# 尝试设置matplotlib中文支持
if CHINESE_SUPPORT:
setup_matplotlib_chinese()
class ImageGenerationSkill:
"""图片生成技能主类"""
def __init__(self, config=None):
self.config = config or {}
# 尝试使用中文字体
if CHINESE_SUPPORT:
self.default_font = get_chinese_font(14)
else:
self.default_font = ImageFont.load_default()
print("⚠️ 使用默认字体,中文可能显示为方框")
print("Image Generation Skill v2.0 (中文支持) 初始化完成")
# ========== 基础功能 ==========
def create_chart(self, data, chart_type='bar', title='', style='default'):
"""创建图表
Args:
data: 数据字典 {'labels': [], 'values': []}
chart_type: 图表类型 ('bar', 'line', 'pie', 'scatter')
title: 图表标题
style: 样式 ('default', 'seaborn')
Returns:
图片路径
"""
try:
plt.figure(figsize=(10, 6))
if style == 'seaborn':
sns.set_palette("husl")
labels = data.get('labels', [])
values = data.get('values', [])
if chart_type == 'bar':
if style == 'seaborn':
ax = sns.barplot(x=labels, y=values)
# 添加数值标签
for i, v in enumerate(values):
ax.text(i, v + 0.1, str(v), ha='center')
else:
bars = plt.bar(labels, values)
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
f'{height}', ha='center', va='bottom')
elif chart_type == 'line':
if style == 'seaborn':
sns.lineplot(x=labels, y=values, marker='o', linewidth=2)
else:
plt.plot(labels, values, marker='o', linewidth=2)
elif chart_type == 'pie':
plt.pie(values, labels=labels, autopct='%1.1f%%', startangle=90)
plt.axis('equal')
elif chart_type == 'scatter' and 'x' in data and 'y' in data:
if style == 'seaborn':
sns.scatterplot(x=data['x'], y=data['y'])
else:
plt.scatter(data['x'], data['y'])
if title:
plt.title(title, fontsize=14, fontweight='bold')
plt.tight_layout()
path = tempfile.mktemp(suffix='.png')
plt.savefig(path, dpi=120, bbox_inches='tight')
plt.close()
return path
except Exception as e:
plt.close()
raise Exception(f'图表生成失败: {e}')
def create_qrcode(self, content, size=200, output_path=None):
"""生成二维码"""
try:
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(content)
qr.make(fit=True)
qr_img = qr.make_image(fill_color='black', back_color='white')
qr_img = qr_img.resize((size, size))
if output_path is None:
output_path = tempfile.mktemp(suffix='.png')
qr_img.save(output_path)
return output_path
except Exception as e:
raise Exception(f'二维码生成失败: {e}')
def text_to_image(self, text, width=400, height=200,
bg_color='white', text_color='black'):
"""文本转图片"""
try:
img = Image.new('RGB', (width, height), color=bg_color)
draw = ImageDraw.Draw(img)
lines = text.split('\n')
line_height = 30
y_start = max(20, (height - len(lines) * line_height) // 2)
for i, line in enumerate(lines):
y = y_start + i * line_height
# 简单居中
text_width = len(line) * 10
x = max(20, (width - text_width) // 2)
draw.text((x, y), line, fill=text_color, font=self.default_font)
path = tempfile.mktemp(suffix='.png')
img.save(path)
return path
except Exception as e:
raise Exception(f'文本转图片失败: {e}')
# ========== 高级功能 ==========
def create_wordcloud(self, text, width=800, height=400,
background_color='white', colormap='viridis'):
"""创建词云图"""
try:
wordcloud = WordCloud(
width=width,
height=height,
background_color=background_color,
colormap=colormap,
max_words=200,
contour_width=1,
contour_color='steelblue'
).generate(text)
path = tempfile.mktemp(suffix='.png')
wordcloud.to_file(path)
return path
except Exception as e:
raise Exception(f'词云图生成失败: {e}')
def create_color_palette(self, colors, title='颜色调色板'):
"""创建颜色调色板"""
try:
fig, axes = plt.subplots(1, len(colors), figsize=(len(colors)*2, 3))
if len(colors) == 1:
axes = [axes]
for ax, color in zip(axes, colors):
ax.add_patch(plt.Rectangle((0, 0), 1, 1, color=color))
ax.text(0.5, -0.2, color, ha='center', transform=ax.transAxes)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
plt.suptitle(title, fontsize=14, fontweight='bold')
plt.tight_layout()
path = tempfile.mktemp(suffix='.png')
plt.savefig(path, dpi=100, bbox_inches='tight')
plt.close()
return path
except Exception as e:
plt.close()
raise Exception(f'颜色调色板生成失败: {e}')
def create_data_card(self, title, data_dict, theme='light'):
"""创建数据卡片"""
try:
# 设置颜色主题
if theme == 'dark':
bg_color = '#2c3e50'
text_color = '#ecf0f1'
accent_color = '#3498db'
else:
bg_color = '#ffffff'
text_color = '#2c3e50'
accent_color = '#3498db'
# 计算卡片尺寸
num_items = len(data_dict)
card_width = 500
card_height = 150 + num_items * 40
# 创建卡片
img = Image.new('RGB', (card_width, card_height), color=bg_color)
draw = ImageDraw.Draw(img)
# 标题区域
draw.rectangle([0, 0, card_width, 60], fill=accent_color)
draw.text((20, 20), title, fill='white')
# 数据项
y = 80
for key, value in data_dict.items():
draw.text((30, y), f'• {key}:', fill=text_color)
draw.text((200, y), str(value), fill=accent_color)
# 如果是百分比,添加进度条
if isinstance(value, (int, float)) and 0 <= value <= 100:
bar_width = 200
bar_height = 20
bar_x = 250
# 进度条背景
draw.rectangle([bar_x, y, bar_x + bar_width, y + bar_height],
fill='#ecf0f1' if theme == 'light' else '#34495e')
# 进度条前景
fill_width = int(bar_width * value / 100)
draw.rectangle([bar_x, y, bar_x + fill_width, y + bar_height],
fill=accent_color)
# 百分比文本
text_color_bar = 'white' if value > 50 else text_color
draw.text((bar_x + 5, y), f'{value}%', fill=text_color_bar)
y += 40
# 边框
draw.rectangle([10, 70, card_width - 10, card_height - 10],
outline=accent_color, width=2)
path = tempfile.mktemp(suffix='.png')
img.save(path)
return path
except Exception as e:
raise Exception(f'数据卡片生成失败: {e}')
def create_system_status(self, status_data):
"""生成系统状态图"""
try:
width, height = 600, 400
img = Image.new('RGB', (width, height), color='#f8f9fa')
draw = ImageDraw.Draw(img)
# 标题
draw.rectangle([0, 0, width, 60], fill='#2c3e50')
draw.text((20, 20), '🖥️ 系统状态监控', fill='white', font=self.default_font)
items = list(status_data.items())
y_start = 80
for i, (label, value) in enumerate(items):
y = y_start + i * 50
draw.text((40, y), f'• {label}', fill='#34495e', font=self.default_font)
draw.text((200, y), str(value), fill='#3498db', font=self.default_font)
import datetime
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
draw.text((40, height-30), f'生成时间: {timestamp}',
fill='#7f8c8d', font=self.default_font)
path = tempfile.mktemp(suffix='.png')
img.save(path)
return path
except Exception as e:
raise Exception(f'系统状态图生成失败: {e}')
def create_comparison_chart(self, data_sets, labels, chart_type='grouped_bar'):
"""创建对比图表"""
try:
plt.figure(figsize=(12, 7))
x = np.arange(len(labels))
width = 0.8 / len(data_sets)
if chart_type == 'grouped_bar':
for i, data in enumerate(data_sets):
offset = (i - len(data_sets)/2 + 0.5) * width
bars = plt.bar(x + offset, data, width, label=f'数据集 {i+1}')
# 添加数值标签
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
f'{height}', ha='center', va='bottom', fontsize=8)
elif chart_type == 'stacked_bar':
bottom = np.zeros(len(labels))
for i, data in enumerate(data_sets):
plt.bar(x, data, width, bottom=bottom, label=f'数据集 {i+1}')
bottom += data
elif chart_type == 'line':
for i, data in enumerate(data_sets):
plt.plot(x, data, marker='o', linewidth=2, label=f'数据集 {i+1}')
plt.xlabel('类别', fontsize=12)
plt.ylabel('数值', fontsize=12)
plt.title('数据对比分析', fontsize=14, fontweight='bold')
plt.xticks(x, labels)
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
path = tempfile.mktemp(suffix='.png')
plt.savefig(path, dpi=120, bbox_inches='tight')
plt.close()
return path
except Exception as e:
plt.close()
raise Exception(f'对比图表生成失败: {e}')
# 创建技能实例
skill = ImageGenerationSkill()
# 快捷函数
def create_chart(*args, **kwargs):
return skill.create_chart(*args, **kwargs)
def create_qrcode(*args, **kwargs):
return skill.create_qrcode(*args, **kwargs)
def text_to_image(*args, **kwargs):
return skill.text_to_image(*args, **kwargs)
def create_wordcloud(*args, **kwargs):
return skill.create_wordcloud(*args, **kwargs)
def create_color_palette(*args, **kwargs):
return skill.create_color_palette(*args, **kwargs)
def create_data_card(*args, **kwargs):
return skill.create_data_card(*args, **kwargs)
def create_system_status(*args, **kwargs):
return skill.create_system_status(*args, **kwargs)
def create_comparison_chart(*args, **kwargs):
return skill.create_comparison_chart(*args, **kwargs)
__all__ = [
'ImageGenerationSkill',
'skill',
'create_chart',
'create_qrcode',
'text_to_image',
'create_wordcloud',
'create_color_palette',
'create_data_card',
'create_system_status',
'create_comparison_chart'
]