1425 lines
126 KiB
JSON
Raw Normal View History

2025-07-31 15:35:23 +08:00
{
"file_path": "poster/utils.py",
"file_size": 26726,
"line_count": 793,
"functions": [
{
"name": "__init__",
"line_start": 24,
"line_end": 25,
"args": [
{
"name": "self"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self):\n self.logger = logging.getLogger(__name__)",
"code_hash": "44df414b12e5ee556f443463aaf624c6"
},
{
"name": "resize_image",
"line_start": 27,
"line_end": 72,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "target_size",
"type_hint": "Tuple[int, int]"
},
{
"name": "keep_aspect",
"type_hint": "bool"
},
{
"name": "crop",
"type_hint": "bool"
}
],
"return_type": "Image.Image",
"docstring": "调整图像大小\n\nArgs:\n image: 原始图像\n target_size: 目标尺寸 (宽, 高)\n keep_aspect: 是否保持宽高比\n crop: 是否裁剪以适应目标尺寸\n \nReturns:\n 调整后的图像",
"is_async": false,
"decorators": [],
"code": " def resize_image(self, image: Image.Image, target_size: Tuple[int, int], \n keep_aspect: bool = True, crop: bool = False) -> Image.Image:\n \"\"\"\n 调整图像大小\n \n Args:\n image: 原始图像\n target_size: 目标尺寸 (宽, 高)\n keep_aspect: 是否保持宽高比\n crop: 是否裁剪以适应目标尺寸\n \n Returns:\n 调整后的图像\n \"\"\"\n if not keep_aspect:\n return image.resize(target_size, Image.LANCZOS)\n \n # 保持宽高比\n target_width, target_height = target_size\n width, height = image.size\n \n # 计算缩放比例\n ratio_w = target_width / width\n ratio_h = target_height / height\n \n if crop:\n # 裁剪模式:先缩放到较大的尺寸,然后裁剪中心部分\n ratio = max(ratio_w, ratio_h)\n new_width = int(width * ratio)\n new_height = int(height * ratio)\n resized = image.resize((new_width, new_height), Image.LANCZOS)\n \n # 计算裁剪区域\n left = (new_width - target_width) // 2\n top = (new_height - target_height) // 2\n right = left + target_width\n bottom = top + target_height\n \n return resized.crop((left, top, right, bottom))\n else:\n # 非裁剪模式:缩放到适应目标尺寸的最大尺寸\n ratio = min(ratio_w, ratio_h)\n new_width = int(width * ratio)\n new_height = int(height * ratio)\n \n return image.resize((new_width, new_height), Image.LANCZOS)",
"code_hash": "b33aae9e6611675806cf4fb375b64a1c"
},
{
"name": "apply_overlay",
"line_start": 74,
"line_end": 92,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "opacity",
"type_hint": "float"
}
],
"return_type": "Image.Image",
"docstring": "在图像上应用半透明覆盖层\n\nArgs:\n image: 原始图像\n color: 覆盖层颜色 (R, G, B)\n opacity: 不透明度 (0.0-1.0)\n \nReturns:\n 应用覆盖层后的图像",
"is_async": false,
"decorators": [],
"code": " def apply_overlay(self, image: Image.Image, color: Tuple[int, int, int], \n opacity: float = 0.5) -> Image.Image:\n \"\"\"\n 在图像上应用半透明覆盖层\n \n Args:\n image: 原始图像\n color: 覆盖层颜色 (R, G, B)\n opacity: 不透明度 (0.0-1.0)\n \n Returns:\n 应用覆盖层后的图像\n \"\"\"\n overlay = Image.new('RGBA', image.size, color + (int(255 * opacity),))\n \n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n return Image.alpha_composite(image, overlay)",
"code_hash": "bb0725c0526df33a4598341b75c6d36c"
},
{
"name": "apply_blur",
"line_start": 94,
"line_end": 105,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "radius",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "应用模糊效果\n\nArgs:\n image: 原始图像\n radius: 模糊半径\n \nReturns:\n 模糊后的图像",
"is_async": false,
"decorators": [],
"code": " def apply_blur(self, image: Image.Image, radius: int = 5) -> Image.Image:\n \"\"\"\n 应用模糊效果\n \n Args:\n image: 原始图像\n radius: 模糊半径\n \n Returns:\n 模糊后的图像\n \"\"\"\n return image.filter(ImageFilter.GaussianBlur(radius))",
"code_hash": "d475d68cee157578b64e7cc19f0ab35b"
},
{
"name": "create_rounded_corners",
"line_start": 107,
"line_end": 134,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "radius",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "创建圆角图像\n\nArgs:\n image: 原始图像\n radius: 圆角半径\n \nReturns:\n 圆角处理后的图像",
"is_async": false,
"decorators": [],
"code": " def create_rounded_corners(self, image: Image.Image, radius: int = 20) -> Image.Image:\n \"\"\"\n 创建圆角图像\n \n Args:\n image: 原始图像\n radius: 圆角半径\n \n Returns:\n 圆角处理后的图像\n \"\"\"\n # 创建一个透明的画布\n rounded = Image.new('RGBA', image.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(rounded)\n \n # 绘制圆角矩形\n width, height = image.size\n draw.rounded_rectangle([(0, 0), (width, height)], radius, fill=(255, 255, 255, 255))\n \n # 确保图像是RGBA模式\n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n # 使用圆角矩形作为蒙版\n result = Image.new('RGBA', image.size, (0, 0, 0, 0))\n result.paste(image, (0, 0), mask=rounded)\n \n return result",
"code_hash": "c96ddfd5e52835dbb2f71c912be68e43"
},
{
"name": "add_shadow",
"line_start": 136,
"line_end": 176,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "offset",
"type_hint": "Tuple[int, int]"
},
{
"name": "radius",
"type_hint": "int"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "opacity",
"type_hint": "float"
}
],
"return_type": "Image.Image",
"docstring": "为图像添加阴影效果\n\nArgs:\n image: 原始图像\n offset: 阴影偏移量 (x, y)\n radius: 阴影模糊半径\n color: 阴影颜色\n opacity: 阴影不透明度\n \nReturns:\n 添加阴影后的图像",
"is_async": false,
"decorators": [],
"code": " def add_shadow(self, image: Image.Image, offset: Tuple[int, int] = (5, 5), \n radius: int = 10, color: Tuple[int, int, int] = (0, 0, 0), \n opacity: float = 0.7) -> Image.Image:\n \"\"\"\n 为图像添加阴影效果\n \n Args:\n image: 原始图像\n offset: 阴影偏移量 (x, y)\n radius: 阴影模糊半径\n color: 阴影颜色\n opacity: 阴影不透明度\n \n Returns:\n 添加阴影后的图像\n \"\"\"\n # 确保图像是RGBA模式\n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n # 创建阴影蒙版\n shadow = Image.new('RGBA', image.size, (0, 0, 0, 0))\n shadow_draw = ImageDraw.Draw(shadow)\n shadow_draw.rectangle([(0, 0), image.size], fill=color + (int(255 * opacity),))\n \n # 应用模糊\n shadow = shadow.filter(ImageFilter.GaussianBlur(radius))\n \n # 创建结果图像\n result = Image.new('RGBA', (\n image.width + abs(offset[0]),\n image.height + abs(offset[1])\n ), (0, 0, 0, 0))\n \n # 放置阴影和原图\n x_offset = max(0, offset[0])\n y_offset = max(0, offset[1])\n result.paste(shadow, (x_offset, y_offset))\n result.paste(image, (max(0, -offset[0]), max(0, -offset[1])), mask=image)\n \n return result",
"code_hash": "ce595e27d5692055226a71112a469f4c"
},
{
"name": "__init__",
"line_start": 182,
"line_end": 185,
"args": [
{
"name": "self"
},
{
"name": "font_dir",
"type_hint": "str"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self, font_dir: str = \"assets/font\"):\n self.font_dir = font_dir\n self.logger = logging.getLogger(__name__)\n self.default_font = self._load_default_font()",
"code_hash": "53bbe02f8a779d48745cc173932affed"
},
{
"name": "_load_default_font",
"line_start": 187,
"line_end": 214,
"args": [
{
"name": "self"
},
{
"name": "size",
"type_hint": "int"
}
],
"return_type": "Optional[ImageFont.FreeTypeFont]",
"docstring": "加载默认字体",
"is_async": false,
"decorators": [],
"code": " def _load_default_font(self, size: int = 24) -> Optional[ImageFont.FreeTypeFont]:\n \"\"\"加载默认字体\"\"\"\n try:\n # 尝试加载系统字体\n system_fonts = [\n \"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\", # Linux\n \"/System/Library/Fonts/STHeiti Light.ttc\", # macOS\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\" # Windows\n ]\n \n # 首先尝试加载指定目录中的字体\n if os.path.exists(self.font_dir):\n font_files = [f for f in os.listdir(self.font_dir) \n if f.endswith(('.ttf', '.otf', '.ttc'))]\n if font_files:\n return ImageFont.truetype(os.path.join(self.font_dir, font_files[0]), size)\n \n # 然后尝试系统字体\n for font_path in system_fonts:\n if os.path.exists(font_path):\n return ImageFont.truetype(font_path, size)\n \n # 最后使用默认字体\n return ImageFont.load_default()\n \n except Exception as e:\n self.logger.warning(f\"加载默认字体失败: {e}\")\n return ImageFont.load_default()",
"code_hash": "1ff8f93f3c2f8e1e623db10208583a7c"
},
{
"name": "draw_text_with_outline",
"line_start": 216,
"line_end": 245,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_width",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "绘制带描边的文字\n\nArgs:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n outline_color: 描边颜色\n outline_width: 描边宽度",
"is_async": false,
"decorators": [],
"code": " def draw_text_with_outline(self, draw: ImageDraw.Draw, \n position: Tuple[int, int],\n text: str, \n font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n outline_color: Tuple[int, int, int, int] = (0, 0, 0, 255),\n outline_width: int = 2):\n \"\"\"\n 绘制带描边的文字\n \n Args:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n outline_color: 描边颜色\n outline_width: 描边宽度\n \"\"\"\n x, y = position\n \n # 绘制描边\n for offset_x in range(-outline_width, outline_width + 1):\n for offset_y in range(-outline_width, outline_width + 1):\n if offset_x == 0 and offset_y == 0:\n continue\n draw.text((x + offset_x, y + offset_y), text, font=font, fill=outline_color)\n \n # 绘制文字\n draw.text(position, text, font=font, fill=text_color)",
"code_hash": "516626aff1f3485af817e553081a7b50"
},
{
"name": "draw_rounded_rectangle",
"line_start": 246,
"line_end": 321,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "size",
"type_hint": "Tuple[int, int]"
},
{
"name": "radius",
"type_hint": "int"
},
{
"name": "fill_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_color",
"type_hint": "Optional[Tuple[int, int, int, int]]"
},
{
"name": "outline_width",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "绘制圆角矩形\n\nArgs:\n draw: PIL绘图对象\n position: 左上角位置\n size: 矩形大小\n radius: 圆角半径\n fill_color: 填充颜色\n outline_color: 边框颜色\n outline_width: 边框宽度",
"is_async": false,
"decorators": [],
"code": " def draw_rounded_rectangle(self, draw: ImageDraw.Draw,\n position: Tuple[int, int],\n size: Tuple[int, int],\n radius: int,\n fill_color: Tuple[int, int, int, int],\n outline_color: Optional[Tuple[int, int, int, int]] = None,\n outline_width: int = 0):\n \"\"\"\n 绘制圆角矩形\n \n Args:\n draw: PIL绘图对象\n position: 左上角位置\n size: 矩形大小\n radius: 圆角半径\n fill_color: 填充颜色\n outline_color: 边框颜色\n outline_width: 边框宽度\n \"\"\"\n x, y = position\n width, height = size\n \n # 确保尺寸有效\n if width <= 0 or height <= 0:\n return\n \n # 限制圆角半径\n radius = min(radius, width // 2, height // 2)\n \n # 创建圆角矩形路径\n # 这是一个简化版本PIL的较新版本有更好的圆角矩形支持\n if radius > 0:\n # 绘制中心矩形\n draw.rectangle([x + radius, y, x + width - radius, y + height], fill=fill_color)\n draw.rectangle([x, y + radius, x + width, y + height - radius], fill=fill_color)\n \n # 绘制四个圆角\n draw.pieslice([x, y, x + 2*radius, y + 2*radius], 180, 270, fill=fill_color)\n draw.pieslice([x + width - 2*radius, y, x + width, y + 2*radius], 270, 360, fill=fill_color)\n draw.pieslice([x, y + height - 2*radius, x + 2*radius, y + height], 90, 180, fill=fill_color)\n draw.pieslice([x + width - 2*radius, y + height - 2*radius, x + width, y + height], 0, 90, fill=fill_color)\n else:\n # 普通矩形\n draw.rectangle([x, y, x + width, y + height], fill=fill_color)\n \n # 绘制边框(如果需要)\n if outline_color and outline_width > 0:\n # 简化的边框绘制 - 使用线条而不是矩形避免坐标错误\n for i in range(outline_width):\n offset = i\n # 确保坐标有效\n if radius > 0:\n # 上边\n if x + radius + offset < x + width - radius - offset:\n draw.line([x + radius + offset, y + offset, \n x + width - radius - offset, y + offset], \n fill=outline_color, width=1)\n # 下边\n if x + radius + offset < x + width - radius - offset and y + height - offset >= y + offset:\n draw.line([x + radius + offset, y + height - offset, \n x + width - radius - offset, y + height - offset], \n fill=outline_color, width=1)\n # 左边\n if y + radius + offset < y + height - radius - offset:\n draw.line([x + offset, y + radius + offset, \n x + offset, y + height - radius - offset], \n fill=outline_color, width=1)\n # 右边\n if y + radius + offset < y + height - radius - offset:\n draw.line([x + width - offset, y + radius + offset, \n x + width - offset, y + height - radius - offset], \n fill=outline_color, width=1)\n else:\n # 普通矩形边框\n draw.rectangle([x + offset, y + offset, x + width - offset, y + height - offset], \n outline=outline_color, width=1)",
"code_hash": "d4cc4ef2bb7fb65ab2d335da8fd8a6c2"
},
{
"name": "draw_text_with_shadow",
"line_start": 323,
"line_end": 349,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_offset",
"type_hint": "Tuple[int, int]"
}
],
"return_type": null,
"docstring": "绘制带阴影的文字\n\nArgs:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n shadow_color: 阴影颜色\n shadow_offset: 阴影偏移",
"is_async": false,
"decorators": [],
"code": " def draw_text_with_shadow(self, draw: ImageDraw.Draw,\n position: Tuple[int, int],\n text: str,\n font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n shadow_color: Tuple[int, int, int, int] = (0, 0, 0, 128),\n shadow_offset: Tuple[int, int] = (2, 2)):\n \"\"\"\n 绘制带阴影的文字\n \n Args:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n shadow_color: 阴影颜色\n shadow_offset: 阴影偏移\n \"\"\"\n x, y = position\n shadow_x, shadow_y = shadow_offset\n \n # 绘制阴影\n draw.text((x + shadow_x, y + shadow_y), text, font=font, fill=shadow_color)\n \n # 绘制文字\n draw.text(position, text, font=font, fill=text_color)",
"code_hash": "27b04c83991936b0619e153f816763ef"
},
{
"name": "get_font",
"line_start": 351,
"line_end": 380,
"args": [
{
"name": "self"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "size",
"type_hint": "int"
}
],
"return_type": "ImageFont.FreeTypeFont",
"docstring": "获取指定字体\n\nArgs:\n font_name: 字体文件名如果为None则使用默认字体\n size: 字体大小\n \nReturns:\n 字体对象",
"is_async": false,
"decorators": [],
"code": " def get_font(self, font_name: Optional[str] = None, size: int = 24) -> ImageFont.FreeTypeFont:\n \"\"\"\n 获取指定字体\n \n Args:\n font_name: 字体文件名如果为None则使用默认字体\n size: 字体大小\n \n Returns:\n 字体对象\n \"\"\"\n if not font_name:\n return self.default_font.font_variant(size=size)\n \n try:\n # 尝试从字体目录加载\n font_path = os.path.join(self.font_dir, font_name)\n if os.path.exists(font_path):\n return ImageFont.truetype(font_path, size)\n \n # 尝试作为绝对路径加载\n if os.path.exists(font_name):\n return ImageFont.truetype(font_name, size)\n \n self.logger.warning(f\"找不到字体: {font_name},使用默认字体\")\n return self.default_font.font_variant(size=size)\n \n except Exception as e:\n self.logger.warning(f\"加载字体 {font_name} 失败: {e}\")\n return self.default_font.font_variant(size=size)",
"code_hash": "c6a099a04c79f2fdc1668e9103756e50"
},
{
"name": "draw_text",
"line_start": 382,
"line_end": 454,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "font_size",
"type_hint": "int"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "align",
"type_hint": "str"
},
{
"name": "max_width",
"type_hint": "Optional[int]"
}
],
"return_type": "Image.Image",
"docstring": "在图像上绘制文本\n\nArgs:\n image: 目标图像\n text: 要绘制的文本\n position: 文本位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n max_width: 最大宽度,超过会自动换行\n \nReturns:\n 绘制文本后的图像",
"is_async": false,
"decorators": [],
"code": " def draw_text(self, image: Image.Image, text: str, position: Tuple[int, int], \n font_name: Optional[str] = None, font_size: int = 24, \n color: Tuple[int, int, int] = (0, 0, 0), \n align: str = \"left\", max_width: Optional[int] = None) -> Image.Image:\n \"\"\"\n 在图像上绘制文本\n \n Args:\n image: 目标图像\n text: 要绘制的文本\n position: 文本位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n max_width: 最大宽度,超过会自动换行\n \n Returns:\n 绘制文本后的图像\n \"\"\"\n draw = ImageDraw.Draw(image)\n font = self.get_font(font_name, font_size)\n \n if not max_width:\n # 简单绘制文本\n x, y = position\n \n if align == \"center\":\n text_width, _ = draw.textsize(text, font=font)\n x -= text_width // 2\n elif align == \"right\":\n text_width, _ = draw.textsize(text, font=font)\n x -= text_width\n \n draw.text((x, y), text, font=font, fill=color)\n return image\n \n # 处理自动换行\n lines = []\n current_line = \"\"\n \n for word in text.split():\n test_line = current_line + word + \" \"\n text_width, _ = draw.textsize(test_line, font=font)\n \n if text_width <= max_width:\n current_line = test_line\n else:\n lines.append(current_line)\n current_line = word + \" \"\n \n if current_line:\n lines.append(current_line)\n \n # 绘制每一行\n x, y = position\n line_height = font_size * 1.2 # 估计行高\n \n for i, line in enumerate(lines):\n line_y = y + i * line_height\n \n if align == \"center\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width // 2\n elif align == \"right\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width\n else:\n line_x = x\n \n draw.text((line_x, line_y), line, font=font, fill=color)\n \n return image",
"code_hash": "77ca0755ebe7c9823cb2f3c5624e0972"
},
{
"name": "calculate_optimal_font_size",
"line_start": 456,
"line_end": 504,
"args": [
{
"name": "self"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "target_width",
"type_hint": "int"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "max_size",
"type_hint": "int"
},
{
"name": "min_size",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "计算最适合的字体大小\n\nArgs:\n text: 文字内容\n target_width: 目标宽度\n font_name: 字体文件名\n max_size: 最大字体大小\n min_size: 最小字体大小\n \nReturns:\n 最适合的字体大小",
"is_async": false,
"decorators": [],
"code": " def calculate_optimal_font_size(self, text: str, \n target_width: int, \n font_name: Optional[str] = None,\n max_size: int = 120, \n min_size: int = 10) -> int:\n \"\"\"\n 计算最适合的字体大小\n \n Args:\n text: 文字内容\n target_width: 目标宽度\n font_name: 字体文件名\n max_size: 最大字体大小\n min_size: 最小字体大小\n \n Returns:\n 最适合的字体大小\n \"\"\"\n if not text.strip():\n return min_size\n \n \n # 二分查找最佳字体大小\n left, right = min_size, max_size\n best_size = min_size\n \n while left <= right:\n mid_size = (left + right) // 2\n \n try:\n if font_path:\n font = self._load_default_font(mid_size)\n else:\n font = ImageFont.load_default()\n \n # 获取文字边界框\n bbox = font.getbbox(text)\n text_width = bbox[2] - bbox[0]\n \n if text_width <= target_width:\n best_size = mid_size\n left = mid_size + 1\n else:\n right = mid_size - 1\n \n except Exception:\n right = mid_size - 1\n \n return best_size",
"code_hash": "7177679a10411717652f703ea3782301"
},
{
"name": "get_text_size",
"line_start": 506,
"line_end": 518,
"args": [
{
"name": "self"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
}
],
"return_type": "Tuple[int, int]",
"docstring": "获取文字的尺寸\n\nArgs:\n text: 文字内容\n font: 字体对象\n \nReturns:\n 文字尺寸 (width, height)",
"is_async": false,
"decorators": [],
"code": " def get_text_size(self, text: str, font: ImageFont.FreeTypeFont) -> Tuple[int, int]:\n \"\"\"\n 获取文字的尺寸\n \n Args:\n text: 文字内容\n font: 字体对象\n \n Returns:\n 文字尺寸 (width, height)\n \"\"\"\n bbox = font.getbbox(text)\n return bbox[2] - bbox[0], bbox[3] - bbox[1]",
"code_hash": "34a4cacfe2cc136498adf181ca8bde4d"
},
{
"name": "draw_multiline_text",
"line_start": 520,
"line_end": 589,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "font_size",
"type_hint": "int"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "align",
"type_hint": "str"
},
{
"name": "spacing",
"type_hint": "int"
},
{
"name": "max_width",
"type_hint": "Optional[int]"
}
],
"return_type": "Image.Image",
"docstring": "绘制多行文本,支持自动换行\n\nArgs:\n image: 目标图像\n text: 要绘制的多行文本\n position: 文本起始位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n spacing: 行间距\n max_width: 最大宽度,超过会自动换行\n \nReturns:\n 绘制文本后的图像",
"is_async": false,
"decorators": [],
"code": " def draw_multiline_text(self, image: Image.Image, text: str, position: Tuple[int, int],\n font_name: Optional[str] = None, font_size: int = 24,\n color: Tuple[int, int, int] = (0, 0, 0),\n align: str = \"left\", spacing: int = 4,\n max_width: Optional[int] = None) -> Image.Image:\n \"\"\"\n 绘制多行文本,支持自动换行\n \n Args:\n image: 目标图像\n text: 要绘制的多行文本\n position: 文本起始位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n spacing: 行间距\n max_width: 最大宽度,超过会自动换行\n \n Returns:\n 绘制文本后的图像\n \"\"\"\n draw = ImageDraw.Draw(image)\n font = self.get_font(font_name, font_size)\n \n # 处理预先分行的文本\n paragraphs = text.split('\\n')\n all_lines = []\n \n for paragraph in paragraphs:\n if not max_width:\n all_lines.append(paragraph)\n continue\n \n # 自动换行处理\n words = paragraph.split()\n current_line = \"\"\n \n for word in words:\n test_line = current_line + word + \" \"\n text_width, _ = draw.textsize(test_line, font=font)\n \n if text_width <= max_width:\n current_line = test_line\n else:\n all_lines.append(current_line)\n current_line = word + \" \"\n \n if current_line:\n all_lines.append(current_line)\n \n # 绘制所有行\n x, y = position\n line_height = font_size + spacing\n \n for i, line in enumerate(all_lines):\n line_y = y + i * line_height\n \n if align == \"center\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width // 2\n elif align == \"right\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width\n else:\n line_x = x\n \n draw.text((line_x, line_y), line, font=font, fill=color)\n \n return image",
"code_hash": "9c35fcd4fda7b5c144d610016a4f3389"
},
{
"name": "__init__",
"line_start": 595,
"line_end": 596,
"args": [
{
"name": "self"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self):\n self.logger = logging.getLogger(__name__)",
"code_hash": "44df414b12e5ee556f443463aaf624c6"
},
{
"name": "extract_dominant_color",
"line_start": 598,
"line_end": 631,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "num_colors",
"type_hint": "int"
}
],
"return_type": "List[Tuple[int, int, int]]",
"docstring": "提取图像中的主要颜色\n\nArgs:\n image: 输入图像\n num_colors: 要提取的颜色数量\n \nReturns:\n 颜色列表,按主导程度排序",
"is_async": false,
"decorators": [],
"code": " def extract_dominant_color(self, image: Image.Image, num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 提取图像中的主要颜色\n \n Args:\n image: 输入图像\n num_colors: 要提取的颜色数量\n \n Returns:\n 颜色列表,按主导程度排序\n \"\"\"\n # 缩小图像以加快处理速度\n small_image = image.resize((100, 100))\n \n # 确保图像是RGB模式\n if small_image.mode != 'RGB':\n small_image = small_image.convert('RGB')\n \n # 获取所有像素\n pixels = list(small_image.getdata())\n \n # 计算颜色频率\n color_counts = {}\n for pixel in pixels:\n if pixel in color_counts:\n color_counts[pixel] += 1\n else:\n color_counts[pixel] = 1\n \n # 按频率排序\n sorted_colors = sorted(color_counts.items(), key=lambda x: x[1], reverse=True)\n \n # 返回前N个颜色\n return [color for color, _ in sorted_colors[:num_colors]]",
"code_hash": "3e134e006e02c56184cc5df05ac798f7"
},
{
"name": "extract_color_palette",
"line_start": 633,
"line_end": 694,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "num_colors",
"type_hint": "int"
}
],
"return_type": "List[Tuple[int, int, int]]",
"docstring": "提取图像的颜色调色板\n使用k-means聚类算法的简化版本\n\nArgs:\n image: 输入图像\n num_colors: 调色板中的颜色数量\n \nReturns:\n 颜色列表",
"is_async": false,
"decorators": [],
"code": " def extract_color_palette(self, image: Image.Image, num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 提取图像的颜色调色板\n 使用k-means聚类算法的简化版本\n \n Args:\n image: 输入图像\n num_colors: 调色板中的颜色数量\n \n Returns:\n 颜色列表\n \"\"\"\n # 缩小图像以加快处理速度\n small_image = image.resize((100, 100))\n \n # 确保图像是RGB模式\n if small_image.mode != 'RGB':\n small_image = small_image.convert('RGB')\n \n # 获取所有像素\n pixels = list(small_image.getdata())\n \n # 随机选择初始中心点\n centers = random.sample(pixels, num_colors)\n \n # 简单的k-means聚类 (最多迭代10次)\n for _ in range(10):\n clusters = [[] for _ in range(num_colors)]\n \n # 将每个像素分配给最近的中心点\n for pixel in pixels:\n distances = [self._color_distance(pixel, center) for center in centers]\n closest_center = distances.index(min(distances))\n clusters[closest_center].append(pixel)\n \n # 更新中心点\n new_centers = []\n for cluster in clusters:\n if not cluster:\n # 如果聚类为空,保持原中心点\n new_centers.append(centers[len(new_centers)])\n continue\n \n # 计算聚类中所有像素的平均值\n r_sum = sum(pixel[0] for pixel in cluster)\n g_sum = sum(pixel[1] for pixel in cluster)\n b_sum = sum(pixel[2] for pixel in cluster)\n \n new_center = (\n r_sum // len(cluster),\n g_sum // len(cluster),\n b_sum // len(cluster)\n )\n new_centers.append(new_center)\n \n # 检查是否收敛\n if centers == new_centers:\n break\n \n centers = new_centers\n \n return centers",
"code_hash": "7805106dfc98d6c6fae59428a0232e8d"
},
{
"name": "_color_distance",
"line_start": 696,
"line_end": 700,
"args": [
{
"name": "self"
},
{
"name": "color1",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "color2",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "float",
"docstring": "计算两个颜色之间的欧几里得距离",
"is_async": false,
"decorators": [],
"code": " def _color_distance(self, color1: Tuple[int, int, int], color2: Tuple[int, int, int]) -> float:\n \"\"\"计算两个颜色之间的欧几里得距离\"\"\"\n r1, g1, b1 = color1\n r2, g2, b2 = color2\n return math.sqrt((r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2)",
"code_hash": "1dbc4818188ff1fffefaeb57e66c333e"
},
{
"name": "generate_complementary_color",
"line_start": 702,
"line_end": 723,
"args": [
{
"name": "self"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "生成互补色\n\nArgs:\n color: 输入颜色 (R, G, B)\n \nReturns:\n 互补色 (R, G, B)",
"is_async": false,
"decorators": [],
"code": " def generate_complementary_color(self, color: Tuple[int, int, int]) -> Tuple[int, int, int]:\n \"\"\"\n 生成互补色\n \n Args:\n color: 输入颜色 (R, G, B)\n \n Returns:\n 互补色 (R, G, B)\n \"\"\"\n r, g, b = color\n \n # 转换为HSV\n h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)\n \n # 互补色的色相相差180度\n h = (h + 0.5) % 1.0\n \n # 转回RGB\n r, g, b = colorsys.hsv_to_rgb(h, s, v)\n \n return (int(r * 255), int(g * 255), int(b * 255))",
"code_hash": "b2d31d5e0dce335fa85e9f72baf70062"
},
{
"name": "generate_color_scheme",
"line_start": 725,
"line_end": 794,
"args": [
{
"name": "self"
},
{
"name": "base_color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "scheme_type",
"type_hint": "str"
},
{
"name": "num_colors",
"type_hint": "int"
}
],
"return_type": "List[Tuple[int, int, int]]",
"docstring": "生成配色方案\n\nArgs:\n base_color: 基础颜色 (R, G, B)\n scheme_type: 配色方案类型 ('complementary', 'analogous', 'triadic', 'monochromatic')\n num_colors: 生成的颜色数量\n \nReturns:\n 颜色列表",
"is_async": false,
"decorators": [],
"code": " def generate_color_scheme(self, base_color: Tuple[int, int, int], \n scheme_type: str = \"complementary\", \n num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 生成配色方案\n \n Args:\n base_color: 基础颜色 (R, G, B)\n scheme_type: 配色方案类型 ('complementary', 'analogous', 'triadic', 'monochromatic')\n num_colors: 生成的颜色数量\n \n Returns:\n 颜色列表\n \"\"\"\n r, g, b = base_color\n h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)\n colors = []\n \n if scheme_type == \"complementary\":\n # 互补色方案\n colors.append(base_color)\n h_comp = (h + 0.5) % 1.0\n r_comp, g_comp, b_comp = colorsys.hsv_to_rgb(h_comp, s, v)\n colors.append((int(r_comp * 255), int(g_comp * 255), int(b_comp * 255)))\n \n # 添加额外的颜色\n for i in range(num_colors - 2):\n h_extra = (h + (i + 1) * 0.1) % 1.0\n r_extra, g_extra, b_extra = colorsys.hsv_to_rgb(h_extra, s, v)\n colors.append((int(r_extra * 255), int(g_extra * 255), int(b_extra * 255)))\n \n elif scheme_type == \"analogous\":\n # 类似色方案\n for i in range(num_colors):\n h_analog = (h + (i - num_colors // 2) * 0.05) % 1.0\n r_analog, g_analog, b_analog = colorsys.hsv_to_rgb(h_analog, s, v)\n colors.append((int(r_analog * 255), int(g_analog * 255), int(b_analog * 255)))\n \n elif scheme_type == \"triadic\":\n # 三色方案\n for i in range(3):\n h_triad = (h + i / 3) % 1.0\n r_triad, g_triad, b_triad = colorsys.hsv_to_rgb(h_triad, s, v)\n colors.append((int(r_triad * 255), int(g_triad * 255), int(b_triad * 255)))\n \n # 添加额外的颜色\n for i in range(num_colors - 3):\n h_extra = (h + (i + 1) * 0.1) % 1.0\n r_extra, g_extra, b_extra = colorsys.hsv_to_rgb(h_extra, s, v)\n colors.append((int(r_extra * 255), int(g_extra * 255), int(b_extra * 255)))\n \n elif scheme_type == \"monochromatic\":\n # 单色方案\n for i in range(num_colors):\n s_mono = max(0.1, min(1.0, s - 0.3 + i * 0.6 / (num_colors - 1)))\n v_mono = max(0.2, min(1.0, v - 0.3 + i * 0.6 / (num_colors - 1)))\n r_mono, g_mono, b_mono = colorsys.hsv_to_rgb(h, s_mono, v_mono)\n colors.append((int(r_mono * 255), int(g_mono * 255), int(b_mono * 255)))\n \n else:\n # 默认返回随机颜色\n colors.append(base_color)\n for _ in range(num_colors - 1):\n h_rand = random.random()\n s_rand = random.uniform(0.5, 1.0)\n v_rand = random.uniform(0.5, 1.0)\n r_rand, g_rand, b_rand = colorsys.hsv_to_rgb(h_rand, s_rand, v_rand)\n colors.append((int(r_rand * 255), int(g_rand * 255), int(b_rand * 255)))\n \n return colors ",
"code_hash": "8ad2f43a907e30368246334878a0e30b"
}
],
"classes": [
{
"name": "ImageProcessor",
"line_start": 21,
"line_end": 176,
"bases": [],
"methods": [
{
"name": "__init__",
"line_start": 24,
"line_end": 25,
"args": [
{
"name": "self"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self):\n self.logger = logging.getLogger(__name__)",
"code_hash": "44df414b12e5ee556f443463aaf624c6"
},
{
"name": "resize_image",
"line_start": 27,
"line_end": 72,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "target_size",
"type_hint": "Tuple[int, int]"
},
{
"name": "keep_aspect",
"type_hint": "bool"
},
{
"name": "crop",
"type_hint": "bool"
}
],
"return_type": "Image.Image",
"docstring": "调整图像大小\n\nArgs:\n image: 原始图像\n target_size: 目标尺寸 (宽, 高)\n keep_aspect: 是否保持宽高比\n crop: 是否裁剪以适应目标尺寸\n \nReturns:\n 调整后的图像",
"is_async": false,
"decorators": [],
"code": " def resize_image(self, image: Image.Image, target_size: Tuple[int, int], \n keep_aspect: bool = True, crop: bool = False) -> Image.Image:\n \"\"\"\n 调整图像大小\n \n Args:\n image: 原始图像\n target_size: 目标尺寸 (宽, 高)\n keep_aspect: 是否保持宽高比\n crop: 是否裁剪以适应目标尺寸\n \n Returns:\n 调整后的图像\n \"\"\"\n if not keep_aspect:\n return image.resize(target_size, Image.LANCZOS)\n \n # 保持宽高比\n target_width, target_height = target_size\n width, height = image.size\n \n # 计算缩放比例\n ratio_w = target_width / width\n ratio_h = target_height / height\n \n if crop:\n # 裁剪模式:先缩放到较大的尺寸,然后裁剪中心部分\n ratio = max(ratio_w, ratio_h)\n new_width = int(width * ratio)\n new_height = int(height * ratio)\n resized = image.resize((new_width, new_height), Image.LANCZOS)\n \n # 计算裁剪区域\n left = (new_width - target_width) // 2\n top = (new_height - target_height) // 2\n right = left + target_width\n bottom = top + target_height\n \n return resized.crop((left, top, right, bottom))\n else:\n # 非裁剪模式:缩放到适应目标尺寸的最大尺寸\n ratio = min(ratio_w, ratio_h)\n new_width = int(width * ratio)\n new_height = int(height * ratio)\n \n return image.resize((new_width, new_height), Image.LANCZOS)",
"code_hash": "b33aae9e6611675806cf4fb375b64a1c"
},
{
"name": "apply_overlay",
"line_start": 74,
"line_end": 92,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "opacity",
"type_hint": "float"
}
],
"return_type": "Image.Image",
"docstring": "在图像上应用半透明覆盖层\n\nArgs:\n image: 原始图像\n color: 覆盖层颜色 (R, G, B)\n opacity: 不透明度 (0.0-1.0)\n \nReturns:\n 应用覆盖层后的图像",
"is_async": false,
"decorators": [],
"code": " def apply_overlay(self, image: Image.Image, color: Tuple[int, int, int], \n opacity: float = 0.5) -> Image.Image:\n \"\"\"\n 在图像上应用半透明覆盖层\n \n Args:\n image: 原始图像\n color: 覆盖层颜色 (R, G, B)\n opacity: 不透明度 (0.0-1.0)\n \n Returns:\n 应用覆盖层后的图像\n \"\"\"\n overlay = Image.new('RGBA', image.size, color + (int(255 * opacity),))\n \n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n return Image.alpha_composite(image, overlay)",
"code_hash": "bb0725c0526df33a4598341b75c6d36c"
},
{
"name": "apply_blur",
"line_start": 94,
"line_end": 105,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "radius",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "应用模糊效果\n\nArgs:\n image: 原始图像\n radius: 模糊半径\n \nReturns:\n 模糊后的图像",
"is_async": false,
"decorators": [],
"code": " def apply_blur(self, image: Image.Image, radius: int = 5) -> Image.Image:\n \"\"\"\n 应用模糊效果\n \n Args:\n image: 原始图像\n radius: 模糊半径\n \n Returns:\n 模糊后的图像\n \"\"\"\n return image.filter(ImageFilter.GaussianBlur(radius))",
"code_hash": "d475d68cee157578b64e7cc19f0ab35b"
},
{
"name": "create_rounded_corners",
"line_start": 107,
"line_end": 134,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "radius",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "创建圆角图像\n\nArgs:\n image: 原始图像\n radius: 圆角半径\n \nReturns:\n 圆角处理后的图像",
"is_async": false,
"decorators": [],
"code": " def create_rounded_corners(self, image: Image.Image, radius: int = 20) -> Image.Image:\n \"\"\"\n 创建圆角图像\n \n Args:\n image: 原始图像\n radius: 圆角半径\n \n Returns:\n 圆角处理后的图像\n \"\"\"\n # 创建一个透明的画布\n rounded = Image.new('RGBA', image.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(rounded)\n \n # 绘制圆角矩形\n width, height = image.size\n draw.rounded_rectangle([(0, 0), (width, height)], radius, fill=(255, 255, 255, 255))\n \n # 确保图像是RGBA模式\n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n # 使用圆角矩形作为蒙版\n result = Image.new('RGBA', image.size, (0, 0, 0, 0))\n result.paste(image, (0, 0), mask=rounded)\n \n return result",
"code_hash": "c96ddfd5e52835dbb2f71c912be68e43"
},
{
"name": "add_shadow",
"line_start": 136,
"line_end": 176,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "offset",
"type_hint": "Tuple[int, int]"
},
{
"name": "radius",
"type_hint": "int"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "opacity",
"type_hint": "float"
}
],
"return_type": "Image.Image",
"docstring": "为图像添加阴影效果\n\nArgs:\n image: 原始图像\n offset: 阴影偏移量 (x, y)\n radius: 阴影模糊半径\n color: 阴影颜色\n opacity: 阴影不透明度\n \nReturns:\n 添加阴影后的图像",
"is_async": false,
"decorators": [],
"code": " def add_shadow(self, image: Image.Image, offset: Tuple[int, int] = (5, 5), \n radius: int = 10, color: Tuple[int, int, int] = (0, 0, 0), \n opacity: float = 0.7) -> Image.Image:\n \"\"\"\n 为图像添加阴影效果\n \n Args:\n image: 原始图像\n offset: 阴影偏移量 (x, y)\n radius: 阴影模糊半径\n color: 阴影颜色\n opacity: 阴影不透明度\n \n Returns:\n 添加阴影后的图像\n \"\"\"\n # 确保图像是RGBA模式\n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n # 创建阴影蒙版\n shadow = Image.new('RGBA', image.size, (0, 0, 0, 0))\n shadow_draw = ImageDraw.Draw(shadow)\n shadow_draw.rectangle([(0, 0), image.size], fill=color + (int(255 * opacity),))\n \n # 应用模糊\n shadow = shadow.filter(ImageFilter.GaussianBlur(radius))\n \n # 创建结果图像\n result = Image.new('RGBA', (\n image.width + abs(offset[0]),\n image.height + abs(offset[1])\n ), (0, 0, 0, 0))\n \n # 放置阴影和原图\n x_offset = max(0, offset[0])\n y_offset = max(0, offset[1])\n result.paste(shadow, (x_offset, y_offset))\n result.paste(image, (max(0, -offset[0]), max(0, -offset[1])), mask=image)\n \n return result",
"code_hash": "ce595e27d5692055226a71112a469f4c"
}
],
"docstring": "图像处理工具类",
"decorators": [],
"code": "class ImageProcessor:\n \"\"\"图像处理工具类\"\"\"\n \n def __init__(self):\n self.logger = logging.getLogger(__name__)\n \n def resize_image(self, image: Image.Image, target_size: Tuple[int, int], \n keep_aspect: bool = True, crop: bool = False) -> Image.Image:\n \"\"\"\n 调整图像大小\n \n Args:\n image: 原始图像\n target_size: 目标尺寸 (宽, 高)\n keep_aspect: 是否保持宽高比\n crop: 是否裁剪以适应目标尺寸\n \n Returns:\n 调整后的图像\n \"\"\"\n if not keep_aspect:\n return image.resize(target_size, Image.LANCZOS)\n \n # 保持宽高比\n target_width, target_height = target_size\n width, height = image.size\n \n # 计算缩放比例\n ratio_w = target_width / width\n ratio_h = target_height / height\n \n if crop:\n # 裁剪模式:先缩放到较大的尺寸,然后裁剪中心部分\n ratio = max(ratio_w, ratio_h)\n new_width = int(width * ratio)\n new_height = int(height * ratio)\n resized = image.resize((new_width, new_height), Image.LANCZOS)\n \n # 计算裁剪区域\n left = (new_width - target_width) // 2\n top = (new_height - target_height) // 2\n right = left + target_width\n bottom = top + target_height\n \n return resized.crop((left, top, right, bottom))\n else:\n # 非裁剪模式:缩放到适应目标尺寸的最大尺寸\n ratio = min(ratio_w, ratio_h)\n new_width = int(width * ratio)\n new_height = int(height * ratio)\n \n return image.resize((new_width, new_height), Image.LANCZOS)\n \n def apply_overlay(self, image: Image.Image, color: Tuple[int, int, int], \n opacity: float = 0.5) -> Image.Image:\n \"\"\"\n 在图像上应用半透明覆盖层\n \n Args:\n image: 原始图像\n color: 覆盖层颜色 (R, G, B)\n opacity: 不透明度 (0.0-1.0)\n \n Returns:\n 应用覆盖层后的图像\n \"\"\"\n overlay = Image.new('RGBA', image.size, color + (int(255 * opacity),))\n \n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n return Image.alpha_composite(image, overlay)\n \n def apply_blur(self, image: Image.Image, radius: int = 5) -> Image.Image:\n \"\"\"\n 应用模糊效果\n \n Args:\n image: 原始图像\n radius: 模糊半径\n \n Returns:\n 模糊后的图像\n \"\"\"\n return image.filter(ImageFilter.GaussianBlur(radius))\n \n def create_rounded_corners(self, image: Image.Image, radius: int = 20) -> Image.Image:\n \"\"\"\n 创建圆角图像\n \n Args:\n image: 原始图像\n radius: 圆角半径\n \n Returns:\n 圆角处理后的图像\n \"\"\"\n # \n rounded = Image.new('RGBA', image.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(rounded)\n \n # \n width, height = image.size\n draw.rounded_rectangle([(0, 0), (width, height)], radius, fill=(255, 255, 255, 255))\n \n # RGBA\n if image.mode != 'RGBA':\n image = image.convert('RGBA')\n \n # 使\n result = Image.new('RGBA', image.size, (0, 0, 0, 0))\n result.paste(image, (0, 0), mask=rounded)\n \n return result\n \n def add_shadow(self, image: Image.Image, offset: Tuple[int, int] = (5, 5), \n radius: int = 10, color: Tuple[int, int, int] =
"code_hash": "c72991ff89be4810020aa9e1f9ba0126"
},
{
"name": "TextRenderer",
"line_start": 179,
"line_end": 589,
"bases": [],
"methods": [
{
"name": "__init__",
"line_start": 182,
"line_end": 185,
"args": [
{
"name": "self"
},
{
"name": "font_dir",
"type_hint": "str"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self, font_dir: str = \"assets/font\"):\n self.font_dir = font_dir\n self.logger = logging.getLogger(__name__)\n self.default_font = self._load_default_font()",
"code_hash": "53bbe02f8a779d48745cc173932affed"
},
{
"name": "_load_default_font",
"line_start": 187,
"line_end": 214,
"args": [
{
"name": "self"
},
{
"name": "size",
"type_hint": "int"
}
],
"return_type": "Optional[ImageFont.FreeTypeFont]",
"docstring": "加载默认字体",
"is_async": false,
"decorators": [],
"code": " def _load_default_font(self, size: int = 24) -> Optional[ImageFont.FreeTypeFont]:\n \"\"\"加载默认字体\"\"\"\n try:\n # 尝试加载系统字体\n system_fonts = [\n \"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\", # Linux\n \"/System/Library/Fonts/STHeiti Light.ttc\", # macOS\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\" # Windows\n ]\n \n # 首先尝试加载指定目录中的字体\n if os.path.exists(self.font_dir):\n font_files = [f for f in os.listdir(self.font_dir) \n if f.endswith(('.ttf', '.otf', '.ttc'))]\n if font_files:\n return ImageFont.truetype(os.path.join(self.font_dir, font_files[0]), size)\n \n # 然后尝试系统字体\n for font_path in system_fonts:\n if os.path.exists(font_path):\n return ImageFont.truetype(font_path, size)\n \n # 最后使用默认字体\n return ImageFont.load_default()\n \n except Exception as e:\n self.logger.warning(f\"加载默认字体失败: {e}\")\n return ImageFont.load_default()",
"code_hash": "1ff8f93f3c2f8e1e623db10208583a7c"
},
{
"name": "draw_text_with_outline",
"line_start": 216,
"line_end": 245,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_width",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "绘制带描边的文字\n\nArgs:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n outline_color: 描边颜色\n outline_width: 描边宽度",
"is_async": false,
"decorators": [],
"code": " def draw_text_with_outline(self, draw: ImageDraw.Draw, \n position: Tuple[int, int],\n text: str, \n font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n outline_color: Tuple[int, int, int, int] = (0, 0, 0, 255),\n outline_width: int = 2):\n \"\"\"\n 绘制带描边的文字\n \n Args:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n outline_color: 描边颜色\n outline_width: 描边宽度\n \"\"\"\n x, y = position\n \n # 绘制描边\n for offset_x in range(-outline_width, outline_width + 1):\n for offset_y in range(-outline_width, outline_width + 1):\n if offset_x == 0 and offset_y == 0:\n continue\n draw.text((x + offset_x, y + offset_y), text, font=font, fill=outline_color)\n \n # 绘制文字\n draw.text(position, text, font=font, fill=text_color)",
"code_hash": "516626aff1f3485af817e553081a7b50"
},
{
"name": "draw_rounded_rectangle",
"line_start": 246,
"line_end": 321,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "size",
"type_hint": "Tuple[int, int]"
},
{
"name": "radius",
"type_hint": "int"
},
{
"name": "fill_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_color",
"type_hint": "Optional[Tuple[int, int, int, int]]"
},
{
"name": "outline_width",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "绘制圆角矩形\n\nArgs:\n draw: PIL绘图对象\n position: 左上角位置\n size: 矩形大小\n radius: 圆角半径\n fill_color: 填充颜色\n outline_color: 边框颜色\n outline_width: 边框宽度",
"is_async": false,
"decorators": [],
"code": " def draw_rounded_rectangle(self, draw: ImageDraw.Draw,\n position: Tuple[int, int],\n size: Tuple[int, int],\n radius: int,\n fill_color: Tuple[int, int, int, int],\n outline_color: Optional[Tuple[int, int, int, int]] = None,\n outline_width: int = 0):\n \"\"\"\n 绘制圆角矩形\n \n Args:\n draw: PIL绘图对象\n position: 左上角位置\n size: 矩形大小\n radius: 圆角半径\n fill_color: 填充颜色\n outline_color: 边框颜色\n outline_width: 边框宽度\n \"\"\"\n x, y = position\n width, height = size\n \n # 确保尺寸有效\n if width <= 0 or height <= 0:\n return\n \n # 限制圆角半径\n radius = min(radius, width // 2, height // 2)\n \n # 创建圆角矩形路径\n # 这是一个简化版本PIL的较新版本有更好的圆角矩形支持\n if radius > 0:\n # 绘制中心矩形\n draw.rectangle([x + radius, y, x + width - radius, y + height], fill=fill_color)\n draw.rectangle([x, y + radius, x + width, y + height - radius], fill=fill_color)\n \n # 绘制四个圆角\n draw.pieslice([x, y, x + 2*radius, y + 2*radius], 180, 270, fill=fill_color)\n draw.pieslice([x + width - 2*radius, y, x + width, y + 2*radius], 270, 360, fill=fill_color)\n draw.pieslice([x, y + height - 2*radius, x + 2*radius, y + height], 90, 180, fill=fill_color)\n draw.pieslice([x + width - 2*radius, y + height - 2*radius, x + width, y + height], 0, 90, fill=fill_color)\n else:\n # 普通矩形\n draw.rectangle([x, y, x + width, y + height], fill=fill_color)\n \n # 绘制边框(如果需要)\n if outline_color and outline_width > 0:\n # 简化的边框绘制 - 使用线条而不是矩形避免坐标错误\n for i in range(outline_width):\n offset = i\n # 确保坐标有效\n if radius > 0:\n # 上边\n if x + radius + offset < x + width - radius - offset:\n draw.line([x + radius + offset, y + offset, \n x + width - radius - offset, y + offset], \n fill=outline_color, width=1)\n # 下边\n if x + radius + offset < x + width - radius - offset and y + height - offset >= y + offset:\n draw.line([x + radius + offset, y + height - offset, \n x + width - radius - offset, y + height - offset], \n fill=outline_color, width=1)\n # 左边\n if y + radius + offset < y + height - radius - offset:\n draw.line([x + offset, y + radius + offset, \n x + offset, y + height - radius - offset], \n fill=outline_color, width=1)\n # 右边\n if y + radius + offset < y + height - radius - offset:\n draw.line([x + width - offset, y + radius + offset, \n x + width - offset, y + height - radius - offset], \n fill=outline_color, width=1)\n else:\n # 普通矩形边框\n draw.rectangle([x + offset, y + offset, x + width - offset, y + height - offset], \n outline=outline_color, width=1)",
"code_hash": "d4cc4ef2bb7fb65ab2d335da8fd8a6c2"
},
{
"name": "draw_text_with_shadow",
"line_start": 323,
"line_end": 349,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_offset",
"type_hint": "Tuple[int, int]"
}
],
"return_type": null,
"docstring": "绘制带阴影的文字\n\nArgs:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n shadow_color: 阴影颜色\n shadow_offset: 阴影偏移",
"is_async": false,
"decorators": [],
"code": " def draw_text_with_shadow(self, draw: ImageDraw.Draw,\n position: Tuple[int, int],\n text: str,\n font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n shadow_color: Tuple[int, int, int, int] = (0, 0, 0, 128),\n shadow_offset: Tuple[int, int] = (2, 2)):\n \"\"\"\n 绘制带阴影的文字\n \n Args:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n shadow_color: 阴影颜色\n shadow_offset: 阴影偏移\n \"\"\"\n x, y = position\n shadow_x, shadow_y = shadow_offset\n \n # 绘制阴影\n draw.text((x + shadow_x, y + shadow_y), text, font=font, fill=shadow_color)\n \n # 绘制文字\n draw.text(position, text, font=font, fill=text_color)",
"code_hash": "27b04c83991936b0619e153f816763ef"
},
{
"name": "get_font",
"line_start": 351,
"line_end": 380,
"args": [
{
"name": "self"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "size",
"type_hint": "int"
}
],
"return_type": "ImageFont.FreeTypeFont",
"docstring": "获取指定字体\n\nArgs:\n font_name: 字体文件名如果为None则使用默认字体\n size: 字体大小\n \nReturns:\n 字体对象",
"is_async": false,
"decorators": [],
"code": " def get_font(self, font_name: Optional[str] = None, size: int = 24) -> ImageFont.FreeTypeFont:\n \"\"\"\n 获取指定字体\n \n Args:\n font_name: 字体文件名如果为None则使用默认字体\n size: 字体大小\n \n Returns:\n 字体对象\n \"\"\"\n if not font_name:\n return self.default_font.font_variant(size=size)\n \n try:\n # 尝试从字体目录加载\n font_path = os.path.join(self.font_dir, font_name)\n if os.path.exists(font_path):\n return ImageFont.truetype(font_path, size)\n \n # 尝试作为绝对路径加载\n if os.path.exists(font_name):\n return ImageFont.truetype(font_name, size)\n \n self.logger.warning(f\"找不到字体: {font_name},使用默认字体\")\n return self.default_font.font_variant(size=size)\n \n except Exception as e:\n self.logger.warning(f\"加载字体 {font_name} 失败: {e}\")\n return self.default_font.font_variant(size=size)",
"code_hash": "c6a099a04c79f2fdc1668e9103756e50"
},
{
"name": "draw_text",
"line_start": 382,
"line_end": 454,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "font_size",
"type_hint": "int"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "align",
"type_hint": "str"
},
{
"name": "max_width",
"type_hint": "Optional[int]"
}
],
"return_type": "Image.Image",
"docstring": "在图像上绘制文本\n\nArgs:\n image: 目标图像\n text: 要绘制的文本\n position: 文本位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n max_width: 最大宽度,超过会自动换行\n \nReturns:\n 绘制文本后的图像",
"is_async": false,
"decorators": [],
"code": " def draw_text(self, image: Image.Image, text: str, position: Tuple[int, int], \n font_name: Optional[str] = None, font_size: int = 24, \n color: Tuple[int, int, int] = (0, 0, 0), \n align: str = \"left\", max_width: Optional[int] = None) -> Image.Image:\n \"\"\"\n 在图像上绘制文本\n \n Args:\n image: 目标图像\n text: 要绘制的文本\n position: 文本位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n max_width: 最大宽度,超过会自动换行\n \n Returns:\n 绘制文本后的图像\n \"\"\"\n draw = ImageDraw.Draw(image)\n font = self.get_font(font_name, font_size)\n \n if not max_width:\n # 简单绘制文本\n x, y = position\n \n if align == \"center\":\n text_width, _ = draw.textsize(text, font=font)\n x -= text_width // 2\n elif align == \"right\":\n text_width, _ = draw.textsize(text, font=font)\n x -= text_width\n \n draw.text((x, y), text, font=font, fill=color)\n return image\n \n # 处理自动换行\n lines = []\n current_line = \"\"\n \n for word in text.split():\n test_line = current_line + word + \" \"\n text_width, _ = draw.textsize(test_line, font=font)\n \n if text_width <= max_width:\n current_line = test_line\n else:\n lines.append(current_line)\n current_line = word + \" \"\n \n if current_line:\n lines.append(current_line)\n \n # 绘制每一行\n x, y = position\n line_height = font_size * 1.2 # 估计行高\n \n for i, line in enumerate(lines):\n line_y = y + i * line_height\n \n if align == \"center\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width // 2\n elif align == \"right\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width\n else:\n line_x = x\n \n draw.text((line_x, line_y), line, font=font, fill=color)\n \n return image",
"code_hash": "77ca0755ebe7c9823cb2f3c5624e0972"
},
{
"name": "calculate_optimal_font_size",
"line_start": 456,
"line_end": 504,
"args": [
{
"name": "self"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "target_width",
"type_hint": "int"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "max_size",
"type_hint": "int"
},
{
"name": "min_size",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "计算最适合的字体大小\n\nArgs:\n text: 文字内容\n target_width: 目标宽度\n font_name: 字体文件名\n max_size: 最大字体大小\n min_size: 最小字体大小\n \nReturns:\n 最适合的字体大小",
"is_async": false,
"decorators": [],
"code": " def calculate_optimal_font_size(self, text: str, \n target_width: int, \n font_name: Optional[str] = None,\n max_size: int = 120, \n min_size: int = 10) -> int:\n \"\"\"\n 计算最适合的字体大小\n \n Args:\n text: 文字内容\n target_width: 目标宽度\n font_name: 字体文件名\n max_size: 最大字体大小\n min_size: 最小字体大小\n \n Returns:\n 最适合的字体大小\n \"\"\"\n if not text.strip():\n return min_size\n \n \n # 二分查找最佳字体大小\n left, right = min_size, max_size\n best_size = min_size\n \n while left <= right:\n mid_size = (left + right) // 2\n \n try:\n if font_path:\n font = self._load_default_font(mid_size)\n else:\n font = ImageFont.load_default()\n \n # 获取文字边界框\n bbox = font.getbbox(text)\n text_width = bbox[2] - bbox[0]\n \n if text_width <= target_width:\n best_size = mid_size\n left = mid_size + 1\n else:\n right = mid_size - 1\n \n except Exception:\n right = mid_size - 1\n \n return best_size",
"code_hash": "7177679a10411717652f703ea3782301"
},
{
"name": "get_text_size",
"line_start": 506,
"line_end": 518,
"args": [
{
"name": "self"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
}
],
"return_type": "Tuple[int, int]",
"docstring": "获取文字的尺寸\n\nArgs:\n text: 文字内容\n font: 字体对象\n \nReturns:\n 文字尺寸 (width, height)",
"is_async": false,
"decorators": [],
"code": " def get_text_size(self, text: str, font: ImageFont.FreeTypeFont) -> Tuple[int, int]:\n \"\"\"\n 获取文字的尺寸\n \n Args:\n text: 文字内容\n font: 字体对象\n \n Returns:\n 文字尺寸 (width, height)\n \"\"\"\n bbox = font.getbbox(text)\n return bbox[2] - bbox[0], bbox[3] - bbox[1]",
"code_hash": "34a4cacfe2cc136498adf181ca8bde4d"
},
{
"name": "draw_multiline_text",
"line_start": 520,
"line_end": 589,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "font_name",
"type_hint": "Optional[str]"
},
{
"name": "font_size",
"type_hint": "int"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "align",
"type_hint": "str"
},
{
"name": "spacing",
"type_hint": "int"
},
{
"name": "max_width",
"type_hint": "Optional[int]"
}
],
"return_type": "Image.Image",
"docstring": "绘制多行文本,支持自动换行\n\nArgs:\n image: 目标图像\n text: 要绘制的多行文本\n position: 文本起始位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n spacing: 行间距\n max_width: 最大宽度,超过会自动换行\n \nReturns:\n 绘制文本后的图像",
"is_async": false,
"decorators": [],
"code": " def draw_multiline_text(self, image: Image.Image, text: str, position: Tuple[int, int],\n font_name: Optional[str] = None, font_size: int = 24,\n color: Tuple[int, int, int] = (0, 0, 0),\n align: str = \"left\", spacing: int = 4,\n max_width: Optional[int] = None) -> Image.Image:\n \"\"\"\n 绘制多行文本,支持自动换行\n \n Args:\n image: 目标图像\n text: 要绘制的多行文本\n position: 文本起始位置 (x, y)\n font_name: 字体文件名\n font_size: 字体大小\n color: 文本颜色 (R, G, B)\n align: 对齐方式 ('left', 'center', 'right')\n spacing: 行间距\n max_width: 最大宽度,超过会自动换行\n \n Returns:\n 绘制文本后的图像\n \"\"\"\n draw = ImageDraw.Draw(image)\n font = self.get_font(font_name, font_size)\n \n # 处理预先分行的文本\n paragraphs = text.split('\\n')\n all_lines = []\n \n for paragraph in paragraphs:\n if not max_width:\n all_lines.append(paragraph)\n continue\n \n # 自动换行处理\n words = paragraph.split()\n current_line = \"\"\n \n for word in words:\n test_line = current_line + word + \" \"\n text_width, _ = draw.textsize(test_line, font=font)\n \n if text_width <= max_width:\n current_line = test_line\n else:\n all_lines.append(current_line)\n current_line = word + \" \"\n \n if current_line:\n all_lines.append(current_line)\n \n # 绘制所有行\n x, y = position\n line_height = font_size + spacing\n \n for i, line in enumerate(all_lines):\n line_y = y + i * line_height\n \n if align == \"center\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width // 2\n elif align == \"right\":\n line_width, _ = draw.textsize(line, font=font)\n line_x = x - line_width\n else:\n line_x = x\n \n draw.text((line_x, line_y), line, font=font, fill=color)\n \n return image",
"code_hash": "9c35fcd4fda7b5c144d610016a4f3389"
}
],
"docstring": "文本渲染工具类",
"decorators": [],
"code": "class TextRenderer:\n \"\"\"文本渲染工具类\"\"\"\n \n def __init__(self, font_dir: str = \"assets/font\"):\n self.font_dir = font_dir\n self.logger = logging.getLogger(__name__)\n self.default_font = self._load_default_font()\n \n def _load_default_font(self, size: int = 24) -> Optional[ImageFont.FreeTypeFont]:\n \"\"\"加载默认字体\"\"\"\n try:\n # 尝试加载系统字体\n system_fonts = [\n \"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\", # Linux\n \"/System/Library/Fonts/STHeiti Light.ttc\", # macOS\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\" # Windows\n ]\n \n # 首先尝试加载指定目录中的字体\n if os.path.exists(self.font_dir):\n font_files = [f for f in os.listdir(self.font_dir) \n if f.endswith(('.ttf', '.otf', '.ttc'))]\n if font_files:\n return ImageFont.truetype(os.path.join(self.font_dir, font_files[0]), size)\n \n # 然后尝试系统字体\n for font_path in system_fonts:\n if os.path.exists(font_path):\n return ImageFont.truetype(font_path, size)\n \n # 最后使用默认字体\n return ImageFont.load_default()\n \n except Exception as e:\n self.logger.warning(f\"加载默认字体失败: {e}\")\n return ImageFont.load_default()\n\n def draw_text_with_outline(self, draw: ImageDraw.Draw, \n position: Tuple[int, int],\n text: str, \n font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n outline_color: Tuple[int, int, int, int] = (0, 0, 0, 255),\n outline_width: int = 2):\n \"\"\"\n 绘制带描边的文字\n \n Args:\n draw: PIL绘图对象\n position: 文字位置\n text: 文字内容\n font: 字体对象\n text_color: 文字颜色\n outline_color: 描边颜色\n outline_width: 描边宽度\n \"\"\"\n x, y = position\n \n # 绘制描边\n for offset_x in range(-outline_width, outline_width + 1):\n for offset_y in range(-outline_width, outline_width + 1):\n if offset_x == 0 and offset_y == 0:\n continue\n draw.text((x + offset_x, y + offset_y), text, font=font, fill=outline_color)\n \n # 绘制文字\n draw.text(position, text, font=font, fill=text_color)\n def draw_rounded_rectangle(self, draw: ImageDraw.Draw,\n position: Tuple[int, int],\n size: Tuple[int, int],\n radius: int,\n fill_color: Tuple[int, int, int, int],\n outline_color: Optional[Tuple[int, int, int, int]] = None,\n outline_width: int = 0):\n \"\"\"\n 绘制圆角矩形\n \n Args:\n draw: PIL绘图对象\n position: 左上角位置\n size: 矩形大小\n radius: 圆角半径\n fill_color: 填充颜色\n outline_color: 边框颜色\n outline_width: 边框宽度\n \"\"\"\n x, y = position\n width, height = size\n \n # \n if width <= 0 or height <= 0:\n return\n \n # \n radius = min(radius, width // 2, height // 2)\n \n # 创建圆角矩形路径\n # 这是一个简化版本PIL的较新版本有更好的圆角矩形支持\n if radius > 0:\n # 绘制中心矩形\n dr
"code_hash": "8edef3ce197efd8b7d6ee106b576960f"
},
{
"name": "ColorExtractor",
"line_start": 592,
"line_end": 794,
"bases": [],
"methods": [
{
"name": "__init__",
"line_start": 595,
"line_end": 596,
"args": [
{
"name": "self"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self):\n self.logger = logging.getLogger(__name__)",
"code_hash": "44df414b12e5ee556f443463aaf624c6"
},
{
"name": "extract_dominant_color",
"line_start": 598,
"line_end": 631,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "num_colors",
"type_hint": "int"
}
],
"return_type": "List[Tuple[int, int, int]]",
"docstring": "提取图像中的主要颜色\n\nArgs:\n image: 输入图像\n num_colors: 要提取的颜色数量\n \nReturns:\n 颜色列表,按主导程度排序",
"is_async": false,
"decorators": [],
"code": " def extract_dominant_color(self, image: Image.Image, num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 提取图像中的主要颜色\n \n Args:\n image: 输入图像\n num_colors: 要提取的颜色数量\n \n Returns:\n 颜色列表,按主导程度排序\n \"\"\"\n # 缩小图像以加快处理速度\n small_image = image.resize((100, 100))\n \n # 确保图像是RGB模式\n if small_image.mode != 'RGB':\n small_image = small_image.convert('RGB')\n \n # 获取所有像素\n pixels = list(small_image.getdata())\n \n # 计算颜色频率\n color_counts = {}\n for pixel in pixels:\n if pixel in color_counts:\n color_counts[pixel] += 1\n else:\n color_counts[pixel] = 1\n \n # 按频率排序\n sorted_colors = sorted(color_counts.items(), key=lambda x: x[1], reverse=True)\n \n # 返回前N个颜色\n return [color for color, _ in sorted_colors[:num_colors]]",
"code_hash": "3e134e006e02c56184cc5df05ac798f7"
},
{
"name": "extract_color_palette",
"line_start": 633,
"line_end": 694,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "num_colors",
"type_hint": "int"
}
],
"return_type": "List[Tuple[int, int, int]]",
"docstring": "提取图像的颜色调色板\n使用k-means聚类算法的简化版本\n\nArgs:\n image: 输入图像\n num_colors: 调色板中的颜色数量\n \nReturns:\n 颜色列表",
"is_async": false,
"decorators": [],
"code": " def extract_color_palette(self, image: Image.Image, num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 提取图像的颜色调色板\n 使用k-means聚类算法的简化版本\n \n Args:\n image: 输入图像\n num_colors: 调色板中的颜色数量\n \n Returns:\n 颜色列表\n \"\"\"\n # 缩小图像以加快处理速度\n small_image = image.resize((100, 100))\n \n # 确保图像是RGB模式\n if small_image.mode != 'RGB':\n small_image = small_image.convert('RGB')\n \n # 获取所有像素\n pixels = list(small_image.getdata())\n \n # 随机选择初始中心点\n centers = random.sample(pixels, num_colors)\n \n # 简单的k-means聚类 (最多迭代10次)\n for _ in range(10):\n clusters = [[] for _ in range(num_colors)]\n \n # 将每个像素分配给最近的中心点\n for pixel in pixels:\n distances = [self._color_distance(pixel, center) for center in centers]\n closest_center = distances.index(min(distances))\n clusters[closest_center].append(pixel)\n \n # 更新中心点\n new_centers = []\n for cluster in clusters:\n if not cluster:\n # 如果聚类为空,保持原中心点\n new_centers.append(centers[len(new_centers)])\n continue\n \n # 计算聚类中所有像素的平均值\n r_sum = sum(pixel[0] for pixel in cluster)\n g_sum = sum(pixel[1] for pixel in cluster)\n b_sum = sum(pixel[2] for pixel in cluster)\n \n new_center = (\n r_sum // len(cluster),\n g_sum // len(cluster),\n b_sum // len(cluster)\n )\n new_centers.append(new_center)\n \n # 检查是否收敛\n if centers == new_centers:\n break\n \n centers = new_centers\n \n return centers",
"code_hash": "7805106dfc98d6c6fae59428a0232e8d"
},
{
"name": "_color_distance",
"line_start": 696,
"line_end": 700,
"args": [
{
"name": "self"
},
{
"name": "color1",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "color2",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "float",
"docstring": "计算两个颜色之间的欧几里得距离",
"is_async": false,
"decorators": [],
"code": " def _color_distance(self, color1: Tuple[int, int, int], color2: Tuple[int, int, int]) -> float:\n \"\"\"计算两个颜色之间的欧几里得距离\"\"\"\n r1, g1, b1 = color1\n r2, g2, b2 = color2\n return math.sqrt((r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2)",
"code_hash": "1dbc4818188ff1fffefaeb57e66c333e"
},
{
"name": "generate_complementary_color",
"line_start": 702,
"line_end": 723,
"args": [
{
"name": "self"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "生成互补色\n\nArgs:\n color: 输入颜色 (R, G, B)\n \nReturns:\n 互补色 (R, G, B)",
"is_async": false,
"decorators": [],
"code": " def generate_complementary_color(self, color: Tuple[int, int, int]) -> Tuple[int, int, int]:\n \"\"\"\n 生成互补色\n \n Args:\n color: 输入颜色 (R, G, B)\n \n Returns:\n 互补色 (R, G, B)\n \"\"\"\n r, g, b = color\n \n # 转换为HSV\n h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)\n \n # 互补色的色相相差180度\n h = (h + 0.5) % 1.0\n \n # 转回RGB\n r, g, b = colorsys.hsv_to_rgb(h, s, v)\n \n return (int(r * 255), int(g * 255), int(b * 255))",
"code_hash": "b2d31d5e0dce335fa85e9f72baf70062"
},
{
"name": "generate_color_scheme",
"line_start": 725,
"line_end": 794,
"args": [
{
"name": "self"
},
{
"name": "base_color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "scheme_type",
"type_hint": "str"
},
{
"name": "num_colors",
"type_hint": "int"
}
],
"return_type": "List[Tuple[int, int, int]]",
"docstring": "生成配色方案\n\nArgs:\n base_color: 基础颜色 (R, G, B)\n scheme_type: 配色方案类型 ('complementary', 'analogous', 'triadic', 'monochromatic')\n num_colors: 生成的颜色数量\n \nReturns:\n 颜色列表",
"is_async": false,
"decorators": [],
"code": " def generate_color_scheme(self, base_color: Tuple[int, int, int], \n scheme_type: str = \"complementary\", \n num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 生成配色方案\n \n Args:\n base_color: 基础颜色 (R, G, B)\n scheme_type: 配色方案类型 ('complementary', 'analogous', 'triadic', 'monochromatic')\n num_colors: 生成的颜色数量\n \n Returns:\n 颜色列表\n \"\"\"\n r, g, b = base_color\n h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)\n colors = []\n \n if scheme_type == \"complementary\":\n # 互补色方案\n colors.append(base_color)\n h_comp = (h + 0.5) % 1.0\n r_comp, g_comp, b_comp = colorsys.hsv_to_rgb(h_comp, s, v)\n colors.append((int(r_comp * 255), int(g_comp * 255), int(b_comp * 255)))\n \n # 添加额外的颜色\n for i in range(num_colors - 2):\n h_extra = (h + (i + 1) * 0.1) % 1.0\n r_extra, g_extra, b_extra = colorsys.hsv_to_rgb(h_extra, s, v)\n colors.append((int(r_extra * 255), int(g_extra * 255), int(b_extra * 255)))\n \n elif scheme_type == \"analogous\":\n # 类似色方案\n for i in range(num_colors):\n h_analog = (h + (i - num_colors // 2) * 0.05) % 1.0\n r_analog, g_analog, b_analog = colorsys.hsv_to_rgb(h_analog, s, v)\n colors.append((int(r_analog * 255), int(g_analog * 255), int(b_analog * 255)))\n \n elif scheme_type == \"triadic\":\n # 三色方案\n for i in range(3):\n h_triad = (h + i / 3) % 1.0\n r_triad, g_triad, b_triad = colorsys.hsv_to_rgb(h_triad, s, v)\n colors.append((int(r_triad * 255), int(g_triad * 255), int(b_triad * 255)))\n \n # 添加额外的颜色\n for i in range(num_colors - 3):\n h_extra = (h + (i + 1) * 0.1) % 1.0\n r_extra, g_extra, b_extra = colorsys.hsv_to_rgb(h_extra, s, v)\n colors.append((int(r_extra * 255), int(g_extra * 255), int(b_extra * 255)))\n \n elif scheme_type == \"monochromatic\":\n # 单色方案\n for i in range(num_colors):\n s_mono = max(0.1, min(1.0, s - 0.3 + i * 0.6 / (num_colors - 1)))\n v_mono = max(0.2, min(1.0, v - 0.3 + i * 0.6 / (num_colors - 1)))\n r_mono, g_mono, b_mono = colorsys.hsv_to_rgb(h, s_mono, v_mono)\n colors.append((int(r_mono * 255), int(g_mono * 255), int(b_mono * 255)))\n \n else:\n # 默认返回随机颜色\n colors.append(base_color)\n for _ in range(num_colors - 1):\n h_rand = random.random()\n s_rand = random.uniform(0.5, 1.0)\n v_rand = random.uniform(0.5, 1.0)\n r_rand, g_rand, b_rand = colorsys.hsv_to_rgb(h_rand, s_rand, v_rand)\n colors.append((int(r_rand * 255), int(g_rand * 255), int(b_rand * 255)))\n \n return colors ",
"code_hash": "8ad2f43a907e30368246334878a0e30b"
}
],
"docstring": "颜色提取工具类",
"decorators": [],
"code": "class ColorExtractor:\n \"\"\"颜色提取工具类\"\"\"\n \n def __init__(self):\n self.logger = logging.getLogger(__name__)\n \n def extract_dominant_color(self, image: Image.Image, num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 提取图像中的主要颜色\n \n Args:\n image: 输入图像\n num_colors: 要提取的颜色数量\n \n Returns:\n 颜色列表,按主导程度排序\n \"\"\"\n # 缩小图像以加快处理速度\n small_image = image.resize((100, 100))\n \n # 确保图像是RGB模式\n if small_image.mode != 'RGB':\n small_image = small_image.convert('RGB')\n \n # 获取所有像素\n pixels = list(small_image.getdata())\n \n # 计算颜色频率\n color_counts = {}\n for pixel in pixels:\n if pixel in color_counts:\n color_counts[pixel] += 1\n else:\n color_counts[pixel] = 1\n \n # 按频率排序\n sorted_colors = sorted(color_counts.items(), key=lambda x: x[1], reverse=True)\n \n # 返回前N个颜色\n return [color for color, _ in sorted_colors[:num_colors]]\n \n def extract_color_palette(self, image: Image.Image, num_colors: int = 5) -> List[Tuple[int, int, int]]:\n \"\"\"\n 提取图像的颜色调色板\n 使用k-means聚类算法的简化版本\n \n Args:\n image: 输入图像\n num_colors: 调色板中的颜色数量\n \n Returns:\n 颜色列表\n \"\"\"\n # 缩小图像以加快处理速度\n small_image = image.resize((100, 100))\n \n # 确保图像是RGB模式\n if small_image.mode != 'RGB':\n small_image = small_image.convert('RGB')\n \n # 获取所有像素\n pixels = list(small_image.getdata())\n \n # 随机选择初始中心点\n centers = random.sample(pixels, num_colors)\n \n # 简单的k-means聚类 (最多迭代10次)\n for _ in range(10):\n clusters = [[] for _ in range(num_colors)]\n \n # 将每个像素分配给最近的中心点\n for pixel in pixels:\n distances = [self._color_distance(pixel, center) for center in centers]\n closest_center = distances.index(min(distances))\n clusters[closest_center].append(pixel)\n \n # 更新中心点\n new_centers = []\n for cluster in clusters:\n if not cluster:\n # 如果聚类为空,保持原中心点\n new_centers.append(centers[len(new_centers)])\n continue\n \n # 计算聚类中所有像素的平均值\n r_sum = sum(pixel[0] for pixel in cluster)\n g_sum = sum(pixel[1] for pixel in cluster)\n b_sum = sum(pixel[2] for pixel in cluster)\n \n new_center = (\n r_sum // len(cluster),\n g_sum // len(cluster),\n b_sum // len(cluster)\n )\n new_centers.append(new_center)\n \n # 检查是否收敛\n if centers == new_centers:\n break\n \n centers = new_centers\n \n return centers\n \n def _color_distance(self, color1: Tuple[int, int, int], color2: Tuple[int, int, int]) -> float:\n \"\"\"计算两个颜色之间的欧几里得距离\"\"\"\n r1, g1, b1 = color1\n r2, g2, b2 = color2\n return math.sqrt((r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2)\n \n def generate_complementary_color(self, color: Tuple[int, int, int]) -> Tuple[int, int, int]:\n \"\"\"\n
"code_hash": "090ffcfee80b02324c426d71037816a1"
}
],
"imports": [
{
"type": "import",
"modules": [
"os"
],
"aliases": []
},
{
"type": "import",
"modules": [
"logging"
],
"aliases": []
},
{
"type": "import",
"modules": [
"random"
],
"aliases": []
},
{
"type": "from_import",
"module": "typing",
"names": [
"Tuple",
"List",
"Dict",
"Any",
"Optional",
"Union"
],
"aliases": [],
"level": 0
},
{
"type": "from_import",
"module": "pathlib",
"names": [
"Path"
],
"aliases": [],
"level": 0
},
{
"type": "import",
"modules": [
"colorsys"
],
"aliases": []
},
{
"type": "import",
"modules": [
"math"
],
"aliases": []
},
{
"type": "from_import",
"module": "PIL",
"names": [
"Image",
"ImageDraw",
"ImageFont",
"ImageFilter",
"ImageEnhance"
],
"aliases": [],
"level": 0
}
],
"constants": [],
"docstring": "海报生成工具类\n提供图像处理、文本渲染和颜色提取等功能",
"content_hash": "c5b6a9806b79140f943b4e47fe2e1c8a"
}