资讯动态

用PHP GD库打造电子签章生成类:圆环、星型与弧线文字的实现

发布时间:2026/10/18 3:40:05 来源:尧图企业网站定制
简介这是一份面向 PHP 开发者的电子印章生成类资源专注解决 Web 应用中的圆形印章绘制、五角星摆放、文字旋转等常见需求。基于 GD 库实现适合需要自动生成企业公章、合同签章或个性印章的中级 PHP 工程师参考示例完整可直接套用。资源包为 7z 格式共 3 个文件、2.24MB包括两个 PHP 文件和一个 TTF 字体文件核心类封装了创建圆形印章、绘制五角星、旋转文本、保存与输出图像等方法配合测试文件可快速观察调用效果ttf 字体则用于印章上的中文文字渲染避免环境缺字问题。目前已有 849 人学习下载。通过这份封装读者能拿到类属性与方法的完整设计思路例如印章颜色、星形角度、字体路径等参数如何影响最终效果并可直接修改集成到自己的 PHP 项目中节省从零编写绘图逻辑的时间。1. PHP 印章类把「盖红章」从 PS 里解放出来做合同管理系统的开发者多半都遇到过这种需求系统里要自动给合同附件盖一个公司红章图章上要有单位名称环绕、五角星、编号和日期而且每家公司参数还不一样。某开发者当年接了个项目每天要人工生成 40 多张不同公司的电子签章图第一周用 PS 一张张套模板第二周就崩溃了——图片像素对不齐、字体不一样、名称长了还要手工调整弧度纯体力活。后来用 PHP 的 GD 扩展写了个印章类输入公司名称、编号、日期几组参数几十毫秒就能输出一张透明底 PNG 公章批量跑几百个也不在话下。这个类很小依赖就两样PHP 的 GD 扩展和一套支持中文的 TTF 字体但圆形公章的核心视觉要素——双圆环、五角星、沿圆弧排布的文字、中央横排编号——全都覆盖了。如果你在做 OA 审批流、电子签章平台、或者要给大批量文档盖项目章这份 PHP 印章类可以直接拿来改。2. 技术选型与架构设计为什么是 GD、类怎么拆、配置放哪里2.1 为什么选 GD 而不是 Imagick印章生成这个场景技术选型上第一反应可能是 Imagick因为它功能更全、支持复杂滤镜。但实际评估下来印章绘制本质是「几何图形加文字」连图片缩放都用得少用 Imagick 属于杀鸡用牛刀。GD 是 PHP 官方扩展大部分环境默认开启不需要额外编译和安装内存占用也低生成一张 400x400 的 PNG峰值内存不到 10MB。Imagick 虽然能做出更细腻的阴影和纹理但安装依赖 ImageMagick 库在虚拟主机和容器环境里翻车概率高不少。某开发者的结论是只要不涉及滤镜、不规则遮罩和复杂特效印章类用 GD 足够维护成本低一半。GD 的另一个优势是输出链路短。它原生支持 imagepng、imagejpeg配合 imagesavealpha 可以直接输出带透明通道的 PNG。印章在合同页上的呈现方式是「浮在文档上方」透明背景是硬需求GD 的 alpha 通道处理虽然接口旧一点但稳定可控。2.2 类设计一个可配置的 Stamp 对象这个印章类的核心是一个 Stamp 类用「一次实例化、一次生成、一次输出」的流程组织。构造时接收配置数组后续所有绘制方法都从配置里取参数避免硬编码。类的边界很清晰公开方法只有三个生成、保存、直接输出其余都是私有绘制方法每个方法只负责印章的一个组成部分。class Stamp { protected array $config; protected $canvas; protected $width; protected $height; public function __construct(array $config) { $this-config array_merge($this-getDefaultConfig(), $config); $this-width $this-config[width]; $this-height $this-config[height]; } public function generate(): self { // 依次调用圆环、五角星、环绕文字、中央文字的绘制方法 return $this; } public function save(string $path): bool { return imagepng($this-canvas, $path); } public function output(): void { header(Content-Type: image/png); imagepng($this-canvas); } public function destroy(): void { imagedestroy($this-canvas); } }这里的核心思路是「生成」和「输出」分离。generate() 只负责在内存画布上完成绘制save() 和 output() 分别对应文件落盘和 HTTP 响应两种使用场景。参数说明config 数组在构造时与默认配置合并调用方只需要传入想覆盖的字段。width 和 height 在实例化时就确定后续所有半径比例都基于这两个值计算保证印章在不同尺寸下不变形。2.3 目录结构与配置格式整个组件按「配置、源码、字体、示例」四块组织结构很直观stamp/ ├── assets/ │ └── fonts/ │ └── CNStamp.ttf # 支持中文的TTF字体需自行放入 ├── config/ │ └── stamp.php # 印章样式默认配置 ├── src/ │ └── Stamp.php # 印章主类 ├── examples/ │ └── demo.php # 调用示例 └── output/ # 生成图片的默认输出目录字体文件不放源码目录而是单独放 assets 下是因为实际部署时字体可能需要换版本或按环境切换。config/stamp.php 返回一个纯数组把「圆环半径、星型大小、文字弧跨角度、字体颜色」这些视觉参数全部收敛在一起。某开发者的习惯是所有跟视觉相关的数字都进配置代码里不出现魔法数字这样改印章样式根本不用动类文件。return [ width 400, height 400, outer_ring [ radius_ratio 0.85, // 外环半径占画布宽度比例 thickness 3, // 外环线宽 ], inner_ring [ radius_ratio 0.78, thickness 2, ], star [ radius_ratio 0.32, // 五角星外接圆半径比例 color [200, 16, 16, 0], // RGBA ], arc_text [ span_angle 180, // 环绕文字的跨角180为半圆 font_size 28, radius_ratio 0.64, // 文字所在圆弧半径 ], center_text [ font_size 26, line_gap 8, // 中央文字行间距 ], color [200, 16, 16, 0], // 印章主色默认标准红 font_file __DIR__ . /../assets/fonts/CNStamp.ttf, ];所有半径都用 radius_ratio 表示是相对画布宽度或高度的比例。这样做的好处是当合同模板要求的印章尺寸从 400 调整到 600 时只改 width 和 height 两个参数圆环、星型、文字位置自动按比例缩放不用逐个调坐标。配置数组里的 color 用 RGBA 四元组第四个值 0 表示完全不透明alpha 的 0 到 127 语义后面会专门展开。3. 核心实现拆解从参数到 PNG 的完整绘制链路3.1 画布初始化与透明通道处理印章绘制的第一步是创建一张全透明的画布。这一步最容易踩坑因为 GD 的 imagecreatetruecolor 默认创建黑色背景如果直接在上面画图最终 PNG 会带一个黑色或灰色底框根本没法直接盖到合同页上。正确处理透明通道需要三个动作配合。$canvas imagecreatetruecolor($this-width, $this-height); // 关闭颜色混合否则透明填充会与默认黑色混合 imagealphablending($canvas, false); // 保存 alpha 通道信息PNG 输出时才保留透明 imagesavealpha($canvas, true); // 用全透明颜色填充画布 $transparent imagecolorallocatealpha($canvas, 0, 0, 0, 127); imagefill($canvas, 0, 0, $transparent);关键在 imagealphablending 和 imagesavealpha 的执行顺序。imagealphablending(false) 先关闭颜色混合此时用透明色填充才会真正写入 alpha 值如果顺序颠倒填充操作会把透明色和黑色背景混合得到的画布依然是黑的。imagesavealpha(true) 告诉 GD 在输出 PNG 时保留通道数据少了这一行输出图片的透明区域会被强行填成黑色。参数说明imagecolorallocatealpha 的最后一个参数 127 是 GD 中的最大透明度0 为完全不透明127 为完全透明这里用它来铺底。3.2 双圆环绘制直径、中心与线宽的细节印章的外框由两个同心圆组成外环粗、内环细中间留一条白缝形成公章典型的双线圈结构。GD 的 imageellipse 接受的是「直径」而非「半径」这是最容易传错的地方半径 170 的圆要传 340 作为宽度和高度。$color imagecolorallocatealpha( $canvas, $this-config[color][0], $this-config[color][1], $this-config[color][2], $this-config[color][3] ); $centerX intval($this-width / 2); $centerY intval($this-height / 2); $outer $this-config[outer_ring][radius_ratio] * $this-width; $inner $this-config[inner_ring][radius_ratio] * $this-width; // 外环 imagesetthickness($canvas, $this-config[outer_ring][thickness]); imageellipse($canvas, $centerX, $centerY, intval($outer * 2), intval($outer * 2), $color); // 内环 imagesetthickness($canvas, $this-config[inner_ring][thickness]); imageellipse($canvas, $centerX, $centerY, intval($inner * 2), intval($inner * 2), $color);imagesetthickness 要放在每次绘制前单独设置因为它影响的是「之后」所有线型绘制两个圆环线宽不同就必须各设一次。中心坐标直接用画布宽高的一半不要用半径反推。这段代码的执行结果是外环半径 340、内环半径 312按默认配置 0.85 和 0.78 乘以 400 再乘 2两环之间留出约 28 像素的白色间隔视觉上就是公章外围的经典双线圈。3.3 五角星顶点计算多边形填充的数学推导五角星的绘制是纯数学问题。一个正五角星有 10 个顶点交替分布在外接圆和内切圆上。某开发者当时的做法是外圆半径取画布宽度的 0.32 倍内圆半径取外圆的一半从正上方 -90 度开始每 36 度一个顶点偶数索引用外半径、奇数索引用内半径。$starOuter $this-config[star][radius_ratio] * $this-width; $starInner $starOuter * 0.5; $points []; for ($i 0; $i 10; $i) { // 偶数位是外顶点奇数位是内凹点 $radius ($i % 2 0) ? $starOuter : $starInner; // 从正上方开始每36度分布一个顶点 $angle deg2rad(-90 $i * 36); $points[] $centerX $radius * cos($angle); $points[] $centerY $radius * sin($angle); } imagefilledpolygon($canvas, $points, 10, $color);顶点交替的逻辑是关键i0 时在外圆正上方i1 时跳到内圆顺时针 36 度处形成凹角i2 又回到外圆 72 度处。这样 10 个点围出来就是标准的五角星轮廓最后用 imagefilledpolygon 一次填充。参数说明-90 起始角让星型有一个角指向正上方符合真实公章的视觉习惯内半径取外半径一半是经验值比例太大会让星型发胖太小则尖角过长。如果不需要五角星比如合同专用章有时用五角星有时不用把这个循环放进 if 判断即可。3.4 环绕文字UTF-8 拆分与圆弧坐标计算环绕文字是印章类最复杂、也最需要耐心的部分。单位名称要沿圆弧均匀分布每个字符独立绘制并旋转到切线方向。这里有两个核心点按 Unicode 码位拆分字符串、以及把字符中心精确放到圆弧上。按码位拆分是因为中文字符在 UTF-8 编码下占 3 个字节直接用 str_split 或 strlen 处理会把一个汉字切成 3 段乱码。正确做法是 preg_split 按空字符模式匹配每个 Unicode 字符$text $this-config[unit_name]; $chars preg_split(//u, $text, -1, PREG_SPLIT_NO_EMPTY); $charCount count($chars); if ($charCount 1) { $charCount 2; // 防止除零 } $spanAngle $this-config[arc_text][span_angle]; // 180 $stepAngle $spanAngle / ($charCount - 1); $arcRadius $this-config[arc_text][radius_ratio] * $this-width; $fontSize $this-config[arc_text][font_size];然后逐字符计算坐标和旋转角。这里的一个关键设计是正上方为 0 度左侧为负、右侧为正字符分布在 -90 到 90 度范围内。每个字符的目标坐标用 sin 和 cos 映射到圆弧上旋转角度则是 90 减去偏转角让字符始终垂直于弧线切线foreach ($chars as $index $char) { // 偏转角从-90度到90度均分 $angle -($spanAngle / 2) $index * $stepAngle; $angleRad deg2rad($angle); // 字符中心在圆弧上的目标位置 $targetX $centerX $arcRadius * sin($angleRad); $targetY $centerY - $arcRadius * cos($angleRad); // 旋转角GD角度为逆时针正上方字符旋转90度保持正立 $textAngle 90 - $angle; // 获取单字符包围盒算出中心偏移量 $box imagettfbbox($fontSize, $textAngle, $fontFile, $char); $offsetX ($box[0] $box[2]) / 2; $offsetY ($box[1] $box[3]) / 2; imagettftext( $canvas, $fontSize, $textAngle, intval($targetX - $offsetX), intval($targetY - $offsetY), $color, $fontFile, $char ); }imagettfbbox 返回的 8 个点坐标描述了字符旋转后的包围盒box[0]、box[1] 是左下角box[2]、box[3] 是右下角。把中心偏移量算出来再减去才能让字符的几何中心精确落在目标坐标上否则所有字符都会向右下方偏移形成一种「沿着弧线但偏了」的错位感。参数说明textAngle 的正负规则是逆时针为正所以正上方angle0的字符旋转 90 度后是横躺状态配合读取方向后才会呈现横向排列的印章文字效果。实际微调时把 90 改成 80 或 100可以看到文字整体沿圆弧旋转这个值就是留给不同字体和阅读习惯的微调旋钮。3.5 中央文字与编号imagettfbbox 的基线对齐环绕文字之外印章中央一般还有一行横排文字常见的是「合同专用章」「项目专用章」或一串编号。中央文字不用旋转难点在于水平居中——imagettftext 的 x、y 坐标是字符「基线起点」而非「几何左上角」直接用字符串长度推位置永远会偏。$centerTexts explode(\n, $this-config[center_text]); $lineHeight $this-config[center_text][font_size] $this-config[center_text][line_gap]; $startY $centerY - ($lineHeight * (count($centerTexts) - 1)) / 2; foreach ($centerTexts as $i $lineText) { $box imagettfbbox($fontSize, 0, $fontFile, $lineText); // 文本像素宽度右下角x - 左下角x $textWidth abs($box[2] - $box[0]); $textHeight abs($box[5] - $box[3]); $x $centerX - $textWidth / 2 - $box[0]; $y $startY $i * $lineHeight $textHeight / 2 - $box[1]; imagettftext($canvas, $fontSize, 0, intval($x), intval($y), $color, $fontFile, $lineText); }这块讲两个容易被忽略的细节。第一imagettfbbox 返回的坐标原点在字符左下角位置可能是负数所以水平居中要减去 box[0] 而不是直接用 textWidth 除以 2。第二垂直方向上 y 传入的是基线位置要让文字垂直居中得用「几何中心 基线 高度一半」反推。支持多行是因为有些印章中央有两行内容比如第一行是编号、第二行是日期用换行符分隔最省事。参数说明line_gap 控制多行间距实际就是字号加上这个值作为行高。调小行距会让两行文字更紧凑适合「编号 日期」的组合排版。3.6 完整调用示例生成一张章的调用方式很短。构造、生成、保存、销毁四步走require_once __DIR__ . /../src/Stamp.php; $stamp new Stamp([ unit_name 某某科技有限公司, center_text 合同专用章\n2026-03-14, width 400, height 400, ]); $stamp-generate(); $stamp-save(__DIR__ . /../output/stamp_demo.png); $stamp-destroy();值得一提的是一次性脚本的写法save 之后要立即 destroy 释放画布内存因为 GD 图像对象在 PHP 请求结束后虽然会自动回收但批量生成几百张图时不及时释放会导致内存峰值飙升直接打满 128M 的默认限制。实际跑批任务时某开发者习惯每生成 50 张就调一次 gc_collect_cycles效果明显。4. 常见问题与避坑记录字体、透明度和坐标系三座大山4.1 输出黑底或印章整体消失现象imagepng 输出的 PNG 打开后背景是纯黑色或者印章图形完全看不见只有一团暗色。原因创建画布后没有正确处理透明通道。imagecreatetruecolor 默认黑色背景直接 imagecolorallocatealpha 填充透明色时如果 imagealphablending 还是默认的 true透明色会和黑色背景做颜色混合结果就是黑底。另一类是 imagesavealpha 没开启GD 输出 PNG 时会丢弃 alpha 通道透明区域硬编码成黑色。解决严格按照「先 imagealphablending(false) → 再 imagesavealpha(true) → 再填充透明色」的顺序操作三行代码顺序不能换。如果从别处复制代码务必检查这两个设置是否在 imagefill 之前执行。4.2 单位名称的中文全部变成方框现象英文字母和数字正常所有汉字渲染成方块或问号像是字体「失效」了。原因GD 的 imagettftext 本身不做字体回退它严格按照 font_file 加载字体文件。如果提供的 TTF 不支持中文很多西文字体就是这样或者字体文件路径写错、GD 静默加载失败就会用内置的 fallback 字形渲染输出就是方框。另一个经常翻车的地方是用 strlen 统计中文字符数一个汉字在 UTF-8 下占 3 字节字符计数直接膨胀 3 倍整个圆弧分布直接乱套。解决确认字体文件支持中文可以用 fontforge 或直接看文件大小——一个完整的中文 TTF 至少 3MB 以上几百 KB 的字体基本可以断定不含中文。字符串拆分统一用 preg_split(//u) 或 mb_str_split不要碰 strlen 和 str_split。判断字体的另一个笨办法先用 imagettftext 画一个「中」字到临时画布输出后肉眼看是否正常这是最直接的验证。4.3 环绕文字间距忽大忽小、字符重叠现象单位名称沿圆弧分布不均匀有的字挤在一起有的字之间空出一大截有的字符甚至叠在五角星上。原因最常见的是字符计数错误导致角度步长计算失真。另外步进按「角度均分」而非「弧长均分」时不同字符自身宽度不同设了固定间距就会出现视觉上的疏密不一。还有一坑是圆弧半径配得太大文字直接顶到外环上重叠是必然结果。解决先用 mb_strlen 拿到真实字符数再算 stepAngle再把每个字符的 imagettfbbox 宽度纳入计算实现按弧长动态步进第 3.4 节的代码是基础版二次开发中把 stepAngle 改成累加弧长即可。如果文字顶到外环优先调低 arc_text.radius_ratio从 0.64 降到 0.58 立竿见影。4.4 左侧文字倒置、右侧文字正常现象印章顶部的文字正立但左侧的文字看起来「头朝下」整个圆弧文字有一半是倒的。原因GD 的 imagettftext 的 angle 参数定义为逆时针为正且旋转轴在字符基线起点而非中心。直接拿偏转角赋值给 textAngle左侧字符负角度区域会因为基数加负号而翻转 180 度。解决统一用 90 - $angle 计算 textAngle保证所有字符沿切线方向站立。如果字体特殊、个别字符看起来歪单独调整某个角度的修正量——把计算逻辑里的常量 90 提取成 arc_text.tilt_offset 配置项这样不用改代码就能微调。5. 二次开发思路模板化、批量流水号与接口化改造5.1 扩展一从圆形公章到椭圆合同章圆形公章能满足一半场景但不少合同模板要求的是椭圆形的合同专用章。改造思路其实简单把画布宽高比例从 1:1 改成 1.4:1然后所有半径计算从「按宽度比例」改成「按短边比例」。具体做法是在配置里加一个 shape 参数generate() 方法里根据 shape 分派绘制逻辑。椭圆绘制用 imageellipse 本身就能画椭圆关键是把五角星的长宽坐标做非等比缩放——五角星在椭圆章里不能是正圆要按短轴比例压扁否则看起来是「圆章被拉伸」而不是一枚自然的椭圆章。5.2 扩展二批量流水号与防伪编码项目章经常需要带编码比如「编号2026-0037」而且每次生成的图编码要递增。这个需求最适合在配置层解决给 Stamp 类加一个 append_number 方法把编号追加到中央文字第二行。批量生成时循环里更新 config[center_text] 再重新 generate 即可。更讲究一点的玩法是把编号改成防伪微缩码——在印章外环内部加一圈小号文字字体大小 8 到 10颜色与主色一致肉眼不细看看不出来但扫描放大后能识别。实现上就是第 3.4 节的环绕文字逻辑改小字号、加大半径另画一圈。5.3 扩展三接口化把印章类包装成 HTTP 服务如果印章生成要接入到现有的合同上传流程最省事的做法是包一层 HTTP 接口。接收 JSON 参数返回 PNG base64。这种封装只改调用层不动 Stamp 类本身四五个方法就能跑起来。某开发者的做法是输出前强制走一遍内存再 base64不落盘、不产生临时文件$stamp-generate(); ob_start(); imagepng($stamp-canvas); $imageData ob_get_clean(); $stamp-destroy(); echo json_encode([ code 0, data data:image/png;base64, . base64_encode($imageData), ]);三个扩展方向的改动点和适用场景如下扩展方向改动位置适用场景椭圆章模板config 加 shape五角星坐标非等比缩放合同专用章、购销协议批量流水号循环更新 center_text加防伪小字圈报告编号、批量单据HTTP 接口化包一层路由和 JSON 响应对接 OA 系统、在线签章某开发者的习惯是每次加新样式前先把配置数组复制一份改参数跑一个单张样本确认视觉再落代码。这样做最大的好处是图像类代码的调试循环短配置驱动比逻辑驱动更容易定位问题——十次有九次是参数不对而不是算法有 bug。后来给任何项目加「改印章样式」的需求都先翻一遍配置而不是翻代码。从那以后凡是涉及图像生成的代码都强制先输出单张样本检查再批量跑这条规矩帮某开发者躲过了很多次「批量生成完才发现字体错了」的血泪教训。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