1. 目标效果

2. 分析实现
- 可以使用 opencv 或者 PIL 进行绘制实现,但是由于 opencv 不能绘制中文,因此采用 PIL 进行印章制作;
- 先绘制边框和隔离线;
- 再绘制文字;
- 最后给印章添加纹理。
3. 绘制边框
3.1 绘制边框分析
- 通过 draw.rectangle 实现公用的矩形的绘制;
- 获取图像长宽 image.size;
- 计算各个矩形的起始点【三分之一宽、二分之一高】;
- 通过一个矩形,将印章分为上下两个矩形【绘制上下矩形, 注意边框占位一半】;
- 通过上边三分之一个矩形将上侧矩形平均分三份【绘制上侧中间矩形】;
- 绘制下侧右边的小矩形【绘制下侧右边的矩形】上下一起绘制;
- 绘制印章完整边框【绘制边框】。
3.2 实现代码
# 绘制印章的格子
def draw_seal_box(self):
# 获取图像长宽
w, h = self.image.size
# 计算各个矩形的起始点
# 三分之一宽
third_w = w / 3
# 二分之一高
half_h = h / 2
# 绘制下侧右边的矩形
self.draw_rect([third_w * 2 - 5,0,w,h], 6)
# 绘制上侧中间矩形
self.draw_rect([third_w,0,third_w * 2,half_h], 6)
# 绘制上下矩形, 注意边框占位一半
self.draw_rect([0,0,w,half_h], 6)
# 绘制边框
self.draw_rect([0,0,w,h])
# 绘制矩形
def draw_rect(self, shape, width=8):
self.draw.rectangle(shape, fill=None, outline=self.border_color, width=width)
3.3 实现结果

4. 绘制文字
4.1 绘制文字分析
- 绘制文字公用方法封装;
1.1 计算传入文本的占位的盒子宽高【textbbox】;
1.2 根据文字的占位,计算绘制文本【text】;
- 获取图像长宽【image.size】;
- 计算各个文本的中心点坐标【六分之一宽、四分之一高】;
- 文本、数字、英文对应的font;
- 获取传入的字段;
- 依次绘制对应的文本。
4.2 文字绘制
# 绘制文本
def draw_text(self, text, font, x, y):
# 计算传入文本的占位的盒子宽高
x1, y1, x2, y2 = self.draw.textbbox((x, y), text, font)
text_size_x = x2 - x1
text_size_y = y2 - y1
# 根据文字的占位,计算绘制文本
self.draw.text(
(x - text_size_x / 2, y - text_size_y / 2),
text,
font=font,
fill=self.text_color
)
4.3 印章文本绘制
# 绘制印章中的文字
def draw_seal_text(self, opts = {}):
# 获取图像长宽
w, h = self.image.size
# 计算各个矩形的起始点
# 六分之一宽
half_third_w = w / 6
# 四分之一高
half_half_h = h / 4
font_size = int(half_half_h)
# 文本、数字、英文对应的font
font = ImageFont.truetype('./lib/font/FZLTZHJW.TTF',font_size)
font_num = ImageFont.truetype('./lib/font/HelveticaNeueLTPro-Lt.ttf',font_size)
font_num_en = ImageFont.truetype('./lib/font/PingFang Medium.ttf',font_size)
# 获取传入的字段
name = opts.get("name")
days = opts.get("days")
money = opts.get("money")
percent = opts.get("percent")
time = opts.get("time")
# 依次绘制对应的文本
self.draw_text(name, font, half_third_w * 2, half_half_h * 3)
self.draw_text(days, font_num, half_third_w * 5, half_half_h)
self.draw_text(money, font_num, half_third_w * 3, half_half_h)
self.draw_text(percent, font_num, half_third_w, half_half_h)
self.draw_text(time, font_num, half_third_w * 5, half_half_h * 3)
4.4 绘制结果

5. 添加纹理
5.1 实现分析
- 获取纹理图像;
- 随机截取一部分纹理图;
- 重新设置截取图片的的大小,并进行一次高斯模糊;
- 将纹理图的灰度映射到原图的透明度,由于纹理图片自带灰度,映射后会有透明效果,所以fill的透明度不能太低;
- 进行一次高斯模糊,提高真实度。
5.2 实现代码
# 给印章添加纹理
def add_texture_to_image(self):
# 打开纹理图像,随机截取旋转
img_wl = Image.open('./lib/icon/bg.png')
# 随机截取一部分纹理图
pos_random = (randint(0,220), randint(0,340))
box = (pos_random[0], pos_random[1], pos_random[0]+180, pos_random[1]+60)
img_wl_random = img_wl.crop(box)
# 重新设置截取图片的的大小,并进行一次高斯模糊
img_wl_random = img_wl_random.resize(self.image.size).convert('L').filter(ImageFilter.GaussianBlur(1))
# 将纹理图的灰度映射到原图的透明度,由于纹理图片自带灰度,映射后会有透明效果,所以fill的透明度不能太低
L, H = self.image.size
for h in range(H):
for l in range(L):
dot = (l, h)
color = self.image.getpixel(dot)[:3]+(int(img_wl_random.getpixel(dot)/255*self.image.getpixel(dot)[3]),)
self.image.putpixel(dot, color)
# 进行一次高斯模糊,提高真实度
self.image = self.image.filter(ImageFilter.GaussianBlur(0.6))
5.3 实现结果

