Python PIL(pillow) 生成瀑布流图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
from PIL import Image, ImageDraw, ImageFont
import httpx
from typing import List
from io import BytesIO
from pathlib import Path
from botoy import logger
import time
from tenacity import Retrying, RetryError, stop_after_attempt

curFileDir = Path(__file__).parent # 当前文件路径

cookies = {
"ipb_member_id": "",
"ipb_pass_hash": "",
"yay": "louder",
"igneous": ""
}

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62",
"Referer": "https://exhentai.org/",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"

}


class Draw:
def __init__(self, image_urls, texts):
self.ONE_LINE_MAX = 3
self.GAP_X = 15
self.pic_with_text_gap = 8 # 图片与文本的间隔
self.font = ImageFont.truetype(str(curFileDir / "files" / "LXGWWenKaiMono-Regular.ttf"), 17)

# ---------------------------
self.image_urls: List[str] = image_urls
self.texts: List[str] = texts
# ---------------------------
self.images: List[Image.Image] = []
self.processed_texts: List[str] = []
self.text_sizes: List[tuple] = []
self.pic_coordinates: List[tuple] = [] # 每张图的对应坐标
self.text_coordinates: List[tuple] = [] # 每张图下方的文字的对应坐标

def download_all_pic(self) -> bool:
"""
下载所有封面
:return:
"""
print("开始下载")
with httpx.Client() as client:
for url in self.image_urls:
try:
for attempt in Retrying(stop=stop_after_attempt(3)):
with attempt:
pic_bytes = client.get(url, headers=headers, cookies=cookies).content
except RetryError:
logger.error(f"ex:下载{url}错误")
self.images.append(Image.open(curFileDir / "files" / "error_page.jpg"))
continue
self.images.append(Image.open(BytesIO(pic_bytes)))
print("下载完成")
print(len(self.images))
if len(self.images) == len(self.image_urls):
return True
return False

def build_coordinate(self) -> (List[tuple], List[tuple]):
"""
生成瀑布流坐标
:param pics: 图片列表
:param self.text_sizes: 文本的实际占用大小
:return: 图片坐标列表,文本坐标列表
"""
# pic_coordinates: List[tuple] = []
# text_coordinates: List[tuple] = []
print(len(self.images))
for i in range(len(self.images)):
if i < self.ONE_LINE_MAX: # 第一行
coordinate_x = self.GAP_X + \
(self.pic_coordinates[i - 1][0] + self.images[i - 1].size[0] if i != 0 else 0)
# 固定偏移+左边图片的坐标+左边图片的宽度
coordinate_y = 20
elif i != 0 and i % self.ONE_LINE_MAX == 0: # 换行
print("换行")
coordinate_x = self.GAP_X
coordinate_y = self.text_sizes[i - self.ONE_LINE_MAX][1] + 2 * self.pic_with_text_gap + \
self.pic_coordinates[i - self.ONE_LINE_MAX][1] + \
self.images[i - self.ONE_LINE_MAX].size[1]
# 向下的固定偏移+上方图片的Y坐标+上方图片的长度
else:
coordinate_x = self.pic_coordinates[i - self.ONE_LINE_MAX][0] # 和上方图片的X坐标保持一致
coordinate_y = self.text_sizes[i - self.ONE_LINE_MAX][1] + 2 * self.pic_with_text_gap + \
self.pic_coordinates[i - self.ONE_LINE_MAX][1] + \
self.images[i - self.ONE_LINE_MAX].size[1]
# 向下的固定偏移+上方图片的Y坐标+上方图片的长度
print(f"NOW :{i}")
self.pic_coordinates.append((coordinate_x, coordinate_y))
self.text_coordinates.append((coordinate_x, coordinate_y + self.pic_with_text_gap + self.images[i].size[1]))
# return pic_coordinates, text_coordinates

def process_text(self):
"""
处理长文本
:param pics: 文本对应上方的图片列表
:param text_list: 待处理的文本
:return: 处理完的文本
"""
# text_list_finally = []
for i in range(len(self.texts)):
text_tmp: str = ""
text_finally: str = ""
# text_height_finally: int = 0
for char in self.texts[i]:
text_tmp += char
text_width, text_height = self.font.getsize(text_tmp)
if text_width > self.images[i].size[0]:
# print(text_width, pics[i].size[0])
text_finally += (text_tmp[:-1] if text_finally == "" else text_tmp[1:-1]) + "\n" + text_tmp[-1]
text_tmp = text_tmp[-1]
if text_tmp[1:] != "":
text_finally += text_tmp[1:]
self.processed_texts.append(text_finally)
# return text_list_finally

