python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python绘制电子印章

基于Python实现制作透明背景的电子印章

作者:Li Ming&

在日常办公、文档管理和自动化流程中,电子印章(电子图章)的使用越来越普遍,本文带你从零开始,深入解析一段完整的 Python 代码,实现一个支持弧形文字、五角星、纹理防伪的电子印章生成器,有需要的小伙伴可以了解下

前言:在日常办公、文档管理和自动化流程中,电子印章(电子图章)的使用越来越普遍。相比于实体印章扫描后贴图,通过 Python 动态生成的电子印章具有背景透明、尺寸可控、样式统一等优势。本文带你从零开始,深入解析一段完整的 Python 代码,实现一个支持弧形文字、五角星、纹理防伪的电子印章生成器。

一、为什么用 Python 生成电子印章?

纯代码生成:无需依赖 Photoshop 等设计软件,可集成到 Web 后端或自动化脚本。

参数化定制:文字内容、字体大小、颜色、弧度、透明度均可灵活调整。

透明背景:生成 PNG 图片,背景为全透明,便于叠加到文档或 PDF 上。

增加纹理:通过随机截取纹理图片,模拟纸质印章的“油墨不均”效果,更逼真。

二、环境准备

代码主要依赖 Pillow(Python 图像处理库),安装方式:

pip install pillow

代码中使用了 Windows 系统字体 simsun.ttc(宋体)和 arial.ttf,如果使用其他操作系统,请修改字体路径或替换为系统自带的中英文字体。

三、完整可运行代码

将以下代码保存为 stamp_generator.py,并准备一张纹理图(例如一张带有杂点的纸纹图片),修改主程序中的路径即可运行。

需要注意的是需要从网上下载对应的电子盖章图放到对应的电脑本地磁盘的路径,仅限一次性使用。

# encoding=utf8

'''
Python制作透明背景的电子印章
'''

# 安装所需模块 
# pip install pillow

from PIL import Image,ImageFont,ImageDraw, ImageFilter
from math import pi, cos, sin, tan
from random import randint

# 定义方法
def is_Chinese(ch):
    '''判断字符是否为中文'''
    if '\u4e00' <= ch <= '\u9fff':
        return True
    return False


def pentagram(x, y, R, yDegree=0):
    '''
    计算五角星各个顶点
    int R:五角星的长轴
    int x, y:五角星的中心点
    int yDegree:长轴与y轴的夹角
    '''
    rad = pi / 180   # 每度的弧度值
    r = R * sin(18 * rad) / cos(36 * rad)   # 五角星短轴的长度

    # 求取外圈点坐标
    RVertex = [(x - (R * cos((90 + k * 72 + yDegree) * rad)), y - (R * sin((90 + k * 72 + yDegree) * rad))) for k in range(5)]
    # 求取内圈点坐标
    rVertex = [(x - (r * cos((90 + 36 + k * 72 + yDegree) * rad)), y - (r * sin((90 + 36 + k * 72 + yDegree) * rad))) for k in range(5)]

    # 顶点左边交叉合并
    vertex = [x for y in zip(RVertex, rVertex) for x in y]
    return vertex


def circle(x, y, r):
    '''计算圆的上下左右切点'''
    return (x - r, y - r, x + r, y + r)