6. 完整代码
import os
import openpyxl
from random import randint
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from PIL import ImageFilter
class Seal:
def __init__(self):
# 印章边框颜色
self.border_color = (234, 24, 27, 180)
# 印章文本颜色
self.text_color = (234, 24, 27, 180)
# 印章背景颜色
self.bg_color = (255,255,255,1)
# 保存印章的图像对象
self.image = None
# 印章图像绘制对象
self.draw = None
# 创建印章
def create_seal(self, opts = {}):
# 印章大小
base = [950,240]
# 创建印章图像
self.image = Image.new('RGBA',tuple(base),self.bg_color)
self.draw = ImageDraw.Draw(self.image)
# 绘制印章格子
self.draw_seal_box()
# 绘制印章文字
self.draw_seal_text(opts)
# 添加纹理
self.add_texture_to_image()
# 绘制印章的格子
def draw_seal_box(self):
# 获取图像长宽
w, h = self.image.size
# 计算各个矩形的起始点
# 三分之一宽
third_w = w / 3
# 二分之一高
half_h = h / 2
# 绘制下侧右边的矩形
self.draw_rect([third_w * 2 - 5,0,w,h], 6)
# 绘制上侧中间矩形
self.draw_rect([third_w,0,third_w * 2,half_h], 6)
# 绘制上下矩形, 注意边框占位一半
self.draw_rect([0,0,w,half_h], 6)
# 绘制边框
self.draw_rect([0,0,w,h])
# 绘制印章中的文字
def draw_seal_text(self, opts = {}):
# 获取图像长宽
w, h = self.image.size
# 计算各个文本的中心点坐标
# 六分之一宽
half_third_w = w / 6
# 四分之一高
half_half_h = h / 4
font_size = int(half_half_h)
# 文本、数字、英文对应的font
font = ImageFont.truetype('./lib/font/FZLTZHJW.TTF',font_size)
font_num = ImageFont.truetype('./lib/font/HelveticaNeueLTPro-Lt.ttf',font_size)
font_num_en = ImageFont.truetype('./lib/font/PingFang Medium.ttf',font_size)
# 获取传入的字段
name = opts.get("name")
days = opts.get("days")
money = opts.get("money")
percent = opts.get("percent")
time = opts.get("time")
# 依次绘制对应的文本
self.draw_text(name, font, half_third_w * 2, half_half_h * 3)
self.draw_text(days, font_num, half_third_w * 5, half_half_h)
self.draw_text(money, font_num, half_third_w * 3, half_half_h)
self.draw_text(percent, font_num, half_third_w, half_half_h)
self.draw_text(time, font_num, half_third_w * 5, half_half_h * 3)
# 给印章添加纹理
def add_texture_to_image(self):
# 打开纹理图像,随机截取旋转
img_wl = Image.open('./lib/icon/bg.png')
# 随机截取一部分纹理图
pos_random = (randint(0,220), randint(0,340))
box = (pos_random[0], pos_random[1], pos_random[0]+180, pos_random[1]+60)
img_wl_random = img_wl.crop(box)
# 重新设置截取图片的的大小,并进行一次高斯模糊
img_wl_random = img_wl_random.resize(self.image.size).convert('L').filter(ImageFilter.GaussianBlur(1))
# 将纹理图的灰度映射到原图的透明度,由于纹理图片自带灰度,映射后会有透明效果,所以fill的透明度不能太低
L, H = self.image.size
for h in range(H):
for l in range(L):
dot = (l, h)
color = self.image.getpixel(dot)[:3]+(int(img_wl_random.getpixel(dot)/255*self.image.getpixel(dot)[3]),)
self.image.putpixel(dot, color)
# 进行一次高斯模糊,提高真实度
self.image = self.image.filter(ImageFilter.GaussianBlur(0.6))
# 测试使用查看当前图片绘制情况
def show(self):
self.image.show()
# 绘制矩形
def draw_rect(self, shape, width=8):
self.draw.rectangle(shape, fill=None, outline=self.border_color, width=width)
# 绘制文本
def draw_text(self, text, font, x, y):
# 计算传入文本的占位的盒子宽高
x1, y1, x2, y2 = self.draw.textbbox((x, y), text, font)
text_size_x = x2 - x1
text_size_y = y2 - y1
# 根据文字的占位,计算绘制文本
self.draw.text(
(x - text_size_x / 2, y - text_size_y / 2),
text,
font=font,
fill=self.text_color
)
if __name__ == "__main__":
seal = Seal()
seal.create_seal({
"name": "美邦服饰",
"days": "89",
"money": "40114.82",
"percent": "24.59",
"time": "20210331"
})
seal.show()
7. 读取 xlsx 文件
7.1 代码分析
- 获取路径下的所有 excel 表;
- 获取 path 路径下的所有文件和文件夹名称;
- 筛选文件列表中的 xlsx 后缀的文件名称;
- 返回筛选结果;
- 初始化印章类【Seal】;
- 循环获取 xlsx 中的内容。
7.2 代码实现
# 获取路径下的所有 excel 表
def get_all_xlsx(path):
# 获取 path 路径下的所有文件和文件夹名称
names = os.listdir(path)
# 筛选文件列表中的 xlsx 后缀的文件名称
xlsx_names = list(filter(lambda x : x.split('.').pop() in ['xlsx'], names))
# 返回筛选结果
return xlsx_names
# 循环读取 xlsx 文件
def loop_read_xlsx_file(path, names):
seal = Seal()
for name in names:
read_xlsx_data(f'{path}/{name}', seal)
8. 批量生成印章
8.1 实现分析
- 读取 xlsx 文件中的数据;
- 获取印章文件名以及印章中的文本字段;
- 判断印章需要的文字是否都存在;
- 去掉字段中印章不需要的符号或者文本;
- 创建印章,调用【Seal】的生成印章方法;
- 将生成的印章进行保存。
8.2 实现代码
# 读取 xlsx 文件中的文本字段
def read_xlsx_data(path, seal):
# 读取 xlsx 文件中的数据
wb = openpyxl.load_workbook(path,data_only=True)
sheets = wb.get_sheet_names()
sheet_first = sheets[0]
ws = wb.get_sheet_by_name(sheet_first)
for r in range(2,ws.max_row + 1):
# 获取印章文件名以及印章中的文本字段
name = ws.cell(row=r, column=1).value
days = ws.cell(row=r, column=2).value
money = ws.cell(row=r, column=3).value
percent = ws.cell(row=r, column=4).value
time = ws.cell(row=r, column=5).value
# print(name, days, money, percent, time)
if name and days and money and percent and time:
# 创建印章
name = name.replace("*", "")
money = money.replace("+", "").replace("-", "").replace(",", "")
percent = percent.replace("+", "").replace("-", "").replace("%", "")
time = time.replace("清仓", "")
seal.create_seal({
"name": name,
"days": days,
"money": money,
"percent": percent,
"time": time
})
seal.save(f'./out_images/{name}{time}.png')
8.3 生成结果