def get_text_sizes(self):
"""
获取渲染后的文本实际大小
:param texts: 文本
:return: 渲染后文本的实际size
"""
# text_size = []
for text in self.processed_texts:
img = Image.new("RGB", (1, 1))
draw = ImageDraw.Draw(img)
size = draw.textsize(text, self.font)
self.text_sizes.append(size)
# print(self.text_sizes)
# return text_size

def get_background_size(self):
# X_MAX = (self.ONE_LINE_MAX + 1) * self.GAP_X + self.ONE_LINE_MAX * 250 # 250是固定图片宽度
X_MAX = 0
Y_MAX = 0
for i in range(0, len(self.images), self.ONE_LINE_MAX):
imgs = self.images[i:i + self.ONE_LINE_MAX]
X_tmp = sum([img.size[0] for img in imgs]) + (len(imgs) + 1) * self.GAP_X
if X_tmp > X_MAX:
X_MAX = X_tmp
for line in range(self.ONE_LINE_MAX):
y_tmp = 20
for i in range(line, len(self.images), 3):
print(i)
y_tmp += (self.images[i].size[1] + self.text_sizes[i][1] + self.pic_with_text_gap * 2)
if y_tmp > Y_MAX:
Y_MAX = y_tmp
return X_MAX, Y_MAX

def main(self):
if not self.download_all_pic():
return
start_time = time.time()
self.process_text()
self.get_text_sizes()

bg_x, bg_y = self.get_background_size()
background = Image.new('RGB', (bg_x, bg_y), (255, 255, 255))
self.build_coordinate()
for i in range(len(self.images)):
background.paste(self.images[i], (self.pic_coordinates[i][0], self.pic_coordinates[i][1]))
draw = ImageDraw.Draw(background)
for i in range(len(self.images)):
draw.text(self.text_coordinates[i], text=self.processed_texts[i], font=self.font, fill='black')
print(time.time() - start_time)
background.show()
with BytesIO() as bf:
background.save(bf, format="JPEG", quality=80)
return bf
# background.save("res.jpg")


if __name__ == '__main__':
urls = [
'https://exhentai.org/t/69/fb/69fb6d072e74f5d86b566a028fed07a5259d64fb-2683152-4400-3500-jpg_250.jpg',
'https://exhentai.org/t/c0/9b/c09b8c2fce0938ff0e2ca5917fcb4e9d58bfb536-215016-828-1280-jpg_250.jpg',
'https://exhentai.org/t/e8/3f/e83f75ed0e835b5f504eacf5e614e00432418c75-494024-872-1088-jpg_250.jpg',
'https://exhentai.org/t/c3/a5/c3a525882e5fa6b5ada3ba4a595d4f4c5c418f21-367626-898-784-jpg_250.jpg',
'https://exhentai.org/t/12/91/1291faa5d37f92be38cca6082f83ab55ba9f7563-1546320-1359-1920-jpg_250.jpg',
'https://exhentai.org/t/97/c8/97c8e1719c92aa46768f44f7e28512456a3382b6-1184155-1063-1500-jpg_250.jpg',
'https://exhentai.org/t/51/c8/51c84ef796e1ac91a2f13c8d1e8b7e6a1d96475a-553270-1200-675-jpg_250.jpg',
'https://exhentai.org/t/17/d9/17d9977eac6cd3717c750051b7e955ee9410b19e-394252-1200-675-jpg_250.jpg',
'https://exhentai.org/t/89/59/8959ece7934f525dba6fa2fc99bb54c383b7826e-313170-1200-675-jpg_250.jpg',
'https://exhentai.org/t/83/dc/83dcb40fa317f6aaa8be36c1162e3a3c3dedf00a-319202-1200-675-jpg_250.jpg',
'https://exhentai.org/t/77/23/7723048a985fa419d07d89d605eae9000ca04eb4-334997-1200-675-jpg_250.jpg',
'https://exhentai.org/t/9d/6c/9d6c5f825d44bae12303da239a7c5e6c79ce4428-459684-1200-675-jpg_250.jpg',
'https://exhentai.org/t/f4/66/f46618de2c1a1db184a84db8544180dc09ca1163-346335-1200-675-jpg_250.jpg',
'https://exhentai.org/t/5e/47/5e477c56f5476ce770cc2bdebdd911817cb11c3b-541571-1200-675-jpg_250.jpg',
'https://exhentai.org/t/ad/9c/ad9c1518a8f772f758cfd8c4f82c3cabf78384ca-386148-1200-675-jpg_250.jpg',
'https://exhentai.org/t/57/e3/57e3a18edee853e8e7896869acbc017857c3c78b-337336-1200-675-jpg_250.jpg',
'https://exhentai.org/t/85/88/85884214c4e2da1fe535aba2f7ceb0a9099b2cf0-426860-1200-675-jpg_250.jpg',
'https://exhentai.org/t/77/1a/771a4c3d41aa4c402ce1bbb05a2b8bb2b2c76104-494156-1200-675-jpg_250.jpg',
'https://exhentai.org/t/41/ab/41ab7891073e8fb07ab6fe73a3e8430d228ad025-556780-1200-675-jpg_250.jpg',
'https://exhentai.org/t/1a/12/1a12bfc5c1270c3ec05e52d7dd168817057a2100-295172-1200-675-jpg_250.jpg',
'https://exhentai.org/t/4b/4a/4b4ab01305648fcbcde875a60e210a3278f2a85e-4401307-4961-7016-jpg_250.jpg',
'https://exhentai.org/t/c5/06/c5063a6d896a56fc7eb7ba33e10d0f722883546e-2834338-1308-1864-png_250.jpg',
'https://exhentai.org/t/08/a7/08a79fc3aa9702f7d1b8029547b94f6b2724a521-383247-800-1159-jpg_250.jpg',
'https://exhentai.org/t/f4/63/f46387049afcecb47375278afcb69571b0f73e17-214700-800-1252-jpg_250.jpg',
'https://exhentai.org/t/a8/36/a8365d1cf7b13d3d96636b6774cb9424b9ef28a8-287840-729-980-jpg_250.jpg']

