跳到主要内容
Vabora 开发极客
首页 归档 分类 标签 案例 服务 关于 聊聊需求

只有 GD 也要做好图片管线:缩略图、方向与 WebP

不少主机只装了 GD,没有 Imagick。这套管线用 GD 完成方向校正、等比缩放、WebP 输出与体积保护,顺带解决"上传 10MB 原图直接当缩略图用"的加载灾难。

只有 GD 也要做好图片管线:缩略图、方向与 WebP 封面

#背景

素材库上线后立刻暴露两个问题:

  1. 手机拍的竖图在页面上横躺着——EXIF 方向信息没有被应用;
  2. 后台列表直接加载 4000px 的原图当缩略图,一页 20 张就是几十 MB,列表页比首页还慢。

不需要 Imagick 也能解决,GD 够用。

#做法

#1. 先读方向,再决定是否旋转

php
$exif = @exif_read_data($file);   // 生产代码里用 set_error_handler 包裹,不用 @
$orientation = (int) ($exif['Orientation'] ?? 1);

$rotate = match ($orientation) {
    3, 4 => 180,
    5, 6 => 90,     // 6 是"手机竖拍"最常见的情况
    7, 8 => 270,
    default => 0,
};

imagerotate() 之后要特别处理透明通道:旋转会把 PNG 的透明区域变黑,需要 imagesavealpha() + imagealphablending(false)。

#2. 缩放要按最长边算,并且不超过原图

php
$ratio = min($max_width / $src_width, $max_height / $src_height, 1);   // 关键:不超过 1
$dst_width = max(1, (int) round($src_width * $ratio));
$dst_height = max(1, (int) round($src_height * $ratio));

$dst = imagecreatetruecolor($dst_width, $dst_height);
imagecopyresampled($dst, $src, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);

min(..., 1) 那一项是"不放大":把小图放大到 1200px 只会变糊,还会让文件变大。

#3. 输出优先 WebP,但要能降级

php
if (function_exists('imagewebp')) {
    imagewebp($dst, $target, 82);
} else {
    imagejpeg($dst, $target, 85);
}

WebP 在同等观感下通常比 JPEG 小 25%~35%,但文件名与 MIME 必须跟着变(.webp + image/webp),否则部分 CDN 会按扩展名推断类型导致下载而不是显示。

#4. 上传阶段就要挡住异常体积

三件事在 store() 里一次做完:

  • 像素上限:读 getimagesize(),超过 8000px 直接拒绝(解码 8000×8000 的图要几百 MB 内存,会把 PHP 进程打死);
  • 体积上限:图片 10MB / 视频 100MB,与 php.ini 的 upload_max_filesize 保持一致并略小于它,否则 PHP 先拦下请求,用户只看到"上传失败";
  • 真实类型校验:用 getimagesize() 的返回类型判断,不信 $_FILES['type'](客户端可伪造)。

#结果

  • 列表页缩略图从"原图直出"变成 400px WebP,单张从 1.2MB 降到约 40KB;
  • 手机竖拍照片不再横躺;
  • 上传异常大图被提前拒绝,而不是让 PHP 崩在解码阶段。

#取舍

  • 不在上传时生成多套尺寸:只在首次使用时按需生成并缓存,避免"上传时生成了 5 个尺寸,结果只用得上 1 个"。代价是首次访问略慢(毫秒级)。
  • 不引入图像 CDN:对内容量不大的站点,图片处理放在应用内更简单可控;真要上 CDN 时,处理结果已经是标准 WebP,迁移无痛。
  • 保留原图:只压缩派生图,原图留档——将来换分辨率或做印刷物料时你会需要它。
最后更新: 本文采用 CC BY-NC-SA 4.0 许可,转载请注明出处与作者。
related

相关文章

评论

还没有评论,来说点什么吧。

发表评论

评论需站长审核后展示 · 最多 1000 字