# 定义类
class Stamp:
    def __init__(self,  edge = 5,               # 图片边缘空白的距离
                        H = 160,                # 圆心到中层文字下边缘的距离
                        R = 250,                # 圆半径
                        border = 13,            # 字到圆圈内侧的距离
                        r = 90,                 # 五星外接圆半径
                        fill = (255, 0, 0, 120),# 印章颜色, 默认纯红色, 透明度0-255,建议90-180

                        words_up = "", # 上部文字
                        angle_up = 270,         # 上部文字弧形角度
                        font_size_up = 80,      # 上部文字大小
                        font_xratio_up = 0.66,  # 上部文字横向变形比例
                        stroke_width_up = 2,    # 上部文字粗细,一般取值0,1,2,3

                        words_mid="",   # 中部文字
                        angle_mid = 72,         # 中部文字弧形角度
                        font_size_mid = 60,     # 中部文字大小
                        font_xratio_mid = 0.7,  # 中部文字横向变形比例
                        stroke_width_mid=1,     # 中部文字粗细,一般取值0,1,2

                        words_down="",# 下部文字
                        angle_down = 60,        # 下部文字弧形角度
                        font_size_down = 20,    # 下部文字大小
                        font_xratio_down = 1,   # 下部文字横向变形比例
                        stroke_width_down=1,    # 下部文字粗细,一般取值0,1,2

                        img_wl_path = "", # 纹理图路径
                        save_path = "" # 保存图片路径
                 ):

        # 打开纹理图像,随机截取旋转
        self.img_wl = Image.open(img_wl_path)

        # 图像初始设置为None
        self.img = None
        self.save_path = save_path

        self.fill = fill                       # 印章颜色
        self.edge = edge                       # 图片边缘空白的距离
        self.H = H                             # 圆心到中层文字下边缘的距离
        self.R = R                             # 圆半径
        self.r = r                             # 五星外接圆半径
        self.border = border                   # 字到圆圈内侧的距离

        self.words_up = words_up               # 上部文字
        self.angle_up = angle_up               # 上部文字弧形角度
        self.font_size_up = font_size_up       # 上部文字大小
        self.font_xratio_up = font_xratio_up   # 上部文字横向变形比例
        self.stroke_width_up = stroke_width_up     # 上部文字粗细,一般取值0,1,2,3

        self.words_mid = words_mid             # 中部文字
        self.angle_mid = angle_mid             # 中部文字弧形角度
        self.font_size_mid = font_size_mid     # 中部文字大小
        self.font_xratio_mid = font_xratio_mid # 中部文字横向变形比例
        self.stroke_width_mid = stroke_width_mid   # 中部文字粗细,一般取值0,1,2,3

        self.words_down = words_down           # 下部文字
        self.angle_down = angle_down           # 下部文字弧形角度
        self.font_size_down = font_size_down   # 下部文字大小
        self.font_xratio_down = font_xratio_down  # 下部文字横向变形比例
        self.stroke_width_down = stroke_width_down   # 中部文字粗细,一般取值0,1,2,3


    def draw_rotated_text(self, image, angle, xy, r, word, fill, font_size, font_xratio, stroke_width, font_flip = False, *args, **kwargs):
        """
            image:底层图片
            angle:旋转角度
            xy:旋转中心
            r:旋转半径
            text:绘制的文字
            fill:文字颜色
            font_size:字体大小
            font_xratio:x方向缩放比例(印章字体宽度较标准宋体偏窄)
            stroke_width: 文字笔画粗细
            font_flip:文字是否垂直翻转(印章下部文字与上部是相反的)
        """

        # 加载字体文件-直接使用windows自带字体,中文用simsun, 英文用arial
        if is_Chinese(word):
            font = ImageFont.truetype("C:/Windows/Fonts/simsun.ttc", font_size, encoding="utf-8")
        else:
            font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", font_size, encoding="utf-8")

        # 获取底层图片的size
        width, height = image.size
        max_dim = max(width, height)

        # 创建透明背景的文字层,大小4倍的底层图片
        mask_size = (max_dim * 2, max_dim * 2)
        # 印章通常使用较窄的字体,这里将绘制文字的图层x方向压缩到font_xratio的比例
        mask_resize = (int(max_dim * 2 * font_xratio), max_dim * 2)
        mask = Image.new('L', mask_size, 0)

        # 在上面文字层的中心处写字,字的左上角与中心对其
        draw = ImageDraw.Draw(mask)

        # 获取当前设置字体的宽高
        bd = draw.textbbox((max_dim, max_dim), word, font=font, align="center", *args, **kwargs)
        font_width = bd[2] - bd[0]
        font_hight = bd[3] - bd[1]

        # 文字在圆圈上下的方向,需要通过文字所在文字图层的位置修正,保证所看到的文字不会上下颠倒
        if font_flip:
            word_pos = (int(max_dim-font_width/2), max_dim+r-font_hight)
        else:
            word_pos = (int(max_dim-font_width/2), max_dim-r)

        # 写字, 以xy为中心,r为半径,文字上边中点为圆周点,绘制文字
        draw.text(word_pos, word, 255, font=font, align="center",  stroke_width = stroke_width, *args, **kwargs)

        # 调整角度,对于Π*n/2的角度,直接rotate即可,对于非Π*n/2的角度,需要先放大图片以减少旋转带来的锯齿
        if angle % 90 == 0:
            rotated_mask = mask.resize(mask_resize).rotate(angle)
        else:
            bigger_mask = mask.resize((int(max_dim*8*font_xratio), max_dim*8),
                                      resample=Image.BICUBIC)
            rotated_mask = bigger_mask.rotate(angle).resize(mask_resize, resample=Image.LANCZOS)

        # 切割文字的图片
        mask_xy = (max_dim*font_xratio - xy[0], max_dim - xy[1])
        b_box = mask_xy + (mask_xy[0] + width, mask_xy[1] + height)
        mask = rotated_mask.crop(b_box)

        # 粘贴到目标图片上
        color_image = Image.new('RGBA', image.size, fill)
        image.paste(color_image, mask)


    def draw_stamp(self):
        #创建一张底图,用来绘制文字
        img = Image.new("RGBA",(2*(self.R+self.edge),2*(self.R+self.edge)),(255,255,255,0))
        draw = ImageDraw.Draw(img)


        #绘制圆弧, R为外边缘,width往圆心算
        draw.arc(circle(self.R+self.edge, self.R+self.edge, self.R), start=0, end=360, fill=self.fill, width = self.border)


        #绘制多边形
        draw.polygon(pentagram(self.R+self.edge,self.R+self.edge,self.r), fill=self.fill, outline=self.fill)


        #绘制上圈文字
        angle_word = self.angle_up / len(self.words_up)
        angle_word_curr = ((len(self.words_up)-1) / 2) * angle_word

        for word in self.words_up:
            self.draw_rotated_text(img, angle_word_curr, (self.R+self.edge,self.R+self.edge), self.R-self.border*2, word, self.fill, self.font_size_up, self.font_xratio_up, self.stroke_width_up)
            angle_word_curr = angle_word_curr - angle_word


        #绘制中层文字
        angle_word = self.angle_mid / len(self.words_mid)
        angle_word_curr = -((len(self.words_mid)-1) / 2) * angle_word

        for word in self.words_mid:
            self.draw_rotated_text(img, 0, (self.R+self.edge+ self.H*tan(angle_word_curr*pi/180),self.R+self.edge), self.H, word, self.fill, self.font_size_mid, self.font_xratio_mid, self.stroke_width_mid, font_flip = True)
            angle_word_curr = angle_word_curr + angle_word


        #绘制下圈文字
        angle_word = self.angle_down / len(self.words_down)
        angle_word_curr = -((len(self.words_down)-1) / 2) * angle_word

        for word in self.words_down:
            self.draw_rotated_text(img, angle_word_curr, (self.R+self.edge,self.R+self.edge), self.R-self.border*2, word, self.fill, self.font_size_down, self.font_xratio_down, self.stroke_width_down, font_flip = True)
            angle_word_curr = angle_word_curr + angle_word

        # 随机圈一部分纹理图
        pos_random = (randint(0,200), randint(0,100))
        box = (pos_random[0], pos_random[1], pos_random[0]+300, pos_random[1]+300)
        img_wl_random = self.img_wl.crop(box).rotate(randint(0,360))
        # 重新设置im2的大小,并进行一次高斯模糊
        img_wl_random = img_wl_random.resize(img.size).convert('L').filter(ImageFilter.GaussianBlur(1))
        # 将纹理图的灰度映射到原图的透明度,由于纹理图片自带灰度,映射后会有透明效果,所以fill的透明度不能太低
        L, H = img.size
        for h in range(H):
            for l in range(L):
                dot = (l, h)
                img.putpixel(dot, img.getpixel(dot)[:3]+(int(img_wl_random.getpixel(dot)/255*img.getpixel(dot)[3]),))
        # 进行一次高斯模糊,提高真实度
        self.img = img.filter(ImageFilter.GaussianBlur(0.6))

    def show_stamp(self):
        if self.img:
            self.img.show()

    def save_stamp(self):
        if self.img:
            self.img.save(self.save_path)