txts = ['[FANBOX] dangonesan [2021-12-24]', '[phrannd] Overstay(ongoing) [Chinese] [lostecho个人汉化]',
'[tokunocin (Tokuno Yuika)] Mousou Shoujo Kikuri-chan 想治治妹妹这个臭丫头的样子!(妄想少女篇) [Chinese] [无糖·漫画组]',
'[Shimahara] Uchi no Neko ga Onnanoko de Kawaii 我家的猫猫是可爱的女孩子! [Chinese] [无糖·漫画组][绿茶汉化组]',
'[Aomushi] Petted Girl 被饲养的女孩 (COMIC Shitsurakuten 2021-08) [夜空下的萝莉x真不可视汉化组]',
'[Aomushi] Pet Girl 饲养女孩 (COMIC Shitsurakuten 2021-07) [夜空下的萝莉x真不可视汉化组]', 'Spirit sacrificeⅠ—— EP22',
'Spirit sacrificeⅠ—— EP21', 'Spirit sacrificeⅠ—— EP20', 'Spirit sacrificeⅠ—— EP19',
'Spirit sacrificeⅠ—— EP18',
'Spirit sacrificeⅠ—— EP17', 'Spirit sacrificeⅠ—— EP16',
'Evil Metropolis Ⅳ —— Return to Golden City 6',
'Evil Metropolis Ⅳ —— Return to Golden City 5', 'Evil Metropolis Ⅳ —— Return to Golden City 4',
'Evil Metropolis Ⅳ —— Return to Golden City 3', 'Evil Metropolis Ⅳ —— Return to Golden City 2',
'Evil Metropolis Ⅲ —— Penalty Golden City 12', 'Evil Metropolis Ⅳ —— Return to Golden City 1',
'[安堂流] お母さんいただきます。2 連載 P1-35 [空気系☆漢化]',
'[Unmei no Ikasumi (Harusame)] Suwarete Dame nara Suttemiro! (Touhou Project) [Chinese] [現場目睹全過程的古明地戀個人漢化] [Digital]',
'Chinese 神契 幻奇谭(刘冲L.Dart)02', 'Chinese 神契 幻奇谭(刘冲L.Dart)01',
'[Pixiv] ebiblue (379606) [Chinese] [白杨汉化组]']

test = Draw(urls, txts)
pic_bytes = test.main()

Python PIL(pillow) 生成瀑布流图片
https://2333.world/_posts/2022_01_13/python-pilpillow-生成瀑布流图片/
作者
Yuban10703
发布于
2022年1月13日
许可协议