a
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
"""
|
||||
图片生成技能 - 完整版本(支持中文)
|
||||
整合多种图片生成功能
|
||||
"""
|
||||
|
||||
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'
|
||||
]
|
||||
Reference in New Issue
Block a user