最稳方案是用 glob() 配合 getimagesize() 动态调用 imagecreatefrom*,加载后及时 imagedestroy(),水印用 imagecopymerge() 或 imagettftext(),大图分批处理并过滤超大尺寸,最后用 spatie/image-optimizer 无损压缩。

用 glob() + imagecreatefrom* 循环处理最稳
直接上手就用 scandir() 容易漏掉隐藏文件或混入目录,glob() 带通配符更干净,也天然跳过 . 和 ..。但要注意:它不递归,子目录得手动处理;另外 Windows 下大小写不敏感,Linux 下严格区分 .JPG 和 .jpg,建议统一用小写后缀匹配。
加载图片时别硬写 imagecreatefromjpeg() —— 万一遇到 .png 就崩。用 getimagesize() 先读类型,再按 IMAGETYPE_JPEG / IMAGETYPE_PNG 分支调用对应函数。GIF 多帧、WebP 透明通道这些,GD 库原生不支持,强行加载会失败或丢帧。
- 推荐写法:
$info = getimagesize($file); if (!$info) continue; - PNG 保存必须用
imagepng(),否则透明背景变黑;加imagesavealpha($img, true)才保 alpha 通道 - 每轮循环末尾必须
imagedestroy($img)和imagedestroy($newImg),否则几百张图跑一半就Fatal error: Allowed memory size exhausted
批量加水印别用 imagecopy() 硬贴
imagecopy() 是像素级覆盖,没透明度控制;真正要带灰度/半透水印,得用 imagecopymerge() 并传入 alpha 参数(0–100),或者先用 imagecolorallocatealpha() 创建带透明度的颜色再填充文字水印。
水印图本身要是 PNG 格式,且自身含 alpha 通道;JPG 水印图无论怎么设 alpha 都是实心块。位置计算也容易错:右下角水印的 $x = $width - $watermarkWidth - 10,但得先 getimagesize() 拿到水印图尺寸,不能写死。
立即学习“PHP免费学习笔记(深入)”;
- 文字水印优先用
imagettftext(),避免 GD 自带字体丑且不支持中文——提前确认.ttf路径可读 - 如果水印图比原图大,
imagecopyresampled()可等比缩放再贴,比先 resize 水印图再imagecopy()更省内存 - 别在循环里反复
imagecreatefrompng('watermark.png'),提出来放到循环外复用资源
用 spatie/image-optimizer 做无损压缩比自己写快十倍
自己用 GD 调 imagejpeg($img, $path, 80) 是有损压缩,画质下降明显;而 spatie/image-optimizer 调的是系统级二进制工具(jpegoptim、pngquant、gifsicle),只删冗余元数据和做熵编码,体积减 30%~60% 画质几乎无感。
但它不处理「尺寸」「旋转」「滤镜」,纯属后期压缩环节。所以最佳流程是:GD/Imagick 先做业务处理 → 保存中间图 → 再用 Optimizer 扫一遍输出目录。别试图让它边优化边改尺寸,它不干这事。
- 安装后必须手动装系统依赖:
sudo apt install jpegoptim optipng pngquant gifsicle,缺一个就报Optimizer not found - 对 WebP/AVIF 支持需额外装
libavif-bin和webp包,否则直接跳过 - 它默认覆盖原文件,想留原始图?必须显式传第二个参数:
$optimizer->optimize($src, $dst)
大图多时内存爆了,别急着加 memory_limit
把 memory_limit 从 128M 改成 512M 只是掩耳盗铃。真正有效的是分批:每次只取 20 张路径,处理完 unset($files),再取下一批。配合 gc_collect_cycles() 主动触发垃圾回收,比调配置管用。
还有个隐形坑:GD 加载一张 4000×3000 的 JPG,内存占用 ≈ 宽 × 高 × 4 字节(RGBA),光图数据就 48MB。所以预处理前先用 getimagesize() 过滤超大图,或强制用 imagescale($img, 1920, 1080, IMG_BILINEAR) 快速缩略,别一上来就 imagecreatetruecolor() 拉满分辨率。
- CLI 模式下可用
set_time_limit(0)防超时,但 Web 模式不行,得拆成 Ajax 分页调用 - Imagick 比 GD 更吃内存,但单图处理快;GD 内存友好但慢——选哪个看你的瓶颈在 CPU 还是 RAM
- 所有
image*函数出错都返回false,别忘了加if (!$img) { error_log("fail load $file"); continue; }
实际跑通的关键,往往卡在三件事:路径权限不对导致 fopen 失败、GD 不支持 WebP 却硬加载、以及忘记 imagedestroy()。这些点不解决,代码写得再漂亮也卡在第 17 张图。



















