---
title: 只有 GD 也要做好图片管线：缩略图、方向与 WebP
url: https://vabora.dev/posts/image-pipeline-gd-webp
author: Vabora
published: 2026-07-28 09:00:00
updated: 2026-09-27 10:33:42
category: 技术分享
tags: PHP 8, 性能, 上传
canonical: https://vabora.dev/posts/image-pipeline-gd-webp
license: 转载与引用请注明出处并保留原文链接
---

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

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

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

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，迁移无痛。
- **保留原图**：只压缩派生图，原图留档——将来换分辨率或做印刷物料时你会需要它。
