iShotaBot/defs/friend_say.py

311 lines
12 KiB
Python
Raw Normal View History

2021-11-28 14:55:10 +00:00
from io import BytesIO
from os import sep
from typing import Tuple, Union, Literal, Optional
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
class ImageUtil:
"""
:说明: `ImageUtil`
> 图片处理工具类
> Author: HibiKier
"""
def __init__(
self,
width: int,
height: int,
paste_image_width: int = 0,
paste_image_height: int = 0,
color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = None,
image_mode: Literal[
"CMYK", "HSV", "LAB", "RGB", "RGBA", "RGBX", "YCbCr"
] = "RGBA",
font_size: int = 10,
background: Union[Optional[str], BytesIO, Path] = None,
font: str = "sarasa-mono-sc-semibold.ttf",
ratio: float = 1,
is_alpha: bool = False,
plain_text: Optional[str] = None,
font_color: Optional[Tuple[int, int, int]] = None,
) -> None:
"""
:说明: `__init__`
> 创建图片处理对象
:参数:
* `width: int`: 图片宽度
* `height: int`: 图片高度
:可选参数:
* `paste_image_width: int = 0`: 当图片做为背景图时设置贴图的宽度用于贴图自动换行
* `paste_image_height: int = 0`: 当图片做为背景图时设置贴图的高度用于贴图自动换行
* `color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = None`: 生成图片的颜色
* `image_mode: Literal["CMYK", "HSV", "LAB", "RGB", "RGBA", "RGBX", "YCbCr"] = "RGBA"`: 图片类型
* `font_size: int = 10`: 字体大小
* `background: Union[Optional[str], BytesIO, Path] = None`: 背景图片路径
* `font: str = "sarasa-mono-sc-semibold.ttf"`: 字体路径
* `ratio: float = 1`: 图片缩放比例
* `is_alpha: bool = False`: 是否使用透明度
* `plain_text: Optional[str] = None`: 纯文本内容
* `font_color: Optional[Tuple[int, int, int]] = None`: 字体颜色
:错误:
- `ValueError`: image_mode 不在范围内
"""
self.width = width
self.height = height
self.paste_image_width = paste_image_width
self.paste_image_height = paste_image_height
self.current_width = 0
self.current_height = 0
self.font = ImageFont.truetype(f"resources{sep}font{sep}{font}", font_size)
if image_mode not in ["CMYK", "HSV", "LAB", "RGB", "RGBA", "RGBX", "YCbCr"]:
raise ValueError(f"image_mode: {image_mode}错误")
if not color:
color = (255, 255, 255)
if not background:
if plain_text:
self.width = (
self.width
if self.width > self.font.getsize(plain_text)[0]
else self.font.getsize(plain_text)[1]
)
self.height = (
self.height
if self.height > self.font.getsize(plain_text)[1]
else self.font.getsize(plain_text)[1]
)
self.mark_image = Image.new(image_mode, (self.width, self.height), color)
self.mark_image.convert(image_mode)
else:
if not width and not height:
self.mark_image = Image.open(background)
width, height = self.mark_image.size
if ratio and ratio > 0 and ratio != 1:
self.width = int(ratio * width)
self.height = int(ratio * height)
self.mark_image = self.mark_image.resize(
(self.width, self.height), Image.ANTIALIAS
)
else:
self.width = width
self.height = height
else:
self.mark_image = Image.open(background).resize(
(self.width, self.height), Image.ANTIALIAS
)
if is_alpha:
array = self.mark_image.load()
for i in range(width):
for j in range(height):
pos = array[i, j] # type: ignore
is_edit = sum([1 for x in pos[0:3] if x > 240]) == 3
if is_edit:
array[i, j] = (255, 255, 255, 0) # type: ignore
self.draw = ImageDraw.Draw(self.mark_image)
self.size = self.width, self.height
if plain_text:
fill = font_color if font_color else (0, 0, 0)
self.text((0, 0), plain_text, fill)
def text(
self,
pos: Tuple[int, int],
text: str,
fill: Tuple[int, int, int] = (0, 0, 0),
center_type: Literal["center", "by_height", "by_width"] = None,
):
"""
:说明: `text`
> 在图片上添加文本
:参数:
* `pos: Tuple[int, int]`: 文本位置
* `text: str`: 文本内容
:可选参数:
* `fill: Tuple[int, int, int] = (0, 0, 0)`: 文本颜色
* `center_type: Literal["center", "by_height", "by_width"] = None`: 文本居中方式
:错误:
* `ValueError`: `center_type` 不为 `center`, `by_height`, `by_width`
"""
if center_type:
if center_type not in ["center", "by_height", "by_width"]:
raise ValueError(
"center_type must be 'center', 'by_width' or 'by_height'"
)
width, height = self.width, self.height
text_widht, text_height = self.getsize(text)
if center_type == "center":
width = int((width - text_widht) / 2)
height = int((height - text_height) / 2)
elif center_type == "by_width":
width = int((width - text_widht) / 2)
height = pos[1]
elif center_type == "by_height":
height = int((height - text_height) / 2)
width = pos[0]
pos = (width, height)
self.draw.text(pos, text, fill=fill, font=self.font)
def paste(
self,
img: Union["ImageUtil", Image.Image],
pos: Tuple[int, int] = None,
alpha: bool = False,
center_type: Literal["center", "by_height", "by_width"] = None,
):
"""
:说明: `paste`
> 在图片上添加图片
:参数:
* `img: ImageUtil`: 图片对象
:可选参数:
* `pos: Tuple[int, int] = None`: 图片位置
* `alpha: bool = False`: 是否使用透明度
* `center_type: Literal["center", "by_height", "by_width"] = None`: 图片居中方式
:错误:
- `ValueError`: `center_type` 不为 `center`, `by_height`, `by_width`
"""
if center_type:
if center_type not in ["center", "by_height", "by_width"]:
raise ValueError(
"center_type must be 'center', 'by_width' or 'by_height'"
)
width, height = 0, 0
if not pos:
pos = (0, 0)
if center_type == "center":
width = int((self.width - img.width) / 2)
height = int((self.height - img.height) / 2)
elif center_type == "by_width":
width = int((self.width - img.width) / 2)
height = pos[1]
elif center_type == "by_height":
width = pos[0]
height = int((self.height - img.height) / 2)
pos = (width, height)
if isinstance(img, ImageUtil):
img = img.mark_image
if self.current_width == self.width:
self.current_width = 0
self.current_height += self.paste_image_height
if not pos:
pos = (self.current_width, self.current_height)
if alpha:
try:
self.mark_image.paste(img, pos, img)
except ValueError:
img = img.convert("RGBA")
self.mark_image.paste(img, pos, img)
else:
self.mark_image.paste(img, pos)
self.current_width += self.paste_image_width
def getsize(self, msg: str) -> Tuple[int, int]:
"""
:说明: `getsize`
> 获取文本大小
:参数:
* `msg: str`: 文本内容
:返回:
- `Tuple[int, int]`: 文本大小
"""
return self.font.getsize(msg)
def point(self, pos: Tuple[int, int], fill: Tuple[int, int, int] = (0, 0, 0)):
"""
:说明: `point`
> 绘制单独的像素点
:参数:
* `pos: Tuple[int, int]`: 像素点位置
:可选参数:
* `fill: Tuple[int, int, int] = (0, 0, 0)`: 像素点颜色
"""
self.draw.point(pos, fill=fill)
def ellipse(
self,
pos: Tuple[int, int, int, int],
fill: Optional[Tuple[int, int, int]] = None,
outline: Optional[Tuple[int, int, int]] = None,
width: int = 1,
):
"""
:说明: `ellipse`
> 绘制描边
:参数:
* `pos: Tuple[int, int, int, int]`: 坐标范围[x1, y1, x2, y2]
:可选参数:
* `fill: Optional[Tuple[int, int, int]] = None`: 填充颜色
* `outline: Optional[Tuple[int, int, int]] = None`: 描边颜色
* `width: int = 1`: 描边宽度
"""
self.draw.ellipse(pos, fill, outline, width)
def save(self, path: Union[str, Path]):
"""
:说明: `save`
> 保存图片
:参数:
* `path: Union[str, Path]`: 保存路径
"""
if isinstance(path, Path):
path = path.absolute()
self.mark_image.save(path)
def convert(
self,
image_mode: Literal[
"CMYK", "HSV", "LAB", "RGB", "RGBA", "RGBX", "YCbCr"
] = "RGBA",
):
"""
:说明: `convert`
> 转换图片类型
:可选参数:
* `image_mode:Literal["CMYK", "HSV", "LAB", "RGB", "RGBA", "RGBX", "YCbCr"] = "RGBA"`: 转换后的图片类型
"""
self.mark_image = self.mark_image.convert(image_mode)
def circle(self):
"""
:说明: `circle`
> 转换图片为圆形
"""
self.convert("RGBA")
r2 = min(self.width, self.height)
if self.width != self.height:
self.resize(width=r2, height=r2)
r3 = int(r2 / 2)
imb = Image.new("RGBA", (r3 * 2, r3 * 2), (255, 255, 255, 0))
pim_a = self.mark_image.load() # 像素的访问对象
pim_b = imb.load()
r = float(r2 / 2)
for i in range(r2):
for j in range(r2):
lx = abs(i - r) # 到圆心距离的横坐标
ly = abs(j - r) # 到圆心距离的纵坐标
length = (pow(lx, 2) + pow(ly, 2)) ** 0.5 # 三角函数 半径
if length < r3:
pim_b[i - (r - r3), j - (r - r3)] = pim_a[i, j] # type: ignore
self.mark_image = imb
def resize(self, ratio: float = 0, width: int = 0, height: int = 0):
"""
:说明: `resize`
> 图片缩放
:可选参数:
* `ratio: float = 0`: 缩放比例
* `width: int = 0`: 缩放后的宽度
* `height: int = 0`: 缩放后的高度
:异常:
- `Exception`: 缺少参数
"""
if not width and not height and not ratio:
raise Exception("缺少参数...")
if not width and not height and ratio:
width = int(self.width * ratio)
height = int(self.height * ratio)
self.mark_image = self.mark_image.resize((width, height), Image.ANTIALIAS)
self.width, self.height = self.mark_image.size
self.size = self.width, self.height
self.draw = ImageDraw.Draw(self.mark_image)