if __name__ == '__main__':

	# 上面的字
    words_up = "深圳叁仁有限公司"
    # 中间的字
    words_mid="编程专用章"
    # 下面的字
    words_down="4778290600"
    # 背景图片路径             (注意:需要替换成自己电脑的图片路径)
    img_wl_path = "电子盖章背景图生成.png"
    # 印章保存路径               
    save_path = "电子盖章背景图生成.png"

    # 执行方法
    stamp = Stamp(words_up=words_up,words_mid=words_mid,words_down=words_down,img_wl_path=img_wl_path,save_path=save_path)
    stamp.draw_stamp()
    stamp.show_stamp()
    stamp.save_stamp()

四、核心代码结构解析

整个功能封装在 Stamp 类中,主要流程:

  1. 初始化参数:设置印章尺寸、颜色、文字内容及样式。
  2. 绘制圆形边框:利用 draw.arc 绘制圆环。
  3. 绘制五角星:通过数学计算五角星顶点坐标,用 draw.polygon 填充。
  4. 绘制弧形文字:将每个字符单独绘制在圆周上,并旋转相应角度。
  5. 叠加纹理:随机截取纹理图,调整大小并映射到透明度通道,增加真实感。
  6. 高斯模糊:对最终图片做轻微模糊,消除锯齿,更接近真实印章效果。

