376 lines
13 KiB
Python
376 lines
13 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
|
|
import ascii_magic
|
|
|
|
class AdvancedImageSkill:
|
|
"""高级图片生成技能"""
|
|
|
|
def __init__(self):
|
|
plt.switch_backend('Agg')
|
|
sns.set_style("whitegrid")
|
|
self.default_font = ImageFont.load_default()
|
|
print("Advanced Image Skill initialized")
|
|
|
|
def create_advanced_chart(self, data, chart_type='bar', title='', style='seaborn'):
|
|
"""创建高级图表
|
|
|
|
Args:
|
|
data: 数据字典
|
|
chart_type: 图表类型 ('bar', 'line', 'scatter', 'heatmap', 'pie')
|
|
title: 图表标题
|
|
style: 样式 ('seaborn', 'matplotlib')
|
|
|
|
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 == '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'])
|
|
|
|
elif chart_type == 'heatmap' and 'matrix' in data:
|
|
if style == 'seaborn':
|
|
sns.heatmap(data['matrix'], annot=True, fmt='.1f', cmap='YlOrRd')
|
|
else:
|
|
plt.imshow(data['matrix'], cmap='hot', interpolation='nearest')
|
|
plt.colorbar()
|
|
|
|
elif chart_type == 'pie':
|
|
plt.pie(values, labels=labels, autopct='%1.1f%%', startangle=90)
|
|
plt.axis('equal') # 确保饼图是圆形
|
|
|
|
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_wordcloud(self, text, width=800, height=400,
|
|
background_color='white', colormap='viridis'):
|
|
"""创建词云图
|
|
|
|
Args:
|
|
text: 文本内容
|
|
width: 图片宽度
|
|
height: 图片高度
|
|
background_color: 背景颜色
|
|
colormap: 颜色映射
|
|
|
|
Returns:
|
|
图片路径
|
|
"""
|
|
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_ascii_art(self, image_path=None, text=None, columns=100):
|
|
"""创建ASCII艺术
|
|
|
|
Args:
|
|
image_path: 图片路径(将图片转为ASCII)
|
|
text: 文本(创建文本ASCII艺术)
|
|
columns: 列数
|
|
|
|
Returns:
|
|
ASCII文本或图片路径
|
|
"""
|
|
try:
|
|
if image_path and os.path.exists(image_path):
|
|
# 从图片创建ASCII艺术
|
|
output = ascii_magic.from_image(
|
|
image_path,
|
|
columns=columns,
|
|
mode=ascii_magic.Modes.HTML
|
|
)
|
|
# 保存为HTML
|
|
path = tempfile.mktemp(suffix='.html')
|
|
ascii_magic.to_html_file(path, output)
|
|
return path
|
|
|
|
elif text:
|
|
# 从文本创建ASCII艺术
|
|
# 这里简化处理,实际可以使用更复杂的ASCII艺术生成
|
|
ascii_text = f"""
|
|
╔{'═' * (columns-2)}╗
|
|
║{' ' * (columns-2)}║
|
|
║{text.center(columns-2)}║
|
|
║{' ' * (columns-2)}║
|
|
╚{'═' * (columns-2)}╝
|
|
"""
|
|
path = tempfile.mktemp(suffix='.txt')
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
f.write(ascii_text)
|
|
return path
|
|
|
|
else:
|
|
raise Exception('需要提供图片路径或文本')
|
|
|
|
except Exception as e:
|
|
raise Exception(f'ASCII艺术生成失败: {e}')
|
|
|
|
def create_color_palette(self, colors, title='颜色调色板'):
|
|
"""创建颜色调色板展示
|
|
|
|
Args:
|
|
colors: 颜色列表,如 ['#FF5733', '#33FF57', '#3357FF']
|
|
title: 标题
|
|
|
|
Returns:
|
|
图片路径
|
|
"""
|
|
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'):
|
|
"""创建数据卡片
|
|
|
|
Args:
|
|
title: 卡片标题
|
|
data_dict: 数据字典
|
|
theme: 主题 ('light', 'dark')
|
|
|
|
Returns:
|
|
图片路径
|
|
"""
|
|
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' if theme == 'dark' else 'white')
|
|
|
|
# 数据项
|
|
y = 80
|
|
for key, value in data_dict.items():
|
|
# 键
|
|
draw.text((30, y), f'• {key}:', fill=text_color)
|
|
|
|
# 值(如果是数字,可以添加进度条)
|
|
value_x = 200
|
|
draw.text((value_x, 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)
|
|
|
|
# 百分比文本
|
|
draw.text((bar_x + 5, y), f'{value}%',
|
|
fill='white' if value > 50 else text_color)
|
|
|
|
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_comparison_chart(self, data_sets, labels, chart_type='grouped_bar'):
|
|
"""创建对比图表
|
|
|
|
Args:
|
|
data_sets: 数据集列表,每个数据集是一个数值列表
|
|
labels: 标签列表
|
|
chart_type: 图表类型 ('grouped_bar', 'stacked_bar', 'line')
|
|
|
|
Returns:
|
|
图片路径
|
|
"""
|
|
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}')
|
|
|
|
# 创建技能实例
|
|
advanced_skill = AdvancedImageSkill()
|
|
|
|
# 导出函数
|
|
def create_advanced_chart(*args, **kwargs):
|
|
return advanced_skill.create_advanced_chart(*args, **kwargs)
|
|
|
|
def create_wordcloud(*args, **kwargs):
|
|
return advanced_skill.create_wordcloud(*args, **kwargs)
|
|
|
|
def create_ascii_art(*args, **kwargs):
|
|
return advanced_skill.create_ascii_art(*args, **kwargs)
|
|
|
|
def create_color_palette(*args, **kwargs):
|
|
return advanced_skill.create_color_palette(*args, **kwargs)
|
|
|
|
def create_data_card(*args, **kwargs):
|
|
return advanced_skill.create_data_card(*args, **kwargs)
|
|
|
|
def create_comparison_chart(*args, **kwargs):
|
|
return advanced_skill.create_comparison_chart(*args, **kwargs)
|
|
|
|
__all__ = [
|
|
'AdvancedImageSkill',
|
|
'advanced_skill',
|
|
'create_advanced_chart',
|
|
'create_wordcloud',
|
|
'create_ascii_art',
|
|
'create_color_palette',
|
|
'create_data_card',
|
|
'create_comparison_chart'
|
|
] |