imagettftext 的 size 参数必须动态传入,即每个字符需单独调用该函数并传入不同 $size 值,范围建议为 round($base_size 0.7) 至 round($base_size 1.3),同时须用 imagettfbbox() 动态计算字符宽度与基线以调整坐标,避免重叠或错行。

imagettftext 的 size 参数必须动态传入
PHP 验证码字符要实现随机缩放,核心是让每个字符的字体大小不一致——这只能通过 imagettftext() 的第 2 个参数($size)控制。GD 库没有「对已绘制文本再缩放」的函数,也不能用 imageaffine 对文字图层二次变形(会模糊失真),所以必须在绘制时就决定每个字符的尺寸。
常见错误是统一设一个固定 $size 值,或只对整串调用一次 imagettftext(),那样所有字符必然等大。
- 为每个字符单独调用
imagettftext(),不能一次性画整串 -
$size建议范围控制在round($base_size * 0.7)到round($base_size * 1.3)之间,避免过小(难识别)或过大(溢出画布) - 注意:该参数是像素值,不是 CSS 的 em/rem,且对同一字体文件,数值非线性影响实际渲染高度(比如从 24→28 增幅感知明显,36→40 就不太明显)
缩放后坐标偏移必须重算
字符大小变了,它占据的水平宽度和基线位置就不同。如果还按等宽布局(比如每字符固定横移 $width / $length),大字会重叠,小字则留白过多。
正确做法是:每次绘制前,用 imagettfbbox() 预估当前字符的实际边界,再据此调整下一个字符的 $x 起始位置。
- 调用
imagettfbbox($size, 0, $fontfile, $char)返回 8 个坐标点,其中[2] - [0]近似为该字符的像素宽度 - 累计宽度时加一点随机 padding(如
+ mt_rand(2, 6)),避免机械感 - 纵坐标
$y也要随$size微调,否则大小混排时基线不齐,看起来像错行
字体文件本身会影响缩放稳定性
不是所有 TTF 字体都支持任意尺寸下的清晰渲染。某些免费字体在 $size 或 <code>$size > 48 时会出现锯齿、空心、甚至部分笔画消失。
实测下来,以下两类字体更稳妥:
- 无衬线黑体类:如
DejaVuSans.ttf、arial.ttf(Windows)、Hiragino Sans GB.ttc(macOS) - 专为屏幕优化的字体:如
Roboto-Regular.ttf、OpenSans-Regular.ttf - 避免使用手写体、书法体或超细/超粗变体——它们在小尺寸下极易糊成一团或断笔
如果发现某字体在 $size = 22 时正常,但 21 或 23 就异常,说明该字体存在尺寸敏感区,应避开这些临界值。
random_int() 生成缩放因子比 mt_rand() 更安全
虽然视觉缩放看似不涉密,但缩放逻辑若被攻击者摸清(比如发现总是「大-小-大-小」交替),可能辅助 OCR 定位字符区域。所以缩放因子也建议用密码学安全方式生成。
- 用
random_int(70, 130)生成 70–130 的整数,再除以 100 得到 0.7–1.3 的缩放系数 - 不要用
mt_rand(7, 13) / 10,因为mt_rand()可被状态恢复,批量请求下易预测序列 - 如果 PHP 版本 openssl_random_pseudo_bytes(2) +
unpack('v', ...)模拟,别退回到rand()
真正容易被忽略的是:缩放后的字符若超出画布右边界,imagettftext() 不报错也不裁剪,而是直接静默丢弃——结果就是验证码少一位,用户反复刷都输不对。务必在循环末尾加 if ($x > $width) { break; } 主动截断。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











