fastapi-uvicorn / src /pyscripts /img_process.py
IsidoreSong's picture
Update src/pyscripts/img_process.py
b9df19e verified
from PIL import Image, ImageDraw, ImageFont
import re
import os
import io
from dataclasses import dataclass
import requests
from pypinyin import pinyin, Style
import rembg
import math
font_path_fangzhenghei = "src/assets/FangZhengHeiTiJianTi-1.ttf"
font_path_zihun59 = "src/assets/字魂59号-创粗黑.ttf"
font_path_notosans = "src/assets/NotoSansHans-Regular.otf"
font_path_feihuasong = "src/assets/FeiHuaSongTi-2.ttf"
font_path_wendingPLjianbaosong = "src/assets/gbsn00lp-2.ttf"
font_path_SourceHanSans = "src/assets/SourceHanSansCN-VF-2.otf"
font_path_notosansM = "src/assets/NotoSansHans-Medium.otf"
font_path_notosansB = "src/assets/NotoSansHans-Bold.otf"
font_path_notosansL = "src/assets/NotoSansHans-Light.otf"
font_path_pingfang = "src/assets/PingFang.ttc"
font_path_pingfangM = "src/assets/pingfang-M.ttf"
@dataclass
class text_info:
text: str
font_size: int
position: list
color: tuple = (0, 0, 0)
max_height: int = 1000
max_width: int = 0
text_width: int = 0
text_height: int = 0
line_space: int = 10
font_path: str = None
em_font_path: str = None
def __post_init__(self):
self.wrap_text()
self.get_text_width()
self.get_text_height()
def wrap_text(self):
"""
resize to fix max_height and max_width
"""
words = re.findall(r"[a-zA-Z\d]+|[\u4e00-\u9fa5]|\n|.", self.text)
self.font = ImageFont.truetype(self.font_path, self.font_size)
lines = []
current_line = ""
for word in words:
if word == "\n":
lines.append(current_line.strip())
current_line = ""
continue
test_line = current_line + word
test_bbox = self.font.getbbox(test_line, anchor="la")
test_width = test_bbox[2] - test_bbox[0] # 计算宽度
if test_width <= self.max_width and "\n" not in test_line:
current_line = test_line
else:
lines.append(current_line.strip())
current_line = word
if current_line:
lines.append(current_line.strip())
self.lines = lines
test_bbox = self.font.getbbox("\n".join(lines), anchor="la")
self.text_height = test_bbox[3] - test_bbox[1]
self.text_width = test_bbox[2] - test_bbox[0] # 计算宽度
# print(lines, self.text_height, self.text_width)
key_lines = [line for line in lines if line.startswith("【】")]
if len(key_lines) > 0:
key_line = max(key_lines, key=len)
else:
key_line = False
if self.text_height > self.max_height or self.text_width > self.max_width or (key_line and not re.search("^" + re.escape(key_line) + "$", self.text, re.M)):
self.font_size -= 2
self.wrap_text()
def get_text_width(self):
text_bbox = self.font.getbbox(self.text, anchor="la")
self.text_width = text_bbox[2] - text_bbox[0]
def get_text_height(self):
text_bbox = self.font.getbbox(self.text, anchor="la")
self.text_height = text_bbox[3] - text_bbox[1]
def write_text_on_image(img, text_info):
# 创建一个可以在给定图像上绘图的对象
draw = ImageDraw.Draw(img)
# 加载字体,如果没有提供字体路径,则使用系统默认字体
font = ImageFont.load_default().font_variant(size=text_info.font_size)
em_font = ImageFont.load_default().font_variant(size=text_info.font_size)
if text_info.font_path:
font = ImageFont.truetype(text_info.font_path, text_info.font_size)
if text_info.em_font_path:
em_font = ImageFont.truetype(text_info.em_font_path, text_info.font_size)
# 在图片上写入文字
# draw.text(text_info.position, text_info.text, fill=text_info.color, font=font)
y_text = text_info.position[1]
wrapped_lines = text_info.lines
text_bbox_chinese = font.getbbox("中文行高")
for line in wrapped_lines:
text_bbox = font.getbbox(line)
line_width = text_bbox[2] - text_bbox[0] # 获取宽度
line_height = max(text_bbox[3] - text_bbox[1], text_bbox_chinese[3] - text_bbox_chinese[1])
# print(text_bbox[3] - text_bbox[1], text_bbox_chinese[3] - text_bbox_chinese[1], text_info.font_size)
if re.search(r"^【.*】.*?" + re.escape(line), text_info.text, re.M) or re.match(r"^【.*】.*?$", line):
draw.text((text_info.position[0], y_text), line.replace("【】", ""), fill=text_info.color, font=em_font)
else:
draw.text((text_info.position[0], y_text), line, fill=text_info.color, font=font)
# 更新y坐标以添加行间距并为下一行做准备
y_text += line_height # + text_info.line_space
return img, y_text
def embed_image(background, foreground, offset_y=0, offset_x=None, standard_fg_size=None):
"""
粘贴图片
Args:
background (_type_): 背景图片
foreground (_type_): 前景图片
offset_y (int, optional): 纵向偏移. Defaults to 1500.
standard_fg_size (tuple, optional): 前景缩放大小. Defaults to (1600, 450).
Returns:
_type_: _description_
"""
# 获取背景图片的尺寸
if standard_fg_size is None:
standard_fg_size = foreground.size
standard_fg_width, standard_fg_height = standard_fg_size
bg_width, bg_height = background.size
fg_width, fg_height = foreground.size
fg_resize_percent = max(fg_width / standard_fg_width, fg_height / standard_fg_height)
# print((fg_width / standard_fg_width, fg_height / standard_fg_height))
foreground = foreground.resize((int(fg_width // fg_resize_percent), int(fg_height // fg_resize_percent)))
fg_width, fg_height = foreground.size
if offset_x is None:
offset_x = (bg_width - fg_width) // 2 # 横轴偏移使小图居中
background.paste(foreground, (int(offset_x), int(offset_y)), foreground)
return background
img_path_dict = {
"old": "src/assets/InvitationTemplateCompressed.pdf",
"common": "src/assets/旧空白邀请函图片.jpg",
"观礼": "src/assets/带名字.jpg",
"演讲": "src/assets/带名字和会.jpg",
"致辞": "src/assets/带名字和会.jpg",
}
font_path = "src/assets/ZhouZiSongTi7000Zi-2.otf"
font_path = "src/assets/FangZhengHeiTiJianTi-1.ttf"
def writeInvitationPDF(person, debug=False):
"旧空白邀请函图片.jpg"
font_size = 40
resized_name_font_size = font_size * 5 / len(person.name + person.title)
resized_conference_font_size = font_size * 9 / len(person.conference)
img_path = img_path_dict[person.category]
image = Image.open(img_path)
draw = ImageDraw.Draw(image)
text_color = (255, 255, 255) # 白色 RGB
draw.text(
(182, 150 + max(0, font_size - resized_name_font_size)),
person.name + person.title,
fill=text_color,
font=ImageFont.truetype(font_path, min(font_size, resized_name_font_size)),
)
draw.text(
(102, 532 + max(0, font_size - resized_conference_font_size)),
person.conference,
fill=text_color,
font=ImageFont.truetype(font_path, min(font_size, resized_conference_font_size)),
)
imgStream = io.BytesIO()
# image.save(imgStream)
# doc.save(pdfStream)
# doc.close()
if debug:
image.save("output/image_with_text.jpg")
return imgStream
def write_text(
text, font_path, image, H_PADDING_RATION, font_size, max_height=None, color=(255, 255, 255), em_font_path=None, alignment="left",
start_y=0, start_x=0, max_width=None, part_width=10000
):
bg_width = image.size[0]
if max_width is None:
max_width = bg_width * (1 - 2 * H_PADDING_RATION)
if max_height is None:
max_height = font_size * len(text.split("\n"))
text = text_info(
text=text,
font_size=font_size,
position=[start_x, start_y],
color=color,
max_width=max_width,
max_height=max_height,
font_path=font_path,
em_font_path=em_font_path,
)
if alignment == "right":
text.position[0] = bg_width * (1 - H_PADDING_RATION) - text.text_width
elif alignment == "medium":
text.position[0] = (min(part_width, bg_width) - text.text_width) // 2 + start_x
elif alignment == "left":
text.position[0] = bg_width * H_PADDING_RATION
print(text)
image, next_height = write_text_on_image(image, text)
return text, image, next_height
def make_exhibitor_poster(logo_link, slogan, content):
background = Image.open("src/assets/展商海报白板.jpg")
H_PADDING_RATION = 0.08
V_PADDING = 25
V_START = 950
MAX_HEIGHT = 520
res = requests.get(logo_link)
foreground = Image.open(io.BytesIO(res.content))
# foreground = Image.open(logo_path_list[FOREGROUND_ID])
background = embed_image(background, foreground, offset_y=750, standard_fg_size=(1600 // 2, 450 // 2))
text = slogan
font_size = 110 // 2
font_path = font_path_fangzhenghei
slogan, background, next_height = write_text(
text,
font_path,
background,
H_PADDING_RATION,
font_size,
color=(0, 0, 0),
alignment="medium",
start_y=V_START,
)
content, background, next_height = write_text(
content,
font_path_SourceHanSans,
background,
H_PADDING_RATION,
100,
max_height=MAX_HEIGHT,
color=(0, 0, 0),
em_font_path=font_path_fangzhenghei,
alignment="left",
start_y=next_height + V_PADDING,
)
img_stream = io.BytesIO()
background.save(img_stream, format="JPEG")
img_stream.seek(0)
background.save("output/展商海报.jpg")
return img_stream
def cut_image_blank(image):
# 获取图片的宽度和高度
width, height = image.size
# 初始化边界框
left = width
top = height
right = 0
bottom = 0
# 遍历图片的每个像素,找到非透明的区域
for x in range(width):
for y in range(height):
pixel = image.getpixel((x, y))
if pixel[3] != 0: # 检查透明度通道
if x < left:
left = x
if x > right:
right = x
if y < top:
top = y
if y > bottom:
bottom = y
if right < left or bottom < top:
return image
cropped_image = image.crop((left, top, right + 1, bottom + 1))
return cropped_image
def rm_img_bg(image):
image = rembg.remove(image)
# image = cut_image_blank(image)
return image
def compose_guest_poster(name, name_pinyin, guest_photo, titles):
background = Image.open("src/assets/Guest04.jpg").convert("RGBA")
background = background.resize((1080 * 2, 2400 * 2))
title_list = titles.split("\n")
# guest = Image.open("src/assets/Guest.png").convert("RGBA")
# higher_mask = Image.open("src/assets/Guest01.png").convert("RGBA")
H_PADDING_RATION = 0.08
V_PADDING = 5
V_START = 3020
bg_width, bg_height = background.size
background = embed_image(background, guest_photo, offset_y=1300, standard_fg_size=(2160, 2600))
lower_mask = Image.open("src/assets/Guest03.png").convert("RGBA")
background = embed_image(background, lower_mask, standard_fg_size=background.size)
higher_mask = Image.open("src/assets/Guest02.png").convert("RGBA")
background = embed_image(background, higher_mask, standard_fg_size=background.size)
seal = Image.open("src/assets/Guest01.png").convert("RGBA")
background = embed_image(background, seal, standard_fg_size=background.size)
# background = background.convert("RGB")
font_size = 210
font_path = font_path_zihun59
text = name
name, background, next_height = write_text(
text,
font_path,
background,
H_PADDING_RATION,
font_size,
alignment="right",
start_y=V_START,
)
next_height += 50
font_size = 100
font_path = font_path_notosans
if name_pinyin == "":
pinyin_list = pinyin(name.text, style=Style.NORMAL)
name_pinyin = (pinyin_list[0][0] + " " + "".join([item[0] for item in pinyin_list[1:]])).title()
text = name_pinyin
name_pinyin, background, next_height = write_text(
text,
font_path,
background,
H_PADDING_RATION,
font_size,
alignment="right",
start_y=next_height + V_PADDING,
)
next_height += 100
font_size = 70
font_path = font_path_notosans
for title in title_list:
text = title
title, background, next_height = write_text(
text,
font_path,
background,
H_PADDING_RATION,
font_size,
alignment="right",
start_y=next_height + V_PADDING,
)
background = background.convert("RGB")
img_stream = io.BytesIO()
background.save(img_stream, format="JPEG")
img_stream.seek(0)
if os.path.exists("src/assets/output"):
background.save("src/assets/output/嘉宾海报.jpg")
return img_stream
def make_guest_poster(name, name_pinyin, photo_link, original_photo_link, titles):
if photo_link != "":
res = requests.get(photo_link)
guest_photo = Image.open(io.BytesIO(res.content)).convert("RGBA")
elif original_photo_link != "":
res = requests.get(original_photo_link)
original_guest_photo = Image.open(io.BytesIO(res.content))
guest_photo = rm_img_bg(original_guest_photo)
if os.path.exists("src/assets/output"):
guest_photo.save("src/assets/output/rmbg.png")
else:
return None
img_stream = compose_guest_poster(name, name_pinyin, guest_photo, titles)
return img_stream
def crop_round_img(image, circle_center, circle_radius):
mask = Image.new("L", image.size, 0)
draw = ImageDraw.Draw(mask)
draw.ellipse((circle_center[0] - circle_radius, circle_center[1] - circle_radius, circle_center[0] + circle_radius, circle_center[1] + circle_radius), fill=255)
output_image = Image.new("RGBA", image.size)
output_image.paste(image, (0, 0), mask)
output_image = output_image.crop(
(circle_center[0] - circle_radius, circle_center[1] - circle_radius, circle_center[0] + circle_radius, circle_center[1] + circle_radius)
)
# output_image = cut_image_blank(output_image)
if os.path.exists("src/assets/output"):
output_image.save("src/assets/output/cropped_circle_image.png")
return output_image
def make_conference_poster(conference_name, conference_name_eng, conference_name_full, conference_info, conference_rundown):
BACKGROUND_COLOR = (1, 6, 143)
background = Image.open("src/assets/schedule/top_background.jpg").convert("RGBA")
bar = Image.open("src/assets/schedule/bar.png").convert("RGBA")
point_bar = Image.open("src/assets/schedule/point_bar.png").convert("RGBA")
H_PADDING_RATION = 0.06
V_PADDING = 80
V_START = 1100
bg_width, bg_height = background.size
print(background.size)
print(point_bar.size)
schedule_top, top_next_height = make_conference_schedule_top(
background, conference_name, conference_name_eng, conference_name_full, conference_info, bar, H_PADDING_RATION, V_START
)
schedule_components = [schedule_top]
##########################################################
for rundown in conference_rundown:
rundown_pic = write_rundown(BACKGROUND_COLOR, rundown, point_bar, H_PADDING_RATION, bg_width)
# if rundown["type"] == "主持人":
# schedule_components = schedule_components[:1] + [rundown_pic] + schedule_components[1:]
# else:
schedule_components.append(rundown_pic)
widths, heights = zip(*(i.size for i in schedule_components))
schedule_poster = Image.new("RGB", (bg_width, sum(heights)))
x_offset = 0
for img in schedule_components:
schedule_poster.paste(img, (0, x_offset))
x_offset += img.size[1]
img_stream = io.BytesIO()
schedule_poster.save(img_stream, format="JPEG")
img_stream.seek(0)
if os.path.exists("src/assets/output"):
schedule_poster.save("src/assets/output/schedule.jpg")
return img_stream
def write_rundown(BACKGROUND_COLOR, rundown, point_bar, H_PADDING_RATION, bg_width):
background = Image.new("RGB", (bg_width, 6000), BACKGROUND_COLOR)
next_height = 0
photo_size_ratio = 0.12
V_PADDING = 80
background = embed_image(
background,
point_bar,
offset_y = V_PADDING,
)
time_width = 0
font_size = 130
font_path = font_path_notosansM
# print(point_bar.size)
if rundown["type"] != "主持人":
text = rundown["start_time"][:-3] + "-" + rundown["end_time"][:-3]
text_ = text_info(text, font_size, [0, 0],
max_height=font_size,
max_width=bg_width * (1 - H_PADDING_RATION * 2.5),
font_path=font_path)
start_y = V_PADDING + (point_bar.height - text_.font_size) // 2
conference_rundown_time, background, next_height = write_text(
text, font_path, background, H_PADDING_RATION * 1.5, font_size,
start_y=start_y,
alignment="left"
)
time_width = conference_rundown_time.text_width
rundown["title"] = rundown["title"] if "title" in rundown else ""
text = rundown["type"] # rundown["title"] if rundown["type"] != "主持人" else "主持人"
pioneer_part_width = H_PADDING_RATION * 1.5 * bg_width + time_width + font_size // 2
text_ = text_info(
text,
font_size,
[0, 0],
max_height=font_size,
max_width=bg_width - pioneer_part_width - H_PADDING_RATION * bg_width, font_path=font_path)
start_y = V_PADDING + (point_bar.height - text_.font_size) // 2
conference_rundown_type, background, next_height = write_text(
text,
font_path,
background,
pioneer_part_width / bg_width,
font_size,
start_y=start_y,
max_width=bg_width - pioneer_part_width - H_PADDING_RATION * bg_width,
alignment="left",
)
next_height = V_PADDING + point_bar.size[1] + V_PADDING
if rundown["type"] != "主持人":
text = rundown["title"] # rundown["title"] if rundown["type"] != "主持人" else "主持人"
pioneer_part_width = H_PADDING_RATION * 1.5 * bg_width + time_width + font_size // 2
text_ = text_info(
text,
font_size,
[0, 0],
max_height=font_size,
max_width=bg_width - pioneer_part_width - H_PADDING_RATION * bg_width, font_path=font_path)
# start_y = V_PADDING + (point_bar.height - text_.font_size) // 2
conference_rundown_type, background, next_height = write_text(
text,
font_path,
background,
pioneer_part_width / bg_width,
font_size,
start_y=next_height,
max_width=bg_width - 2 * H_PADDING_RATION * bg_width,
alignment="medium",
)
for person in rundown["people"]:
# if person["photo_link"] != "":
next_height += V_PADDING
background, next_height = write_person_line(background, person, H_PADDING_RATION, next_height, photo_size_ratio)
background = background.convert("RGB")
background = background.crop((0, 0, background.size[0], next_height + V_PADDING))
if os.path.exists("src/assets/output"):
background.save("src/assets/output/schedule_rundown.jpg")
return background
def write_person_line(background, person, H_PADDING_RATION, start_y, photo_size_ratio, scale=1, text_start_padding=120, name_font_size=120, title_font_size=80):
bg_width = background.size[0]
text = person["name"]
font_size = name_font_size * scale
font_path = font_path_zihun59
alignment = "left"
pioneer_part_width = H_PADDING_RATION * bg_width + text_start_padding * scale
radius = int(photo_size_ratio * bg_width)
if person["photo_link"] != "":
round_photo = write_round_photo(background, person, H_PADDING_RATION * bg_width + radius, start_y + radius, radius)
pioneer_part_width += round_photo.size[1]
titles = person["title"].split("\n")
name_padding = 50 * scale
title_padding = 20 * scale
people_text_y = start_y + (2 * radius - font_size - name_padding - title_font_size * scale * len(titles)) // 2
conference_rundown_people_name, background, next_height = write_text(
text,
font_path,
background,
pioneer_part_width / bg_width,
font_size,
start_y=people_text_y,
max_width=bg_width - pioneer_part_width - H_PADDING_RATION * bg_width,
alignment=alignment
)
next_height += name_padding
font_size = title_font_size * scale
font_path = font_path_notosansM
# pioneer_part_width = H_PADDING_RATION * bg_width + round_photo.size[1] + font_size
for i, text in enumerate(titles):
conference_rundown_people_title, background, next_height = write_text(
text,
font_path,
background,
pioneer_part_width / bg_width,
font_size,
start_y=next_height + title_padding,
max_width=bg_width - pioneer_part_width - H_PADDING_RATION * bg_width,
max_height=radius * 1.2,
alignment=alignment,
)
return background, start_y + radius * 2
def write_round_photo(background, person, start_x, start_y, radius):
photo = Image.new("RGBA", (100, 100), (125, 125, 125, 0))
res = requests.get(person["photo_link"])
photo = Image.open(io.BytesIO(res.content)).convert("RGBA")
width, height = photo.size
circle_center = (width // 2, height // 3.75)
circle_radius = min(width//2, height // 3.75)
print(width, height, circle_center, circle_radius)
round_photo = crop_round_img(photo, circle_center, circle_radius)
round_photo = round_photo.resize((radius*2, radius*2))
background = embed_image(background, round_photo, offset_y=start_y-radius, offset_x=start_x-radius)
return round_photo
def make_conference_schedule_top(background, conference_name, conference_name_eng, conference_name_full, conference_info, bar, H_PADDING_RATION, V_START):
font_size = 250
font_path = font_path_pingfangM
text = conference_name
conference_name, background, next_height = write_text(
text,
font_path,
background,
H_PADDING_RATION,
font_size,
start_y=V_START,
alignment="left",
)
font_size = 110
font_path = font_path_notosans
text = conference_name_eng
V_PADDING = 120
conference_name_eng, background, next_height = write_text(
text, font_path, background, H_PADDING_RATION, font_size,
start_y=next_height + V_PADDING, alignment="left"
)
font_size = 160
font_path = font_path_notosansM
text = "——" + conference_name_full + "——"
V_PADDING = 180
conference_name_full, background, next_height = write_text(
text, font_path, background, H_PADDING_RATION, font_size, start_y=next_height + V_PADDING, alignment="medium"
)
# write_text(" ", font_path, background, H_PADDING_RATION, 200, start_height=next_height + 400, alignment="medium")
INNER_PADDING = 60
next_height = next_height + V_PADDING - INNER_PADDING
background, next_height = write_conference_info(conference_info, background, bar, H_PADDING_RATION, INNER_PADDING, next_height)
background = background.convert("RGB")
background = background.crop((0, 0, background.size[0], next_height + V_PADDING))
if os.path.exists("src/assets/output"):
background.save("src/assets/output/schedule_top.jpg")
return background, next_height
def write_conference_info(conference_info, background, bar, H_PADDING_RATION, V_PADDING, next_height):
bg_width, vg_height = background.size
for i, info in enumerate(conference_info):
font_size = 110
font_path = font_path_notosansB
text = info[0]
bar = bar.resize((int(bg_width * 0.06), int(font_size * 0.7)))
info_name, background, next_height = write_text(
text, font_path, background, H_PADDING_RATION, font_size, start_y=next_height + V_PADDING, alignment="left"
)
background = embed_image(
background, bar, offset_y=next_height - (bar.size[1] + info_name.font_size) // 2, offset_x=bg_width * H_PADDING_RATION + info_name.text_width
)
font_size = 80
text = info[1]
pioneer_part_width = bar.size[0] + info_name.text_width + font_size
text_ = text_info(
text,
font_size,
[0, 0],
max_height=font_size,
max_width=bg_width * (1 - 2 * H_PADDING_RATION) - info_name.text_width - bar.width,
font_path=font_path)
info_content, background, _ = write_text(
text,
font_path,
background,
H_PADDING_RATION + pioneer_part_width / bg_width,
font_size,
max_width=bg_width * (1 - 2 * H_PADDING_RATION) - info_name.text_width - bar.width,
start_y=next_height - info_name.font_size + (info_name.font_size - text_.font_size) // 2,
alignment="left",
)
# next_height = next_height + info_name.font_size
return background, next_height
def getGuestComposePoster(conference_name, conference_eng_name, people):
top = Image.open("src/assets/GuestComposePoster/top.png").convert("RGBA")
card = Image.open("src/assets/GuestComposePoster/card.png").convert("RGBA")
V_PADDING = 80
background = Image.new("RGB", (top.size[0], card.size[1] * math.ceil(len(people) / 3) + V_PADDING), (255, 255, 255))
bg_width, bg_height = background.size
H_PADDING = (bg_width - 3*card.size[0]) / 2
V_START = 1200
print(top.size)
print(background.size)
font_size = 220
font_path = font_path_notosansL
text = conference_name
conference_name, top, next_height = write_text(
text,
font_path,
top,
H_PADDING / bg_width,
font_size,
start_y=V_START,
alignment="medium",
)
font_size = 250
font_path = font_path_notosansL
text = conference_eng_name
conference_eng_name, top, next_height = write_text(
text,
font_path,
top,
H_PADDING / bg_width,
font_size,
start_y=next_height + V_PADDING,
max_height=700,
alignment="medium",
)
people = [p for p in people if p["photo_link"] != ""]
for (i, person) in enumerate(people):
background = write_person_card(background, card, person, H_PADDING + card.size[0] * (i % 3), card.height * (i // 3))
guest_compose_poster = Image.new("RGB", (bg_width, background.size[1] + top.size[1]))
guest_compose_poster.paste(top, (0, 0))
guest_compose_poster.paste(background, (0, top.size[1]))
img_stream = io.BytesIO()
guest_compose_poster.save(img_stream, format="JPEG")
img_stream.seek(0)
if os.path.exists("src/assets/output"):
guest_compose_poster.save("src/assets/output/guest_compose_poster.jpg")
return img_stream
def write_person_card(background, card, person, H_PADDING, start_y):
bg_width = background.size[0]
# print(bg_width * photo_size_ratio)
white_bar = 70
radius = int(0.67 * card.width) // 2
round_photo = write_round_photo(background, person, H_PADDING + card.width // 2, start_y+white_bar+radius, radius)
background = embed_image(background, card, offset_y=start_y, offset_x=H_PADDING)
text = person["name"]
font_size = 120
font_path = font_path_zihun59
pioneer_part_width = H_PADDING
people_text_y = start_y + 2 * radius + white_bar + 20
guest_compose_poster_people_name, background, next_height = write_text(
text, font_path, background,
pioneer_part_width / bg_width,
font_size,
start_y=people_text_y,
start_x=H_PADDING,
part_width=card.width,
max_width=card.width,
alignment="medium",
# color=(0,0,0)
)
text = "\n".join(person["title"].split("\n")[:5])
font_size = 40
font_path = font_path_notosansM
next_height += 30
for i, text in enumerate(text.split("\n")):
pioneer_part_width = H_PADDING
people_text_y = next_height + 10
guest_compose_poster_people_title, background, next_height = write_text(
text, font_path, background,
pioneer_part_width / bg_width,
font_size,
start_y=people_text_y,
start_x=H_PADDING,
part_width=card.width,
max_width=card.width-white_bar,
alignment="medium",
# color=(0,0,0)
)
return background
def getGuestEScreen(root_type, person):
template_info = {
"full": {
"width": 3328,
"height": 1152
},
"half": {
"width": 3072,
"height": 1280
},
"M": {
"width": 3072,
"height": 1280
}
}
background = Image.open(f"src/assets/EScreen/{root_type}.jpg")
bg_width, bg_height = background.size
scale = bg_width / template_info[root_type]["width"]
radius = 600 // 2 * scale
radius_ratio = radius / bg_width
H_PADDING_RATION = 0.22
V_START = bg_height // 2 - radius
print(background.size)
write_person_line(background, person, H_PADDING_RATION, V_START, radius_ratio,
text_start_padding=200, name_font_size=100, title_font_size=50, scale=scale)
img_stream = io.BytesIO()
background.save(img_stream, format="JPEG")
img_stream.seek(0)
if os.path.exists("src/assets/output"):
background.save("src/assets/output/hall_screen.jpg")
return img_stream
if __name__ == "__main__":
# image_path = 'src/assets/邀请函封面.jpg' # 替换为你的图片文件路径
# text_to_write = "你好,世界!"
# font_path = "src/assets/FangZhengHeiTiJianTi-1.ttf"
# position = (120, 140)
from collections import namedtuple
Person = namedtuple("Person", ["name", "title", "category", "conference", "respond_person"])
person = Person(name="一二三四五六", title="先生", category="演讲", conference="8月21日中欧智慧论坛,做主题演讲", respond_person="Eazy")
output_path = "output/output.pdf"
# writeInvitationPDF(person, debug=True)
text_list = [
"""Shanghai 协砺科技有限公司专注于青少年科技教育12年,通过STEAM25教育模式培养青少年的科学思维和创新能力。在全国设有数十家学习中心,服务上万名学员,提供科创、机器人和编程课程。公司还设有赛事研究中心,由专业工程师团队提供赛事研究和策略支持。学员在各类科技赛事中屡获佳绩。协砺科技致力于推动中国科技教育的创新与发展,为国家繁荣贡献力量。
【主要产品】科创、编程、赛事
【核心特点】为青少年提供优质的科创学习规划。
【核心亮点】高效提供更优质的赛事解决方案——快速赋能当地传统教育企业转型智能化教育赛道。""",
"""天天乐学创办于2015年6月,总部位于北京,创始团队均来自清北等知名高校,旗下产品天天乐学定制app,主打立体化教学和趣味性学习,可深度匹配教学需求,实现个性化教学。目前合作校近三万家,遍布全国31个省,300座城市,是国内知名的教育科技公司。
【主要产品】独立教师·机构·绘本馆英语APP定制
【核心特点】内容深度定制,帮助机构实现线上线下联动教学
""",
"""【】星瀚之光是明德传承集团旗下文化传媒品牌
成立于2023年,立足深圳,布局全国九大中心城市,是国内首家定位于价值观驱动的P产业链开发的MCN机构,拥有知识P孵化事业群和商业P孵化事业群两大主营板块,旗下涵盖了创始人P孵化直播电商、私域消费电商、企业整合营销、艺人经纪等业务,致力于成为中国规模最大的P生态搭建领军企业。
【】坚守“自利利他、奋斗为本、价值为纲、成就客户”的企业价值观
公司成立以来发展迅猛,迅速孵化出各大赛道的多个头部P,霸榜各大短视频平台榜单,包括:有家庭教育赛道:白瑞(家庭教育赛道头部主播);身心灵赛道:尚致胜(新中式心理学首创者);女性成长赛道:黄欢;商业赛道:苏建诚(亚细亚集团联合创始人)、王怀南(宝宝树创始人)、杨晓燕(长江商学院助理院长)、何慕(联众智达董事长)等;国学赛道:沈德斌(了凡四训学者)、钱锦国老师等。""",
"""学趋坊是一家为学校、机构、智能自习室等提供在线化和智能化解决方案的数字化教育内容供应商。已经为多个知名的在线教育品牌、智能自习室品牌提供0DM和OEM服务,产品用户累计超过1000万。
【主要产品】智能校本
【核心特点】为本地化内容插上科技的翅膀。
【核心亮点】高质量高效本地化教研—快速赋能当地传统教育企业转型智能化教育赛道。""",
]
logo_path_list = [
"src/assets/logo/学区房logo.png",
"src/assets/logo/星瀚之光logo.jpg",
"src/assets/logo/软科起点logo.png",
"src/assets/logo/天天乐学logo.png",
]
slogan_list = ["探索科技,启迪未来。", "价值观驱动的知识P全域孵化领先平台", "定制英语APP学习系统"]
FOREGROUND_ID = 3 # 0, 1, 2, 3
SLOGAN_ID = 1 # 0, 1, 2
TEXT_ID = 2 # 0, 1, 2, 3
LOGO_LINK = "https://weboffice-temporary.ks3-cn-beijing.wpscdn.cn/thumbnail/tJUjbgWdD6R5k5rECKka1yK_100.png?Expires=1721322000&KSSAccessKeyId=AKLTmoJhggaFT1CHuozGZqbC&Signature=qZCdG1n42uwCFGJ8%2BAlF6VK8KEM%3D&response-cache-control=public%2Cmax-age%3D86400"
name, titles = "王千里", "全国政协文员\n好好先生"
PHOTO_LINK = "https://weboffice-temporary.ks3-cn-beijing.wpscdn.cn/thumbnail/tpcdnbPEKEJ3ge831dSHEd3_100.png?Expires=1720890000&KSSAccessKeyId=AKLTmoJhggaFT1CHuozGZqbC&Signature=jeKkEy%2BTx3BAMNzqrmjUbg6uBK8%3D&response-cache-control=public%2Cmax-age%3D86400"
ORIGINAL_PHOTO_LINK = "https://weboffice-temporary.ks3-cn-beijing.wpscdn.cn/thumbnail/tn6C9byoghgm8cnQYtShMdb_100.jpeg?Expires=1723395600&KSSAccessKeyId=AKLTmoJhggaFT1CHuozGZqbC&Signature=%2BJX%2BaPumafx8mXpV7wfN1Z3FVpA%3D&response-cache-control=public%2Cmax-age%3D86400"
conference_name = "教育行业规范管理发展论坛"
conference_name_eng = "2024 World Education Conference"
conference_name_full = "2024世界教育者大会上海主会"
conference_info = [
["指导单位", "WEC教育者大会组委会、上海教科院民办教育研究所"],
["主办单位", "上海市民办教育协会、长三角教育发展研究院\上海市民办教育协会、长三角教育发展研究院\上海市民办教育协会、长三角教育发展研究院"],
["论坛时间", "2024年8月21日上午9:00-12:30"],
["论坛地址", "国家会展中心4.2号馆A"],
]
conference_rundown = [
{
"type": "主持人",
"title": "上海市教科规划办副主任",
"people": [
{
"name": "方法名",
"title": "上海市教科规划办副主任\n上海教科院改革发展研究部研究员\n华东师范大学国家宏观教育研究院博士生导师",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任\n上海教科院改革发展研究部研究员\n华东师范大学国家宏观教育研究院博士生导师",
"photo_link": "",
},
],
"start_time": "9:00:00",
"end_time": "9:20:00",
},
{
"type": "致辞",
"title": "上海市教科规划办副主",
"people": [
{
"name": "方法名",
"title": "上海市教科规划办副主任",
"photo_link": ORIGINAL_PHOTO_LINK,
}
],
"start_time": "9:00:00",
"end_time": "9:20:00",
}, {
"type": "致辞",
"title": "上海市教科规划办副主任上海市教科规划办副主任上海市教科规",
"people": [
{
"name": "方法名",
"title": "上海市教科规划办副主任",
"photo_link": ORIGINAL_PHOTO_LINK,
}
],
"start_time": "9:00:00",
"end_time": "9:20:00",
},
]
people = [
{
"name": "方法名",
"title": "上海市教科规划办副主任\n上海教科院改革发展研究部研究员\n华东师范大学国家宏观教育研究院博士生导师",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任\n上海教科院改革发展研究部研究员\n华东师范大学国家宏观教育研究院博士生导师",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任\n上海教科院改革发展研究部研究员\n华东师范大学国家宏观教育研究院博士生导师",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任\n上海教科院改革发展研究部研究员\n华东师范大学国家宏观教育研究院博士生导师",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任\n上海教科院改革发展研究部研究员\n华东师范大学国家宏观教育研究院博士生导师",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任",
"photo_link": ORIGINAL_PHOTO_LINK,
},
{
"name": "方法名",
"title": "上海市教科规划办副主任",
"photo_link": ORIGINAL_PHOTO_LINK
},
{
"name": "方法名",
"title": "上海市教科规划办副主任",
"photo_link": ORIGINAL_PHOTO_LINK,
},
]
# res = requests.get(ORIGINAL_PHOTO_LINK)
# # guest_photo = Image.open(io.BytesIO(res.content)).convert("RGBA")
# original_guest_photo = Image.open(io.BytesIO(res.content)).convert("RGBA")
# guest_photo = rm_img_bg(original_guest_photo)
# compose_guest_poster(name, guest_photo, titles)
# for file in os.listdir("src/assets/GuestPoster/original_guest"):
# image = Image.open(os.path.join("src/assets/GuestPoster/original_guest", file))
# image = rm_img_bg(image)
# image.save(os.path.join("src/assets/output", file.replace("jpg", "png")))
# image = Image.open('src/assets/苏建诚.png')
# width, height = image.size
# circle_center = (width // 2, height // 4)
# circle_radius = min(width, height//2) // 2
# round_img = crop_round_img(image, circle_center, circle_radius)
# print(round_img.size)
# res = requests.get(ORIGINAL_PHOTO_LINK)
# photo = Image.open(io.BytesIO(res.content)).convert("RGBA")
# make_exhibitor_poster(LOGO_LINK, slogan_list[SLOGAN_ID], text_list[TEXT_ID])
# make_guest_poster(name, "", "", ORIGINAL_PHOTO_LINK, titles)
# make_conference_poster(conference_name, conference_name_eng, conference_name_full, conference_info, conference_rundown)
# 定义圆心和半径
# res = requests.get(ORIGINAL_PHOTO_LINK)
# print(res)
# with open("src/assets/output/guest_compose_poster.png", "wb") as f:
# f.write(res.content)
# photo = Image.open(io.BytesIO(res.content)).convert("RGBA")
# getGuestComposePoster("全球教育者公论教育", "EDUCATORS AROUND THE GLOBE TALKING ABOUT EDUCATION", people)
getGuestEScreen("full", conference_rundown[0]["people"][0])