简介:面向PHP开发者的电子印章生成类资源,核心封装了圆形印章绘制、五角星旋转、文字环绕与角度控制等常用能力,适合需要在Web应用中快速生成带公章效果的图片,或学习GD库图像处理的中级开发者。压缩包共3个文件,包含两个PHP类文件与一个楷体字体文件(ttf),约2.24MB。其中类文件负责图像生成逻辑,字体文件用于指定印章上的中文显示效果,结构精简,可直接参考或嵌入项目。资源描述中详细说明了从自定义类属性(颜色、字体、角度、直径)到方法(创建圆形印章、绘制五角星、旋转文本、保存与显示)的完整实现思路,并附有可运行的调用示例,读者可借此理解GD库绘图、真彩色字体渲染与图像输出等关键环节。目前已吸引849人浏览学习,适合希望快速掌握PHP动态生成图片印章的开发者下载参考。
1. PHP印章类项目:办公系统里最刚需的一个小功能
之前做OA审批流程时遇到一个很实际的需求:合同审批走到最后一步,要在电子版上盖公章。选了一圈第三方签章服务,费用高且对接麻烦,最后找到这个PHP印章类开源项目来改造。它本质上是一套基于GD库的PHP类库,配置公司名称、印章用途、字体、颜色等参数后,在服务端直接生成一张带透明背景的圆形PNG印章,能无缝叠加到合同签字页、审批单据或者报销单上。适合两类人:一是PHP后端开发,想在自己业务系统里嵌入一键盖章能力;二是正在做电子签、合同归档、OA流程印章自动化的开发者。它不依赖前端Canvas,也不依赖任何第三方API,纯服务端生成,拿过来就能跑,改起来也很快。
2. 架构与目录:一个PHP类怎么从配置走到PNG图片
2.1 目录结构与职责划分
这个项目的目录划分很直观,核心代码全部收敛在src目录里,字体、示例、输出目录独立放,职责边界比较清楚。我当时拿到源码第一件事就是画目录树,把每个文件对应到“配置-渲染-输出”这条链路上,后面改起来就快很多。
seal/ ├── src/ │ ├── Seal.php // 门面类,对外唯一入口 │ ├── SealConfig.php // 配置对象,所有可调参数 │ └── Renderer/ │ ├── SealRenderer.php // 渲染器接口,定义render契约 │ └── CircleSealRenderer.php // 圆形印章渲染实现 ├── fonts/ │ └── SourceHanSansCN-Medium.ttf // 中文字体,避免系统字体缺失 ├── examples/ │ └── demo.php // 可直接运行的示例脚本 └── output/ └── seal.png // 渲染结果输出目录Seal.php是门面类,只暴露一个render方法,内部按type字段选择具体渲染器;SealConfig.php是参数持有者,保存公司名、印章类型、尺寸、颜色、字体路径等配置;Renderer目录下是渲染器实现,目前主要是CircleSealRenderer,也就是圆形印章。fonts目录专门放了一个思源黑体TTF,原因是GD库渲染中文必须依赖本机字体文件,不带字体的话换个服务器就乱码,这个细节直接决定项目能不能跨机器跑。
2.2 调用链与类协作方式
整个项目的调用链非常短,从业务代码到图片生成只经过三层,这也是我比较喜欢它的地方。业务侧不用关心GD库的坐标计算和透明通道处理,只需准备一个SealConfig对象,丢给Seal即可。
use Seal\Seal; use Seal\SealConfig; $config = new SealConfig(); $config->company = '某科技有限公司'; // 环绕文字 $config->usage = '合同专用章'; // 中心下方文字 $config->type = SealConfig::SEAL_CIRCLE; $config->width = 300; // 画布宽度(像素) $config->height = 300; // 画布高度(像素) $config->borderWidth = 3; // 外边框粗细 $config->fontSize = 24; // 正文字号 $config->centerFontSize = 20; // 中心文字字号 $config->color = '#C7000B'; // 印章红 $seal = new Seal($config); $result = $seal->render()->save('/tmp/seal.png');这里有一个我后来认为很关键的设计:render方法返回的是一个SealResult对象而不是直接写文件,这样既可以把图片保存成文件,也可以转成Base64字符串走网络传输,甚至转成Data URI直接塞给前端img标签。如果没有这层封装,业务侧就得自己用ob_start配合imagepng去接缓冲区,代码会散得到处都是。
render内部的执行顺序是固定的:校验配置合法性,根据type实例化渲染器,渲染器调用GD库画图,最后包装成SealResult返回。每一步的异常都会在Seal层统一捕获并转为RuntimeException抛出,方便业务侧做统一异常处理。
2.3 配置参数说明
我把经常动的配置参数整理成了一张表,方便对照修改。需要注意GD库默认用像素作为单位,所以所有尺寸类参数都是整数,颜色统一用十六进制字符串,内部再转成GD的RGBA数组。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| company | string | 无 | 印章外圈公司全称,必须设置 |
| usage | string | 空 | 印章中心下方的用途文字,如合同专用章、财务专用章 |
| type | int | SEAL_CIRCLE | 印章类型,预留扩展点 |
| width / height | int | 300 | 画布尺寸,方形印章通常相等 |
| borderWidth | int | 3 | 外圆边框像素宽度 |
| fontSize | int | 24 | 外圈文字字号 |
| centerFontSize | int | 20 | 中心用途文字字号 |
| color | string | #C7000B | 印章主色,默认是标准印章红 |
| fontPath | string | 内置字体 | 中文字体路径,默认指向fonts目录 |
参数命名还是偏直白的,基本看到名字就知道是什么。唯一要注意的是fontPath,如果你的生产环境对字体有合规要求,可以直接替换成自己授权过的字体文件,不需要改任何代码。
3. 核心实现拆解:从GD画布到圆形印章的关键代码
3.1 初始化透明画布:背景必须是“没有”,不是“白色”
印章图片最重要的一点是背景透明,否则盖到合同上会压掉底下的正文。刚接手这个项目时,我在这一步栽过跟头:用imagecreatetruecolor创建的画布默认是黑色不透明的,直接在上面画图,最终输出PNG时黑底全露出来了。正确做法是创建画布后立刻关闭alpha混合、设置保存alpha通道,再用透明色填充整个画布。
// 创建真彩色画布 $image = imagecreatetruecolor($width, $height); // 关键步骤:先关闭混合,保证填充的透明色不会被合成 imagealphablending($image, false); // 告诉GD输出PNG时保留alpha通道 imagesavealpha($image, true); // 分配一个完全透明的颜色并填充全图 $transparent = imagecolorallocatealpha($image, 0, 0, 0, 127); imagefill($image, 0, 0, $transparent); // 后续绘制字符和图形时重新开启混合模式 imagealphablending($image, true);这里面的逻辑顺序不能乱。imagesavealpha必须在imagefill之前设置,否则填充的透明色会被当作不透明颜色处理。imagecolorallocatealpha最后一个参数是透明度,取值范围0到127,127表示完全透明。填充完成后重新打开alphablending,是为了让后续画的文字和圆框边缘有抗锯齿效果,不然线条边缘会出现明显的像素颗粒。
3.2 圆弧文字排列:坐标、角度、基线三件事一次搞清楚
圆形印章最核心的算法就是把公司名按圆弧排列在外圈。最初我用最笨的方式,把公司名拆成单个字符逐个用imagettftext画,结果文字方向全乱了,有的字横躺、有的字倒立。后来理清了imagettftext的angle参数规则才明白:它在GD里是逆时针为正的度数,而印章文字需要每个字都保持“顶部朝圆心外”的姿态。
// 将公司名拆成单字符数组 $chars = mb_str_split($config->company); $total = count($chars); // 圆弧半径:外圆半径减去字符高度的一半 $charRadius = $borderRadius - $config->fontSize * 0.7; // 从正上方开始,顺时针均匀分布 for ($i = 0; $i < $total; $i++) { // 当前字符所在角度(度数制,-90度为正上方) $angleDeg = -90 + (360 / $total) * ($i + 0.5); $angleRad = deg2rad($angleDeg); // 字符中心的坐标,注意GD的Y轴向下 $x = $cx + $charRadius * cos($angleRad); $y = $cy + $charRadius * sin($angleRad); // 文字跟随圆弧方向旋转 $charAngle = -($angleDeg + 90); // 用imagettfbbox测出字符实际宽高,做居中补偿 $bbox = imagettfbbox($config->fontSize, $charAngle, $fontPath, $chars[$i]); $width = abs($bbox[2] - $bbox[0]); $height = abs($bbox[7] - $bbox[1]); // imagettftext的x是基线起点,y是基线位置,需要补偿到字符中心 $xPos = $x - $width / 2; $yPos = $y - $height / 2; imagettftext( $image, $config->fontSize, $charAngle, $xPos, $yPos, $textColor, $fontPath, $chars[$i] ); }这段代码里最值得琢磨的是三个角度。第一个是极坐标角度angleDeg,它决定字符“摆在圆环的哪个位置”;第二个是旋转角度charAngle,它决定字符“朝哪个方向站立”;第三个是imagettfbbox返回的边界框,它决定“补偿几个像素才能让字符中心正好落在极坐标点上”。三个角度配合好,文字才会既均匀分布又全部正立可读。
如果公司名很长,比如超过15个字,可以按字符数量反推合适的fontSize,公式是fontSize = 圆周长 / 字符数 * 0.8。我通常会在渲染前加一道自适应逻辑,避免大公司名被挤出圆外。
3.3 五角星、内外边框与防伪线的绘制
圆形印章中间的五角星是识别度的关键。GD没有直接画五角星的函数,需要用imagefilledpolygon按十个顶点交替使用外半径和内半径来填充。内半径与外半径的比例用0.382比较接近标准五角星形状。绘制顺序上要先画五角星,再画边框,最后画防伪线,这样层次感更接近真实印章。
// 绘制中心五角星,starRadius约为画布半径的25% $starPoints = []; $starRadius = $borderRadius * 0.25; $innerRadius = $starRadius * 0.382; for ($i = 0; $i < 10; $i++) { $rad = deg2rad(-90 + $i * 36); $radius = ($i % 2 === 0) ? $starRadius : $innerRadius; $starPoints[] = $cx + $radius * cos($rad); $starPoints[] = $cy + $radius * sin($rad); } imagefilledpolygon($image, $starPoints, (int)(count($starPoints) / 2), $textColor); // 外边框:粗线圆 imagesetthickness($image, $config->borderWidth); imageellipse($image, $cx, $cy, $borderRadius * 2, $borderRadius * 2, $textColor); // 内边框:细线圆,用于圈住外圈文字 imagesetthickness($image, 1); imageellipse($image, $cx, $cy, ($borderRadius - $config->fontSize) * 2, ($borderRadius - $config->fontSize) * 2, $textColor); // 防伪线:外圈内侧一圈随机波动短线 $segments = 72; for ($i = 0; $i < $segments; $i++) { $angle1 = deg2rad(360 / $segments * $i); $angle2 = deg2rad(360 / $segments * ($i + 1)); $r1 = $borderRadius - 6 + rand(-2, 2); $r2 = $borderRadius - 6 + rand(-2, 2); imageline( $image, $cx + $r1 * cos($angle1), $cy + $r1 * sin($angle1), $cx + $r2 * cos($angle2), $cy + $r2 * sin($angle2), $textColor ); } // 中心下方绘制用途文字,比如“合同专用章” $bbox = imagettfbbox($config->centerFontSize, 0, $fontPath, $config->usage); $textWidth = abs($bbox[2] - $bbox[0]); imagettftext( $image, $config->centerFontSize, 0, $cx - $textWidth / 2, $cy + $starRadius + $config->centerFontSize, $textColor, $fontPath, $config->usage );防伪线的实现原理很简单:把圆分成72段,每段的两个端点半径在正常值附近随机抖动1到2像素,连接后形成一圈不规则的细波浪,模拟真实印章盖印时的边缘纹理。这种做法成本低,肉眼效果却很接近真实盖章,扫描后也能看出痕迹差异,可以有效防止直接截图复用。
4. 避坑指南:五个让印章翻车的常见问题
4.1 中文文字变成方框或乱码
现象:渲染出来的PNG里中文全是方框或者问号,英文和数字正常。
原因:GD库的imagettftext依赖指定路径的字体文件,如果字体文件不支持中文字形,或者传进去的字符串不是UTF-8编码,就会用默认字体兜底,最终显示成方框。另一个隐患是Linux服务器上常见的中文字体目录和Windows不同,直接用系统字体路径很容易踩空。
解决:项目自带的fonts目录下放一个完整中文字体,fontPath参数用绝对路径或基于__DIR__的相对路径。字符串入库和读取时统一走UTF-8,mb_str_split之前先确认编码,能用mb_convert_encoding就显式转换,不要依赖默认值。
4.2 文字方向倒置与圆弧偏移
现象:外圈文字刚画出来时,有的字是正立的、有的字是旋转90度的,偶尔还会整体偏到圆环一侧。
原因:这是imagettftext的angle参数没有按极坐标角度联动造成的。文字旋转角度必须跟随字符所在圆弧位置实时计算,顶部是0度、右侧是-90度,不能全用同一个角度。偏移问题则是忽略了imagettftext的坐标基准点是基线而非字符中心,直接用了圆心加半径的坐标。
解决:用代码里提到的charAngle公式,让每个字符的旋转角等于“当前位置与正上方的夹角再取负”。基线补偿用imagettfbbox先量出宽高,再向左向上各平移一半尺寸,就能让字符中心准确落在圆弧上。我当时就是漏了这个补偿,调了一个下午才发现是差半字高。
4.3 输出背景是黑色而不是透明
现象:PNG图片看起来是正常的,但拖到Word文档里发现白色区域全部变成黑块,把底下的文字全遮住了。
原因:imagecreatetruecolor创建的是黑色不透明画布,只设置imagesavealpha(true)是不够的,还必须在填充透明色之前关闭alphablending。否则imagefill填充的透明色会被直接合成到黑色背景上,最终保存的是“半透明的黑色”,视觉上就是一块黑斑。
解决:严格按3.1的顺序执行:关闭alphablending → 开启savealpha → 填充全透明色 → 重新开启alphablending。这条顺序是写死的,调换任何两步结果都不一样。另外输出时用imagepng保存,不要用imagejpeg,JPEG格式本身不支持透明通道。
4.4 文字挤在一起或者首尾相连
现象:公司名短的时候印章很协调,公司名一长,文字全部挤在圆环下半部分,甚至最后一个字和第一个字重叠。
原因:项目默认采用固定字号渲染,不会根据字符数量动态调整。字符数量多时,每个字符占用的弧长变小,字距被压缩到零,GD不会自动排布间距,只会在原地重叠。如果圆环半径又偏小,最后几个字就会落到圆外。
解决:在渲染流程的配置校验阶段增加一个自动适配逻辑:用字符串长度除以圆弧周长,算出每个字符的理论弧长,再根据最小间距反推字号。我一般会保证每个字符弧长不小于字号乘1.2,如果不够就逐档降低fontSize,最多降两档,否则印章会显得空旷。
4.5 字体路径在Windows和Linux上表现不同
现象:本地Windows环境跑得好好的,部署到Linux服务器就报找不到字体文件,或者画出来的字变成豆腐块。
原因:开发环境用的字体路径是Windows风格的反斜杠和盘符,部署到Linux后路径不存在。更隐蔽的是Windows对字体文件名大小写不敏感,但Linux严格区分大小写,文件名写错一个字母就加载失败。
解决:fontPath统一用__DIR__拼出绝对路径,并加一个is_file校验,加载失败时抛出带路径的异常信息。实际部署时用realpath确认字体真实路径,同时检查字体文件读写权限,HTTP进程用户至少要可读。从那以后我每次接新项目都会先检查字体路径是不是写死成Windows风格的,避免上线前一刻才发现。
5. 二次开发:增加椭圆印章、输出Base64与对接框架
5.1 从圆形扩展到椭圆印章
不少财务场景需要椭圆印章,这个项目的Renderer接口就是为扩展准备的。新增一个EllipseSealRenderer,把画布宽高按2比3设置,圆形绘制改成imageellipse传不同宽高参数,圆弧文字部分把极坐标半径替换为椭圆的参数方程即可。椭圆弧长计算比圆复杂,我一般会先采样绘制字符坐标,让每个字符按角度均匀分布,再用atan2校正实际坐标,保证相邻字符间隔视觉上基本一致。
// 椭圆印章的字符定位:角度相同时,半径要按椭圆方程计算 $x = $cx + $radiusX * cos($angleRad); $y = $cy + $radiusY * sin($angleRad); // radiusX和radiusY分别为椭圆的长短轴半径5.2 输出Base64与加缓存
实际对接业务系统时,多数情况不需要把图片落盘,而是直接给前端一个Base64字符串。SealResult上我加了一个toBase64方法,用输出缓冲把imagepng的结果接住,再编码返回。这样接口层可以直接把这个字符串作为JSON字段返回,前端用data:image/png;base64,前缀就能渲染。
public function toBase64(): string { ob_start(); imagepng($this->image); $data = ob_get_clean(); return 'data:image/png;base64,' . base64_encode($data); }缓存方面,用公司名、用途、尺寸、颜色的MD5值作为文件名,相同配置直接读缓存文件,避免每次请求都重新跑一遍GD渲染。印章生成属于CPU密集操作,加一层文件缓存能明显降低接口延迟。
如果只是要改颜色、加字体、调边框粗细,完全不用动渲染器;要新增印章形态时,照着CircleSealRenderer的结构复制一个类,实现render接口就行。整体改造成本比我预想的低不少。希望这些踩坑记录和扩展思路能帮到你。
本文还有配套的精品资源,点击获取