from PIL import Image, ImageDraw, ImageFile, ImageFont from typing import Union, Type class PicItem: def __init__(self, pic, label: str): self.pic = Image.open(pic) if isinstance(pic, str) else pic self.label = label class PictureMatrixGenerater(): def __init__(self, background: str, picSize: tuple, itemSize: tuple, margin: int, itemNum: int, fontMargin: int, font: str, fontSize: int): self.backImage = Image.open(background) self.picSize = picSize self.itemSize = itemSize self.margin = margin self.itemNum = itemNum self.fontSize = fontSize self.fontMargin = fontMargin self.actualSize = (itemSize[0], itemSize[1] + fontMargin + int(fontSize * 1.5)) self.font = ImageFont.truetype(font, fontSize) def cutPic(self, img: ImageFile) -> Image: # 等比放小50% img = img.resize((int(img.width * 0.5), int(img.height * 0.5))) left = (img.width - self.itemSize[0])/2 top = (img.height - self.itemSize[1])/2 right = (img.width + self.itemSize[0])/2 bottom = (img.height + self.itemSize[1])/2 return img.crop((left, top, right, bottom)) def drawComponent(self, pic: ImageFile, text: str, startX: int = 0, startY: int = 0) -> Image: components = Image.new('RGBA', self.actualSize, color=(0, 0, 0, 0)) components.paste(self.cutPic(pic), (0, 0)) draw = ImageDraw.Draw(components) text_bbox = draw.textbbox((0, 0), text, font=self.font) # 计算文本宽度(文本框实际宽度) text_width = text_bbox[2] - text_bbox[0] # 计算文本居中 item_center_x = (startX + (self.itemSize[0] / 2)) text_y = startY + self.itemSize[1] + self.fontMargin draw.text((item_center_x - (text_width / 2), text_y), text, fill=(255, 255, 255), font=self.font) return components def generate(self, picList: list[PicItem]) -> Image: backImage = Image.new('RGB', self.picSize, color=(255, 255, 255)) backImage.paste(self.backImage, (0, 0)) num: int = len(picList) # 计算某行的实际宽度 vSize = num * (self.actualSize[0] + num - 1) if num <= 5 else 5 * (self.actualSize[0] + num - 1) # 计算左右边距以确保当前行居中展示 mSize = (self.picSize[0] - vSize) / 2 - self.margin * 2 # 如果只有一行,则计算居中,如果两行,则设置为200 marginTop = int(self.picSize[1] / 2) - int(self.itemSize[1] / 2) if num <= 5 else 200 nextYStart = marginTop + self.actualSize[1] + 50 if num > 5 else -1 nextViewSize = (num % 5) * (self.itemSize[0] + num - 1) if num < 10 else 5 * (self.itemSize[0] + num - 1) nextMarginSize = (self.picSize[0] - nextViewSize) / 2 - self.margin * 2 for idx, img in enumerate(picList): calc_X = int((self.itemSize[0] + self.margin) * idx + mSize) if idx < 5 else int((self.itemSize[0] + self.margin) * (idx % 5) + nextMarginSize) calc_Y = marginTop if idx < 5 else nextYStart # print((calc_X, calc_Y)) component = self.drawComponent(img.pic, img.label, 0, 0) backImage.paste(component, (calc_X, calc_Y), mask=component) return backImage def config(self, picSize: tuple, itemSize: tuple, margin: int, itemNum: int, fontMargin: int, font: str, fontSize: int): self.picSize = picSize self.itemSize = itemSize self.margin = margin self.itemNum = itemNum self.fontSize = fontSize self.fontMargin = fontMargin self.actualSize = (itemSize[0], itemSize[1] + fontMargin + int(fontSize * 1.5)) self.font = ImageFont.truetype(font, fontSize)