""" 图片生成技能 - 完整版本(支持中文) 整合多种图片生成功能 """ 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' ]