ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用Python和Pillow构建艺术签名生成器:从字体渲染到API封装

用Python和Pillow构建艺术签名生成器:从字体渲染到API封装 简介这是一份面向Python初中级开发者的艺术签名生成器项目源码解决用户输入姓名后自动生成个性化签名并导出图片的需求。项目基于Tkinter搭建图形界面借助Pillow库处理字体与图像支持加载不同字体文件并综合运用字体大小、旋转角度、笔画粗细等变换结合math库中的贝塞尔曲线与random随机参数让每次生成的签名风格独特适合电子文档、品牌标识等场景。代码结构涵盖GUI事件绑定、PIL图像绘制、数学建模、随机化处理、文件序列化、异常捕获以及单元测试等多个核心模块通过完整项目实践可帮助读者掌握Python桌面应用开发的整体思路并具备进一步扩展为自定义字体签名导出器的能力。压缩包为zip格式大小约371KB下载页暂未显示具体文件数量与类型但资源主题明确为Python脚本项目源码。目前已有417人学习/下载非常适合希望通过项目提升编程与图形设计能力的初学者参考。1. 艺术签名生成器这个 Python 脚本到底在解决什么问题做 PPT、短视频封面、公众号文章头图时经常要放一张“手写感”签名用来落款或装饰。找设计出图要等排期用网上的签名生成站又担心分辨率不够、水印和个人信息被存走。于是很多开发者会在本地写一个 Python 脚本把艺术签名生成器拆成三件事输入姓名、指定风格、输出 PNG。这个方向并不需要深度学习Pillow 的字体引擎加上几个图像变换就够用代码量通常控制在一两百行以内。它适合正在练项目的 Python 入门者也适合需要批量生成签名素材、水印文件或装饰文字的前端和后端工程师。2. 用 Pillow 搭建最小签名生成脚本文字渲染与字体选择2.1 为什么先从 Pillow 而不是 Matplotlib 或 OpenCV 开始Pillow 是处理这类图像最顺手的库内部直接封装 FreeType传入字体文件路径就能按 Unicode 渲染中文渲染结果天然带透明通道。Matplotlib 也能画文字但保存流程、坐标体系和字体管理对做签名这件事是绕路OpenCV 更擅长像素处理但在字体排版上反而要自己拼。常见做法是先创建临时画布测量文字包围盒再按包围盒创建正式画布这样不管输入的是两个字还是四个字的姓名图片都不会把字截断。更关键的是Pillow 的字体渲染是“所见即所得”字体文件的每个字形直接决定输出效果不需要训练或预处理。艺术签名生成器里的“艺术感”很大程度来自你选的那一个字体文件而不是像素算法。2.2 最小可运行代码姓名转签名图片先写一个不追求任何装饰的最小脚本把流程跑通。import platform from PIL import Image, ImageDraw, ImageFont def load_font(font_pathNone, size120): 从参数或系统字体中加载一个支持中文的字体。 if font_path: return ImageFont.truetype(font_path, size) system platform.system() if system Windows: return ImageFont.truetype(C:/Windows/Fonts/msyh.ttc, size) if system Darwin: return ImageFont.truetype(/System/Library/Fonts/PingFang.ttc, size) return ImageFont.truetype(/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc, size) def generate_signature(name, fontNone, size120, outsignature.png): font font or load_font(sizesize) tmp Image.new(RGB, (10, 10)) draw ImageDraw.Draw(tmp) bbox draw.textbbox((0, 0), name, fontfont) padding 30 w bbox[2] - bbox[0] padding * 2 h bbox[3] - bbox[1] padding * 2 img Image.new(RGB, (w, h), white) draw ImageDraw.Draw(img) draw.text((padding - bbox[0], padding - bbox[1]), name, fontfont, fillblack) img.save(out) if __name__ __main__: generate_signature(张三)这段代码是整个项目源码的第一块地基。它用textbbox拿到文字最小外接矩形而不是直接用font.getbbox因为后者的坐标定义在不同 Pillow 版本里有差异。绘制时把坐标偏移到padding - bbox[0]和padding - bbox[1]文字内容就会刚好落在画布中央。这里有几个需要注意的参数。size决定图片物理尺寸120 合适预览如果要打印或放大建议设置到 300 以上。msyh.ttc是微软雅黑字重均匀但没有任何手写感只用来验证流程真正做艺术签名要换成行书、草书或手写体字体文件。textbbox是 Pillow 8.0 之后的 API老项目里常见的textsize已不建议使用。2.3 字体选择与显示前提生成艺术签名最重要的不是代码而是字体。同一个“张三”用黑体渲染和用毛笔行书渲染视觉差距巨大。字体文件必须覆盖姓名中的全部字符否则会出现方框或空白。下面是几个核心参数的建议参数推荐值说明字体路径WindowsC:/Windows/Fonts/STXINGKA.TTFLinux/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc选择造型夸张的手写体艺术效果更强font_size200 以上图片像素随字号增大后期缩小比放大更清晰前景色纯黑或深灰#333333模拟墨迹时不要用纯黑透明度叠加后更有层次背景色白色或透明合成到其他设计稿时用透明背景更方便输出格式PNG不要用 JPGJPEG 会在文字边缘产生压缩噪点如果 Linux 服务器没有安装中文字体运行脚本会直接抛出OSError: cannot open resource。先用fc-list :langzh查看已有字体确认路径后写死或者把字体文件放到项目fonts/目录下这样换机器也不会崩。另一个经验是把字体文件放到项目里时和“个人学习使用”的字样一起保留很多免费字体并不允许商业分发这个边界要提前确认。3. 让签名更耐看逐字旋转、基线摆动与连笔曲线3.1 “艺术”在哪里打破规整的排版为什么直接渲染出来的直排文本不像签名因为标准字体排版是水平对齐、字距均匀的这是为可读性设计的规则手写签名讲究的是“不稳定”每个字角度不完全一致字与字之间高低起伏笔画之间常有自然回笔。艺术签名生成的本质就是把整段文字拆成单个字符逐字做旋转和位移再在字与字之间补一条平滑的连笔曲线。这个做法避开了 Pillow 没有手写画笔的问题同时保留了字体本身的笔画结构。整行旋转和逐字旋转有个常被忽略的差异整行旋转得到的是整体倾斜但内部仍然规则排版的图看起来像贴歪的海报而不是签名逐字旋转会在每个字的重心附近做小角度偏转这才像手写过程中笔尖方向不断调整。3.2 逐字渲染从整体绘制改成字符级合成把每个字渲染到独立 RGBA 画布再旋转、移动、叠回大画布。import random from PIL import Image, ImageDraw, ImageFont class ArtSignGenerator: def __init__(self, font_path, font_size180, rotation_range12, baseline_drift14, spacing_jitter22, ink_opacity180, seed42): random.seed(seed) self.font ImageFont.truetype(font_path, font_size) self.font_size font_size self.rot_rng rotation_range self.drift baseline_drift self.spacing spacing_jitter self.ink ink_opacity def render_char(self, ch): pad self.font_size layer Image.new(RGBA, (pad * 2, pad * 2), (0, 0, 0, 0)) draw ImageDraw.Draw(layer) draw.text((pad, pad), ch, fontself.font, fill(0, 0, 0, self.ink), anchormm) bbox draw.textbbox((pad, pad), ch, fontself.font, anchormm) margin 6 box (bbox[0] - margin, bbox[1] - margin, bbox[2] margin, bbox[3] margin) char_img layer.crop(box) angle random.uniform(-self.rot_rng, self.rot_rng) return char_img.rotate(angle, expandTrue, resampleImage.Resampling.BICUBIC) def render_full(self, text, with_lineTrue): chars [self.render_char(ch) for ch in text] widths [c.width random.randint(0, self.spacing) for c in chars] total_w sum(widths) self.font_size total_h max(c.height for c in chars) self.font_size canvas Image.new(RGBA, (total_w, total_h), (0, 0, 0, 0)) x self.font_size // 2 prev_anchor None for i, char in enumerate(chars): y self.font_size // 2 random.randint(-self.drift, self.drift) if with_line and prev_anchor: self._connect(canvas, prev_anchor, (x, y)) canvas.alpha_composite(char, (x, y)) prev_anchor (x char.width, y char.height // 2) x widths[i] return canvasrender_char先把字画在足够大的透明画布中央anchormm表示给定坐标是文字中心点。随后用textbbox裁掉多余空白再围绕中心旋转。旋转使用BICUBIC重采样边缘抗锯齿比最近邻更好。render_full里维护prev_anchor记录上一个字符右端和下边缘中点作为连笔起点。这一步最容易犯的错是用canvas.paste(char, (x, y))粘贴。对 RGBA 图层来说paste默认不做 alpha 合成透明区域会变成黑块。处理透明图层粘贴用alpha_composite或者给paste传maskchar.split()[-1]结果才是干净的字形。3.3 字间连笔曲线生成_connect画一条从上一字出口到下一字入口的二次贝塞尔曲线。Pillow 的ImageDraw不提供贝塞尔工具但可以用线性插值逐点模拟。def _connect(self, canvas, start, end, width7): draw ImageDraw.Draw(canvas) mid ((start[0] end[0]) // 2, min(start[1], end[1]) - random.randint(8, 30)) points [start] for step in range(1, 21): t step / 20 u 1 - t x u * u * start[0] 2 * u * t * mid[0] t * t * end[0] y u * u * start[1] 2 * u * t * mid[1] t * t * end[1] points.append((int(x), int(y))) draw.line(points, fill(0, 0, 0, self.ink - 40), widthwidth)mid取两点中点再把 y 坐标向上抬高 8 到 30 像素连接线会有向上拱的弧线模仿硬笔书写时快速连笔的轨迹。透明度比文字低 40视觉上弱化连接线避免抢了正字风头。如果连笔越过文字边界把width改小到 3 或 4或者让mid方向反过来都能快速修正。不同姓名笔画走势不同想要稳定效果建议固定seed每次调整后对比输出。3.4 风格参数怎么调节rotation_range控制旋转强度12 度以内像手写超过 25 度会显得杂乱。baseline_drift控制上下浮动8 到 16 像素比较自然。spacing_jitter控制字距不均匀度太小和默认排版一样规矩太大会把签名拉得很散。ink_opacity控制墨色180 左右模拟略干笔触255 是浓黑。这几个参数不需要一次调到位常见做法是先用一组默认值批量生成十张挑出接近手写感觉的比例再对单字微调。4. 把脚本封装成可批量使用的命令行工具参数表与导出格式4.1 为什么需要命令行入口核心类能生成一张图后真正用起来会发现一次只生成一张、手动改参数、手动命名文件非常依赖 IDE。给艺术签名生成器加命令行入口后可以一次输入多个姓名配合 shell 脚本批量处理名单。这个封装也让脚本变成可复用模块其他项目只要from main import gen就能调用。4.2 命令行参数设计建立main.py用argparse定义参数。import argparse from pathlib import Path from PIL import Image from signature_generator import ArtSignGenerator def build_parser(): p argparse.ArgumentParser(description艺术签名生成器命令行) p.add_argument(names, nargs, help要生成签名的姓名支持多个) p.add_argument(--font, requiredTrue, help字体文件路径) p.add_argument(--font-size, typeint, default180, help字号) p.add_argument(--rotation, typeint, default12, help单字旋转幅度) p.add_argument(--seed, typeint, default42, help随机种子复现结果) p.add_argument(--bg, defaultwhite, choices[white, transparent], help背景类型) p.add_argument(--outdir, defaultoutput, help输出目录) return p def main(): args build_parser().parse_args() Path(args.outdir).mkdir(parentsTrue, exist_okTrue) gen ArtSignGenerator(args.font, font_sizeargs.font_size, rotation_rangeargs.rotation, seedargs.seed) for name in args.names: img gen.render_full(name) if args.bg white: bg Image.new(RGBA, img.size, white) bg.alpha_composite(img) img bg out Path(args.outdir) / f{name}_sign.png img.convert(RGB if args.bg white else RGBA).save(out) print(f[ok] {name} - {out}) if __name__ __main__: main()这段代码里有一个容易被忽略的细节如果背景设为 white不要直接在原始画布上填充白色因为原始画布尺寸是由连笔曲线决定的填充后连笔可能超出白底范围。正确的顺序是先生成签名图再创建同样尺寸的白底图用alpha_composite合成。透明背景则直接保存 RGBA。完整的参数表如下参数示例作用--fontfonts/STXINGKA.TTF必填签名实际使用字体--font-size240字号影响图片清晰度与打印尺寸--rotation8单字旋转范围0 为不旋转--seed7随机种子同参数可复现--bgtransparent背景类型透明图方便设计稿合成--outdiroutput生成目录脚本自动创建4.3 shell 脚本批量调用与格式扩展命令行封装好后可以配合 shell 脚本使用。把姓名列表放在文本文件里逐行读取调用while IFS read -r name; do python main.py $name --font fonts/sign.ttf --seed 11 --bg transparent done names.txt这里的循环每次重新加载字体文件速度会慢一些更高效的是在 Python 里做批量模式但 shell 方式的好处是方便实时观察哪个人名失败也能单独重跑失败项。如果要在不同电脑生成完全一致的结果固定seed只是第一步还要保证 Pillow 版本和字体文件一致。字体文件一旦替换笔画全部变化seed无法兜底。Pillow 还支持保存为 PDF。生成的签名要用于打印签字页时白底图转 RGB 后保存为.pdf后缀Pillow 可以按单页输出 PDF。这样整个流程就不仅生成 PNG还能直接进入打印场景。5. 排错与再进一步中文乱码、透明通道与签名 API 封装5.1 高频错误中文字体缺失和路径错误脚本跑出来是空白或方框优先检查字体是否真的有这个字形。Windows 上常见手写风格字体因人而异不能直接沿用别人的路径Linux 上用fc-list :langzh确认有没有中文字体没有就去安装 Noto Sans CJK。字体路径写错时ImageFont.truetype会抛OSError排查时可以在构造函数里先打印一下读取路径确认运行环境。5.2 Pillow 版本差异和透明通道问题老项目里常见的textsize在 Pillow 10 已经移除统一用textbbox或textlength。升级依赖后如果报AttributeError说明调用了旧 API。旋转时用的Image.Resampling.BICUBIC是 Pillow 9.1 之后的写法老版本需要兼容try: RESAMPLE Image.Resampling.BICUBIC except AttributeError: RESAMPLE Image.BICUBIC透明通道出现问题的最典型现象是签名文字周围出现黑色方块。原因几乎都是把 RGBA 字符图直接paste到目标图。统一改用alpha_composite如果确实要用paste就传maskim.split()[-1]。合成白底和合成到设计稿时都要记住。5.3 把生成器封装成一个签名 API如果要在博客或内部系统提供在线签名能力用 Flask 包一层是最直接的方式。import io from flask import Flask, request, send_file from signature_generator import ArtSignGenerator app Flask(__name__) font fonts/sign.ttf app.route(/sign) def sign(): name request.args.get(name, ).strip() if not name: return {error: name is required}, 400 gen ArtSignGenerator(font, seed7) img gen.render_full(name) buf io.BytesIO() img.save(buf, formatPNG) buf.seek(0) return send_file(buf, mimetypeimage/png) if __name__ __main__: app.run(host0.0.0.0, port8000)接口接收查询参数name返回 PNG 图片。生产环境要加入限流和缓存同一姓名可以缓存结果避免每次请求重新渲染。验证输出时也有一个实用技巧批量生成后检查 PNG 的 alpha 通道范围例如alpha img.split()[-1]再调用getextrema()如果返回(0, 0)说明画布上没有任何内容问题基本出在字体加载或姓名为空这两处。本文还有配套的精品资源点击获取
返回列表