From c1c50bbcbec90c3d0e32824767a96d5ef0657a39 Mon Sep 17 00:00:00 2001 From: Starlight-0208 <89368027+Starlight0208@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:47:59 +0800 Subject: [PATCH] Initialize this repository --- .gitignore | 7 ++ MatrixGenerator/__init__.py | 76 +++++++++++++++++++ README.md | 69 +++++++++++++++++ archived/__pycache__/op.cpython-39.pyc | Bin 0 -> 2672 bytes archived/final.py | 92 +++++++++++++++++++++++ archived/op.py | 100 +++++++++++++++++++++++++ archived/test.py | 70 +++++++++++++++++ main.py | 19 +++++ requriement.txt | 1 + 9 files changed, 434 insertions(+) create mode 100644 .gitignore create mode 100644 MatrixGenerator/__init__.py create mode 100644 README.md create mode 100644 archived/__pycache__/op.cpython-39.pyc create mode 100644 archived/final.py create mode 100644 archived/op.py create mode 100644 archived/test.py create mode 100644 main.py create mode 100644 requriement.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd8490d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +pic/* +sucai/* +archived/pic/* +archived/sucai/* +*.jpg +*.png +*.ttf \ No newline at end of file diff --git a/MatrixGenerator/__init__.py b/MatrixGenerator/__init__.py new file mode 100644 index 0000000..04adcc2 --- /dev/null +++ b/MatrixGenerator/__init__.py @@ -0,0 +1,76 @@ +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) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e599796 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +## 项目简介 +我也不知道这个东西是什么,乱搞的。 +## 使用方法 +### 安装依赖 +首先安装一个图像处理包 +```sh +pip install pillow==11.3.0 +``` +或者直接使用`requirement.txt` +```sh +pip install -r requirement.txt +``` +### 项目使用 +`MatrixGenerator`就是模块本身。 +使用的时候直接导入`PictureMatrixGenerater`和`PicItem` +```python +from MatrixGenerator import PictureMatrixGenerater, PicItem +``` +前者是生成器,后者是图片项。 +图片项包含两个属性: +- `pic`:图片路径或者图片对象 +- `label`:图片标签 +图片支持两种类型: +- 图片路径:直接传入图片路径 +- 图片对象:直接传入图片对象(`PIL.Image`) +图片对象导入方式大概是这样的: +```python +from PIL import Image +Image.open("/path/to/open.jpg") +``` +使用`PictureMatrixGenerater`生成图片矩阵,首先需要传入背景图片路径、图片矩阵大小、图片项大小、图片项间距、图片项数量、字体间距、字体路径、字体大小。 +- `background`:背景图片路径(必须是路径,不支持图片对象) +- `picSize`:图片矩阵大小 +- `itemSize`:图片项大小 +- `margin`:图片项间距 +- `itemNum`:图片项数量 +- `fontMargin`:字体间距 +- `font`:字体路径 +- `fontSize`:字体大小 +例如: +```python +generator = PictureMatrixGenerater( + background="./pic/background.jpg", + picSize=(1920, 1080), + itemSize=(200, 200), + margin=20, + itemNum=4, + fontMargin=20, + font="./font/STKAITI.TTF", + fontSize=30 +) +``` +生成图片矩阵之前,需要一个图片项列表: +```python +picList = [ + PicItem("./pic/1.jpg", "1"), + PicItem("./pic/2.jpg", "2"), + PicItem("./pic/3.jpg", "3"), + PicItem("./pic/4.jpg", "4"), +] +``` +这里提供一个小技巧: +```python +picList = [PicItem(pic, str(i)) for i, pic in enumerate(picList)] +``` +之后调用`generate`方法生成图片矩阵: +```python +generator.generate(picList) +``` \ No newline at end of file diff --git a/archived/__pycache__/op.cpython-39.pyc b/archived/__pycache__/op.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01af5b677a344174d410e724a21a0494f72b2c3b GIT binary patch literal 2672 zcma)7-ESMm5#Qb0J02aYH}I7g5k7I$$r!R=%@u z70y9*mvaUZggWy~sFFJSL3yzd_62&nbi&U)_VE|1BA5>rU>Ruq1lK46Jnb|rVB8CqorM=qc64HxYzfPkvv(b4an%j1vX}Q+Fs|l;I^5V;QZrD8 zadioKUWR2ghMX-^Ua?+o4l{g;!-0eF7>n*m#%kj5P0pD1(8b{`0&4ohX@W8VD%m>z@cH)}+Y9xj7J68EC)Ay| z`}n6Tjg_#QG*<35-UAi&J9T4P+qwEu(r<>*p^J+S1;N(@-{MmfnGu!_W zA3bDFaoojUk|LyRj~Sk_4WT$)33luZ#+X9BgBBaYNF^vW%9&w1WbL3t9!tcD4Yn)U zI43qkiNu}Ul&2zfQrVLGM5}v$cRFuE+|21k%}g5D zGMSg4OY@82_klix7EMh5@k3cZ=Kj|>F>He?ZiEubdFt%QRPG_KDP}=QY98b6c_}ir z_v!L91)S$`{YdJkInq~I zns14et2}xuP;;abb1`y1lOMeO;~&-JAQd(r$8f+iw;8u%eIQQMD3nyHgS1&&%H4r; z_$_|)wIetXXcx8H`X!XqikiwbbGopZ6~d?!KnsKKWccE*NOe))}MfpyGJgspeQLJFg}5Aflmm0aWu>I_sQHrHgye0dJ$o} zTwlUkS!(y=ttgDKj_Vt}bxmHj{b4gN)sWCmm^|8^sGm0Mb=Nboxz;B?&p-R!H_!g~ z%^#lp^Dmzre)-jDMB($_|LXa(Kk2Kq>veMdq9fd~b}E0hS2GbOne+a;Ke0Knv}-C^ zP4`5W!+YB|OU!S@-*$%%?0^JzZ-0EJ2;t`9}A_Jnn%_S?08%o8{V3VpOCt z@3_?cbdd5v59bG-Kg6~zbTL5>s5wnmr18%p*tE7|G#li6u(qf1VzA|sIf>Dhbp0~z zpI-}`kL(4lmuN2I9!8J{+!=Ie=t@yrgB!zZhV5npo7R3GWclNg$#^T1N%$#VF|ZXG zTfv~0>5#X{X|WxLs-d72_SJg?&G%ZWmDe1ez66n`CWq#{XWkzW%&pAuuz7mK36 QeOFRxI&)4Di*p+BfAnREIRF3v literal 0 HcmV?d00001 diff --git a/archived/final.py b/archived/final.py new file mode 100644 index 0000000..6277285 --- /dev/null +++ b/archived/final.py @@ -0,0 +1,92 @@ +import os +from PIL import Image, ImageDraw, ImageFile, ImageFont + +class PicItem: + def __init__(self, pic: ImageFile, label: str): + self.pic = 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) + +generator = PictureMatrixGenerater( + background="./pic/background.jpg", + picSize=(1920, 1080), + itemSize=(320, 320), + margin=50, + itemNum=10, + fontMargin=20, + font="./font/HarmonyOS_Sans_SC_Medium.ttf", + fontSize=26 +) +picList = [PicItem(Image.open('./sucai/' + i), i) for i in os.listdir("./sucai/")] +print(picList) +Sol = generator.generate(picList) +# 写入结果 +with open('./finalc.png', 'wb') as f: + Sol.save(f) \ No newline at end of file diff --git a/archived/op.py b/archived/op.py new file mode 100644 index 0000000..92866d2 --- /dev/null +++ b/archived/op.py @@ -0,0 +1,100 @@ +import os +from PIL import Image, ImageDraw, ImageFile, ImageFont + +picSize = (1920, 1080) +itemSize = (320, 320) +margin = 50 +itemNum = 10 +fontSize = 26 +fontMargin = 20 +actualSize = (itemSize[0], itemSize[1] + fontMargin + int(fontSize * 1.5)) +font = ImageFont.truetype("./font/HarmonyOS_Sans_SC_Medium.ttf", fontSize) + +num = len(os.listdir('./sucai')) +# num = 10 +# 计算某行的实际宽度 +vSize = num * (itemSize[0] + num - 1) if num <= 5 else 5 * (itemSize[0] + num - 1) +# 计算左右边距以确保当前行居中展示 +mSize = (picSize[0] - vSize) / 2 - margin * 2 +# 如果只有一行,则计算居中,如果两行,则设置为200 +marginTop = int(picSize[1] / 2) - int(itemSize[1] / 2) if num <= 5 else 200 +nextYStart = marginTop + actualSize[1] + 50 if num > 5 else -1 +nextViewSize = (num % 5) * (itemSize[0] + num - 1) if num < 10 else 5 * (itemSize[0] + num - 1) +nextMarginSize = (picSize[0] - nextViewSize) / 2 - margin * 2 +print(nextMarginSize) +# colNum = int(num / 5) + 1 +# # print(colNum) + +def cutPic(img: ImageFile) -> Image: + # 等比放小50% + img = img.resize((int(img.width * 0.5), int(img.height * 0.5))) + left = (img.width - itemSize[0])/2 + top = (img.height - itemSize[1])/2 + right = (img.width + itemSize[0])/2 + bottom = (img.height + itemSize[1])/2 + return img.crop((left, top, right, bottom)) + + +def drawComponent(pic: ImageFile, text: str, startX: int = 0, startY: int = 0) -> Image: + components = Image.new('RGBA', actualSize, color=(0, 0, 0, 0)) + components.paste(cutPic(pic), (0, 0)) + draw = ImageDraw.Draw(components) + text_bbox = draw.textbbox((0, 0), text, font=font) + # 计算文本宽度(文本框实际宽度) + text_width = text_bbox[2] - text_bbox[0] + # 计算文本居中 + item_center_x = (startX + (itemSize[0] / 2)) + text_y = startY + itemSize[1] + fontMargin + draw.text((item_center_x - (text_width / 2), text_y), text, fill=(255, 255, 255), font=font) + return components + + + +backImage = Image.new('RGB', picSize, color=(255, 255, 255)) +backImage.paste(Image.open('./pic/luoxiaohei.jpg'), (0, 0)) +# backImage.paste(drawComponent(Image.open('./sucai/1.jpg'), "我真的很长很长很长的文本", 0, 0), (200, 200)) +for idx, img in enumerate(os.listdir('./sucai')): + calc_X = int((itemSize[0] + margin) * idx + mSize) if idx < 5 else int((itemSize[0] + margin) * (idx % 5) + nextMarginSize) + calc_Y = marginTop if idx < 5 else nextYStart + print((calc_X, calc_Y)) + component = drawComponent(Image.open('./sucai/' + img), img, 0, 0) + backImage.paste(component, (calc_X, calc_Y), mask=component) + + +# 写入结果 +with open('./final.png', 'wb') as f: + backImage.save(f) + +exit(0) +for idx, file in enumerate(os.listdir('./sucai')): + # 将图片修剪为指定大小 + img = Image.open('./sucai/' + file) + # img = img.resize(itemSize) + # img.thumbnail(itemSize) + # 再居中裁剪 + img = cutPic(img) + # 粘贴到背景图 + backImage.paste(img, (int((itemSize[0] + margin) * idx + mSize) , marginTop)) + # 在图片下面嵌字 + draw = ImageDraw.Draw(backImage) + font = ImageFont.load_default() + # 使用24px大小的字体 + + # 绘制文本 + # 计算文本宽度 + text = "我真的很长很长很长的文本" + text_bbox = draw.textbbox((0, 0), text, font=font) + text_width = text_bbox[2] - text_bbox[0] + + # 计算图片项的中心位置 + item_center_x = ((itemSize[0] + margin) * idx + mSize) + (itemSize[0] / 2) + + # 计算文本的起始x坐标,使其居中 + text_x = item_center_x - (text_width / 2) + text_y = marginTop + itemSize[1] + fontMargin + + draw.text((text_x, text_y), text, fill=(255, 255, 255), font=font) + +# 写入结果 +with open('./luoxiaohei.jpg', 'wb') as f: + backImage.save(f) \ No newline at end of file diff --git a/archived/test.py b/archived/test.py new file mode 100644 index 0000000..52b8f43 --- /dev/null +++ b/archived/test.py @@ -0,0 +1,70 @@ +from PIL import Image, ImageDraw, ImageFont +import math + +def generate_image_grid(images, titles, output_path, small_width=200, small_height=200, caption_height=30, max_per_row=5): + num_images = len(images) + + # 参数检查 + if num_images > 10: + raise ValueError("最多十张图片") + if num_images != len(titles): + raise ValueError("图片和标题数量必须一致") + + # 计算行数 + rows = math.ceil(num_images / max_per_row) + + # 创建大图 + total_width = max_per_row * small_width + total_height = rows * (small_height + caption_height) + grid_image = Image.new('RGB', (total_width, total_height), color=(255, 255, 255)) + draw = ImageDraw.Draw(grid_image) + + # 加载字体(尝试使用系统字体,否则使用默认字体) + try: + font = ImageFont.truetype("arial.ttf", 16) + except: + font = ImageFont.load_default() + + # 拼接图片并添加标题 + for i in range(num_images): + row = i // max_per_row + col = i % max_per_row + x = col * small_width + y = row * (small_height + caption_height) + + # 调整图片大小并粘贴 + img = images[i].resize((small_width, small_height)) + grid_image.paste(img, (x, y)) + + # 绘制标题 + caption = titles[i] + text_width, text_height = font.getsize(caption) + text_x = x + (small_width - text_width) // 2 + text_y = y + small_height + (caption_height - text_height) // 2 + draw.text((text_x, text_y), caption, fill=(0, 0, 0), font=font) + + # 保存结果 + grid_image.save(output_path) + + +def create_test_images(n, size=(200, 200)): + colors = ['red', 'green', 'blue', 'yellow', 'cyan', + 'magenta', 'black', 'white', 'orange', 'purple'] + images = [] + for i in range(n): + img = Image.new('RGB', size, color=colors[i % len(colors)]) + draw = ImageDraw.Draw(img) + draw.text((10, 10), f"Image {i+1}", fill=(255, 255, 255), font=ImageFont.load_default()) + images.append(img) + return images + +# 示例调用 +if __name__ == "__main__": + # 生成 7 张测试图片 + test_images = create_test_images(7) + + # 设置标题 + titles = [f"Title {i+1}" for i in range(7)] + + # 拼接图片并保存 + generate_image_grid(test_images, titles, 'grid_output.png') diff --git a/main.py b/main.py new file mode 100644 index 0000000..909f4d2 --- /dev/null +++ b/main.py @@ -0,0 +1,19 @@ +import os +from PIL import Image +from MatrixGenerator import PicItem, PictureMatrixGenerater + +generator = PictureMatrixGenerater( + background="./pic/background.jpg", + picSize=(1920, 1080), + itemSize=(320, 320), + margin=50, + itemNum=10, + fontMargin=20, + font="./font/HarmonyOS_Sans_SC_Medium.ttf", + fontSize=26 +) +picList = [PicItem(Image.open('./sucai/' + i), i) for i in os.listdir("./sucai/")] +Sol = generator.generate(picList) +# 写入结果 +with open('./finalc.png', 'wb') as f: + Sol.save(f) \ No newline at end of file diff --git a/requriement.txt b/requriement.txt new file mode 100644 index 0000000..bb86348 --- /dev/null +++ b/requriement.txt @@ -0,0 +1 @@ +pillow==11.3.0 \ No newline at end of file