等比例縮略圖生成是圖片處理的基礎需求。本文提供一個精簡、健壯的 reSizeImg() 函數,支持**裁剪模式**(填滿目標框、居中裁剪)與**非裁剪模式**(完整顯示、尺寸不固定)雙模式,代碼結構清晰,邏輯嚴密,可直接集成至項目。
<?php
/**
* 等比例生成縮略圖
* @param string $img_src 源圖路徑
* @param int $resize_width 目標寬度
* @param int $resize_height 目標高度
* @param bool $is_cut true=裁剪模式(填滿), false=非裁剪模式(完整顯示)
* @return bool 成功返回 true,失敗拋異常
*/
function reSizeImg($img_src, $resize_width, $resize_height, $is_cut = false) {
// 1. 解析源圖類型(通過擴展名,簡化版;生產建議用魔數/finfo)
$type = strtolower(pathinfo($img_src, PATHINFO_EXTENSION));
$type = $type === 'jpg' ? 'jpeg' : $type; // 統一 jpg→jpeg
// 2. 載入源圖資源
$create_map = [
'jpeg' => 'imagecreatefromjpeg',
'png' => 'imagecreatefrompng',
'gif' => 'imagecreatefromgif',
'webp' => 'imagecreatefromwebp',
];
if (!isset($create_map[$type])) {
throw new Exception("不支持的圖片格式: $type");
}
$src_img = $create_map[$type]($img_src);
if (!$src_img) throw new Exception("無法載入源圖: $img_src");
// 3. 獲取源圖尺寸
$src_w = imagesx($src_img);
$src_h = imagesy($src_img);
// 4. 構建目標文件名:原名_s.擴展名
$path_info = pathinfo($img_src);
$dst_img = $path_info['dirname'] . '/' . $path_info['filename'] . '_s.' . $path_info['extension'];
// 5. 計算比例
$resize_ratio = $resize_width / $resize_height; // 目標比例
$src_ratio = $src_w / $src_h; // 源圖比例
if ($is_cut) {
// ===== 裁剪模式:填滿目標框,居中裁剪 =====
$dst_img_res = imagecreatetruecolor($resize_width, $resize_height);
if ($src_ratio >= $resize_ratio) {
// 源圖更「寬」或等比:以高度為基準縮放,寬度超出 → 裁剪左右
$scale = $resize_height / $src_h;
$scaled_w = (int)round($src_w * $scale);
$scaled_h = $resize_height;
$src_x = (int)(($scaled_w - $resize_width) / 2);
$src_y = 0;
} else {
// 源圖更「高」:以寬度為基準縮放,高度超出 → 裁剪上下
$scale = $resize_width / $src_w;
$scaled_w = $resize_width;
$scaled_h = (int)round($src_h * $scale);
$src_x = 0;
$src_y = (int)(($scaled_h - $resize_height) / 2);
}
// 中間畫布:縮放後的完整圖
$mid_img = imagecreatetruecolor($scaled_w, $scaled_h);
copy_transparency($src_img, $mid_img, $type);
imagecopyresampled($mid_img, $src_img, 0, 0, 0, 0, $scaled_w, $scaled_h, $src_w, $src_h);
// 目標畫布:裁剪中心區域
imagecopy($dst_img_res, $mid_img, 0, 0, $src_x, $src_y, $resize_width, $resize_height);
imagedestroy($mid_img);
} else {
// ===== 非裁剪模式:完整顯示,尺寸≤目標框 =====
if ($src_ratio >= $resize_ratio) {
// 源圖更寬:以寬度為基準
$dst_w = $resize_width;
$dst_h = (int)round($resize_width / $src_ratio);
} else {
// 源圖更高:以高度為基準
$dst_h = $resize_height;
$dst_w = (int)round($resize_height * $src_ratio);
}
$dst_img_res = imagecreatetruecolor($dst_w, $dst_h);
copy_transparency($src_img, $dst_img_res, $type);
imagecopyresampled($dst_img_res, $src_img, 0, 0, 0, 0, $dst_w, $dst_h, $src_w, $src_h);
}
// 6. 保存輸出
$save_map = [
'jpeg' => fn($im, $path) => imagejpeg($im, $path, 90),
'png' => fn($im, $path) => imagepng($im, $path, 6),
'gif' => fn($im, $path) => imagegif($im, $path),
'webp' => fn($im, $path) => imagewebp($im, $path, 90),
];
$save_map[$type]($dst_img_res, $dst_img);
// 7. 清理資源
imagedestroy($src_img);
imagedestroy($dst_img_res);
return true;
}
/**
* 透明度保持輔助函數
*/
function copy_transparency($src, $dst, $type) {
if (in_array($type, ['png', 'gif', 'webp'])) {
imagealphablending($dst, false);
imagesavealpha($dst, true);
$tp = imagecolorallocatealpha($dst, 255, 255, 255, 127);
imagefill($dst, 0, 0, $tp);
}
}
<?php
require 'reSizeImg.php';
// 1. 裁剪模式:生成 300x300 正方形頭像(填滿、居中裁剪)
reSizeImg('uploads/avatar.jpg', 300, 300, true);
// 輸出:uploads/avatar_s.jpg (300x300)
// 2. 非裁剪模式:生成寬度≤400、高度≤300 的列表圖(完整顯示)
reSizeImg('uploads/photo.png', 400, 300, false);
// 輸出:uploads/photo_s.png (如 400x225 或 300x300)
// 3. 批量處理上傳文件
$uploaded = $_FILES['photo'];
if ($uploaded['error'] === UPLOAD_ERR_OK) {
$ext = pathinfo($uploaded['name'], PATHINFO_EXTENSION);
$tmp_name = 'uploads/' . bin2hex(random_bytes(8)) . '.' . $ext;
move_uploaded_file($uploaded['tmp_name'], $tmp_name);
// 生成三種規格
reSizeImg($tmp_name, 800, 600, false); // 大圖
reSizeImg($tmp_name, 400, 300, false); // 中圖
reSizeImg($tmp_name, 150, 150, true); // 縮略圖(正方形裁剪)
}
源圖: 800×600 (4:3) 目標: 300×300 (1:1) 裁剪模式 src_ratio = 1.33, resize_ratio = 1.0 src_ratio >= resize_ratio → 以高為準 scale = 300/600 = 0.5 scaled_w = 800*0.5 = 400, scaled_h = 300 src_x = (400-300)/2 = 50, src_y = 0 → 縮放至 400×300,裁剪左/右各 50px,得到 300×300 中心區域 源圖: 600×800 (3:4) 目標: 400×300 (4:3) 非裁剪模式 src_ratio = 0.75, resize_ratio = 1.33 src_ratio < resize_ratio → 以高為準 dst_h = 300, dst_w = 300*0.75 = 225 → 輸出 225×300,完整顯示,寬度未滿 400
true:Cover 模式(填滿裁剪),輸出尺寸**固定**為 $resize_width × $resize_height。false:Contain 模式(完整顯示),輸出尺寸**≤**目標框,保持原比例。$mid_img = imagecreatetruecolor($scaled_w, $scaled_h); // 縮放後完整圖
imagecopyresampled($mid_img, $src_img, ...); // 繪製縮放圖
imagecopy($dst_img_res, $mid_img, 0, 0, $src_x, $src_y, ...); // 裁剪中心
先縮放後裁剪,而非直接帶座標裁剪,邏輯更清晰、座標計算更直觀。
copy_transparency() 封裝了 PNG/GIF/WebP 的 4 步驟,避免代碼重複與遺漏。
{原名}_s.{擴展名} 規則簡單明確,避免覆蓋原圖,便於識別。
finfo 或魔數替代擴展名判斷,防偽造。$dst_path 參數,默認自動生成。$src_img,循環生成多個 $dst_img_res。imagesx * imagesy * 4 > memory_limit * 0.8,超限拒絕或降級。
function reSizeImg($img_src, $resize_width, $resize_height, $is_cut = false, $watermark = null) {
// ... 生成 $dst_img_res 後 ...
if ($watermark) apply_watermark($dst_img_res, $watermark);
// ... 保存 ...
}
function apply_watermark($img, $wm) {
// $wm = ['text'=>'©Site', 'font'=>'...', 'color'=>[255,255,255,100], 'pos'=>'br', 'size'=>14]
// 或 $wm = ['image'=>'logo.png', 'pos'=>'br', 'opacity'=>0.5]
// 實現略...
}
reSizeImg() 函數以 60 行核心代碼實現了「雙模式等比縮放、透明度保持、多格式支持、自動命名、資源清理」的完整鏈路。裁剪模式通過「中間畫布縮放→中心裁剪」清晰解決幾何計算;非裁剪模式直接計算目標尺寸繪製。代碼無第三方依賴、邏輯自洽、易於閱讀與擴展,是中小項目圖片處理的理想基石。