9. 加入批量操作的全部代码
import os
import openpyxl
from random import randint
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from PIL import ImageFilter
class Seal:
def __init__(self):
# 印章边框颜色
self.border_color = (234, 24, 27, 180)
# 印章文本颜色
self.text_color = (234, 24, 27, 180)
# 印章背景颜色
self.bg_color = (255,255,255,1)
# 保存印章的图像对象
self.image = None
# 印章图像绘制对象
self.draw = None
# 创建印章
def create_seal(self, opts = {}):
# 印章大小
base = [950,240]
# 创建印章图像
self.image = Image.new('RGBA',tuple(base),self.bg_color)
self.draw = ImageDraw.Draw(self.image)
# 绘制印章格子
self.draw_seal_box()
# 绘制印章文字
self.draw_seal_text(opts)
# 添加纹理
self.add_texture_to_image()
# 绘制印章的格子
def draw_seal_box(self):
# 获取图像长宽
w, h = self.image.size
# 计算各个矩形的起始点
# 三分之一宽
third_w = w / 3
# 二分之一高
half_h = h / 2
# 绘制下侧右边的矩形
self.draw_rect([third_w * 2 - 5,0,w,h], 6)
# 绘制上侧中间矩形
self.draw_rect([third_w,0,third_w * 2,half_h], 6)
# 绘制上下矩形, 注意边框占位一半
self.draw_rect([0,0,w,half_h], 6)
# 绘制边框
self.draw_rect([0,0,w,h])
# 绘制印章中的文字
def draw_seal_text(self, opts = {}):
# 获取图像长宽
w, h = self.image.size
# 计算各个文本的中心点坐标
# 六分之一宽
half_third_w = w / 6
# 四分之一高
half_half_h = h / 4
font_size = int(half_half_h)
# 文本、数字、英文对应的font
font = ImageFont.truetype('./lib/font/FZLTZHJW.TTF',font_size)
font_num = ImageFont.truetype('./lib/font/HelveticaNeueLTPro-Lt.ttf',font_size)
font_num_en = ImageFont.truetype('./lib/font/PingFang Medium.ttf',font_size)
# 获取传入的字段
name = opts.get("name")
days = opts.get("days")
money = opts.get("money")
percent = opts.get("percent")
time = opts.get("time")
# 依次绘制对应的文本
self.draw_text(name, font, half_third_w * 2, half_half_h * 3)
self.draw_text(days, font_num, half_third_w * 5, half_half_h)
self.draw_text(money, font_num, half_third_w * 3, half_half_h)
self.draw_text(percent, font_num, half_third_w, half_half_h)
self.draw_text(time, font_num, half_third_w * 5, half_half_h * 3)
# 给印章添加纹理
def add_texture_to_image(self):
# 打开纹理图像,随机截取旋转
img_wl = Image.open('./lib/icon/bg.png')
# 随机截取一部分纹理图
pos_random = (randint(0,220), randint(0,340))
box = (pos_random[0], pos_random[1], pos_random[0]+180, pos_random[1]+60)
img_wl_random = img_wl.crop(box)
# 重新设置截取图片的的大小,并进行一次高斯模糊
img_wl_random = img_wl_random.resize(self.image.size).convert('L').filter(ImageFilter.GaussianBlur(1))
# 将纹理图的灰度映射到原图的透明度,由于纹理图片自带灰度,映射后会有透明效果,所以fill的透明度不能太低
L, H = self.image.size
for h in range(H):
for l in range(L):
dot = (l, h)
color = self.image.getpixel(dot)[:3]+(int(img_wl_random.getpixel(dot)/255*self.image.getpixel(dot)[3]),)
self.image.putpixel(dot, color)
# 进行一次高斯模糊,提高真实度
self.image = self.image.filter(ImageFilter.GaussianBlur(0.6))
# 测试使用查看当前图片绘制情况
def show(self):
if self.image:
self.image.show()
# 保存图片
def save(self, path):
if self.image:
self.image.save(path)
# 绘制矩形
def draw_rect(self, shape, width=8):
self.draw.rectangle(shape, fill=None, outline=self.border_color, width=width)
# 绘制文本
def draw_text(self, text, font, x, y):
# 计算传入文本的占位的盒子宽高
x1, y1, x2, y2 = self.draw.textbbox((x, y), text, font)
text_size_x = x2 - x1
text_size_y = y2 - y1
# 根据文字的占位,计算绘制文本
self.draw.text(
(x - text_size_x / 2, y - text_size_y / 2),
text,
font=font,
fill=self.text_color
)
# 获取路径下的所有 excel 表
def get_all_xlsx(path):
# 获取 path 路径下的所有文件和文件夹名称
names = os.listdir(path)
# 筛选文件列表中的 xlsx 后缀的文件名称
xlsx_names = list(filter(lambda x : x.split('.').pop() in ['xlsx'], names))
# 返回筛选结果
return xlsx_names
# 循环读取 xlsx 文件
def loop_read_xlsx_file(path, names):
seal = Seal()
for name in names:
read_xlsx_data(f'{path}/{name}', seal)
# 读取 xlsx 文件中的文本字段
def read_xlsx_data(path, seal):
# 读取 xlsx 文件中的数据
wb = openpyxl.load_workbook(path,data_only=True)
sheets = wb.get_sheet_names()
sheet_first = sheets[0]
ws = wb.get_sheet_by_name(sheet_first)
for r in range(2,ws.max_row + 1):
# 获取印章文件名以及印章中的文本字段
name = ws.cell(row=r, column=1).value
days = ws.cell(row=r, column=2).value
money = ws.cell(row=r, column=3).value
percent = ws.cell(row=r, column=4).value
time = ws.cell(row=r, column=5).value
# print(name, days, money, percent, time)
if name and days and money and percent and time:
# 创建印章
name = name.replace("*", "")
money = money.replace("+", "").replace("-", "").replace(",", "")
percent = percent.replace("+", "").replace("-", "").replace("%", "")
time = time.replace("清仓", "")
seal.create_seal({
"name": name,
"days": days,
"money": money,
"percent": percent,
"time": time
})
seal.save(f'./out_images/{name}{time}.png')
if __name__ == "__main__":
# 获取所有的xlsx文件
xlsxs = get_all_xlsx('./')
# 读取文件内容
loop_read_xlsx_file('./', xlsxs)
10. 总结
- 配合 excel 录入印章的文本,可以批量生成印章;
- 将生成的印章添加到对应的文件中,实现批量盖章。