应动态计算验证码画布宽度:宽度 = 字符数 × 20 + (字符数 − 1) × 3,高度设为42;正确使用imagettfbbox()补偿基线偏移;干扰元素须在写字前绘制;字母必须为ASCII字符串。

验证码图片太窄导致字母挤在一起
默认用 imagecreatetruecolor(100, 40) 创建画布时,如果生成 4–5 个随机字母,宽度常不够——字母间距被压缩,甚至重叠,OCR 或人眼都难识别。
解决思路不是盲目拉宽,而是按字符数动态计算最小安全宽度:每个字母占约 18–22 像素(含左右 padding),再加 2–4 像素字间距。比如 5 个字母,建议起始宽度为 5 * 20 + 4 * 3 = 112,高度保持 40–45 即可。
- 避免固定写死
100,改用变量计算:$width = strlen($code) * 20 + (strlen($code) - 1) * 3 - 高度建议设为
42,兼容多数字体 baseline,低于 38 容易裁切底部笔画(如 g、y、q) - 若用
imagettftext()写字,必须确保 TTF 字体文件路径正确,否则会静默回退到内置点阵字体,导致尺寸计算失效
用 imagettftext 画字母时位置偏移或截断
常见现象是字母顶部被切掉,或整体下坠贴底——本质是没正确处理字体的 baseline 和 descender。GD 的 y 坐标是文字基线位置,不是图片顶部。
推荐做法:先用 imagettfbbox() 测量实际占用区域,再反推绘制起点。例如:
立即学习“PHP免费学习笔记(深入)”;
list($l, $t, $r, $b) = imagettfbbox(16, 0, 'font.ttf', $char); $char_width = $r - $l; $char_height = $b - $t; $x = $offset_x - $l; // 补偿左偏移 $y = $base_y - $t; // 补偿上偏移
-
$l通常为负数(字体左侧有空白),直接用$x = $offset_x会导致所有字母右移 -
$t是负值(表示基线上方高度),不补偿就会让字母“浮”在顶部之外 - 不要依赖字体文件名里的字号数字,实测中
16和18在同字体下渲染差异明显,需调参
验证码背景干扰线/点影响字母可读性
加太多噪点或粗线条,会让小写字母(如 o、c、e)和干扰点混成一团,尤其在低分辨率(width )下更明显。
- 干扰线建议最多 3–4 条,用
imagesetthickness($img, 1)控制为 1 像素,颜色比背景灰度差 ≤30(如背景 #f0f0f0,线用 #c0c0c0) - 噪点数量限制在
mt_rand(30, 60),用imagesetpixel()逐点绘制,避免用imagefilledellipse()画大圆点 - 关键原则:所有干扰元素必须在写字 之前 绘制,否则会覆盖字母;顺序错会导致验证码不可读
中文系统下字母变形或显示方块
不是字体问题,而是 GD 默认使用 Latin-1 编码,而 PHP 字符串若含 UTF-8 字节(如从配置读取的字母表),传给 imagettftext() 就会乱码或崩溃。
- 确保验证码字母来源是 ASCII 字符串,例如:
$chars = 'ABCDEFGHKMNPQRSTUVWXYZ23456789';,别用mb_substr()截取 UTF-8 字符串 - 禁用任何
mb_internal_encoding('UTF-8')后直接传字符串进 GD 函数,GD 不支持 UTF-8 - 如果必须支持中文提示(如“请输入验证码”),那是前端 HTML 层的事,GD 图片里只画纯英文数字
字体路径错误、y 坐标理解偏差、干扰层绘制顺序、字符编码混用——这四个点卡住的人最多。调尺寸前先确认这四件事都对了,否则光改 100 到 150 没用。



