下面我们拆解每一步的实现细节。

4.1 五角星顶点计算

def pentagram(x, y, R, yDegree=0):
    rad = pi / 180
    r = R * sin(18 * rad) / cos(36 * rad)   # 短轴长度
    RVertex = [(x - (R * cos((90 + k * 72 + yDegree) * rad)), ...) for k in range(5)]
    rVertex = [(x - (r * cos((90 + 36 + k * 72 + yDegree) * rad)), ...) for k in range(5)]
    vertex = [x for y in zip(RVertex, rVertex) for x in y]
    return vertex

4.2 弧形文字绘制(核心)

弧形文字是电子印章最具技术难点的部分。代码采用 “先写后旋转再裁剪” 的策略:

def draw_rotated_text(self, image, angle, xy, r, word, fill, font_size, font_xratio, stroke_width, font_flip=False):
    # 1. 创建透明蒙版,大小是图片的4倍,避免旋转时丢失边缘
    mask = Image.new('L', (max_dim*2, max_dim*2), 0)
    draw = ImageDraw.Draw(mask)
    # 2. 在蒙版中心写字(字符位于半径为r的圆周上)
    draw.text(word_pos, word, 255, font=font, stroke_width=stroke_width)
    # 3. 对蒙版进行缩放(x方向压缩,模拟印章字体窄长效果)
    # 4. 旋转指定角度
    rotated_mask = mask.resize(mask_resize).rotate(angle)
    # 5. 裁剪出与原始图片相同大小的区域,并粘贴到目标图片
    color_image = Image.new('RGBA', image.size, fill)
    image.paste(color_image, mask)

关键点:

4.3 文字布局逻辑

印章通常包含三圈文字:

代码中通过计算每个字符的分布角度,循环调用 draw_rotated_text 实现。

4.4 纹理叠加增强真实感

pos_random = (randint(0,200), randint(0,100))
box = (pos_random[0], pos_random[1], pos_random[0]+300, pos_random[1]+300)
img_wl_random = self.img_wl.crop(box).rotate(randint(0,360))
img_wl_random = img_wl_random.resize(img.size).convert('L').filter(ImageFilter.GaussianBlur(1))
# 将纹理灰度映射到目标图片的透明度通道
for h in range(H):
    for l in range(L):
        dot = (l, h)
        img.putpixel(dot, img.getpixel(dot)[:3] + (int(img_wl_random.getpixel(dot)/255*img.getpixel(dot)[3]),))

到此这篇关于基于Python实现制作透明背景的电子印章的文章就介绍到这了,更多相关Python绘制电子印章内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文