頭像、商品封面、文章配圖常需統一規格(如 180×180px)。本文展示基於 CodeIgniter 3 + GD2 庫的完整實現:上傳驗證 → 等比縮放至目標邊長 → 居中裁剪多餘區域 → 輸出高質量縮圖。
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Media extends CI_Controller {
public function __construct() {
parent::__construct();
$this->load->helper(['form', 'url']);
}
public function upload_avatar() {
// 1. 上傳配置
$config = [
'upload_path' => './data/uploads/',
'allowed_types' => 'gif|jpg|png',
'max_size' => 2048, // 2MB
'max_width' => 2048,
'max_height' => 2048,
'encrypt_name' => true, // 防重名
];
$this->load->library('upload', $config);
if (!$this->upload->do_upload('avatar')) {
return $this->output
->set_status(400)
->set_json(['error' => $this->upload->display_errors()]);
}
$data = $this->upload->data(); // 含 full_path, file_name, image_width, image_height
$src = $data['full_path'];
list($w, $h) = [$data['image_width'], $data['image_height']];
// 2. 等比縮放:短邊縮放至 180px
$target = 180;
$this->load->library('image_lib');
$cfg = [
'image_library' => 'gd2',
'source_image' => $src,
'maintain_ratio'=> TRUE,
'master_dim' => ($w >= $h) ? 'height' : 'width', // 短邊為基準
'width' => $target,
'height' => $target,
'quality' => 90,
];
$this->image_lib->initialize($cfg);
if (!$this->image_lib->resize()) {
return $this->output->set_status(500)->set_json(['error' => $this->image_lib->display_errors()]);
}
// 3. 居中裁剪:長邊超出部分兩側各切一半
$this->image_lib->clear();
$cfg['maintain_ratio'] = FALSE;
if ($w >= $h) {
// 橫圖:寬 > 高,裁剪左右
$newW = round($w * $target / $h);
$cfg['x_axis'] = floor(($newW - $target) / 2);
$cfg['y_axis'] = 0;
} else {
// 縱圖:高 > 寬,裁剪上下
$newH = round($h * $target / $w);
$cfg['x_axis'] = 0;
$cfg['y_axis'] = floor(($newH - $target) / 2);
}
$this->image_lib->initialize($cfg);
if (!$this->image_lib->crop()) {
return $this->output->set_status(500)->set_json(['error' => $this->image_lib->display_errors()]);
}
// 4. 返回結果
$url = base_url('data/uploads/' . $data['file_name']);
$this->output->set_json(['url' => $url, 'width' => $target, 'height' => $target]);
}
}
| 參數 | 含義 | 本例取值 |
|---|---|---|
master_dim | 基準維度:width/height/auto | 短邊優先,保證長邊 ≥ 目標尺寸 |
maintain_ratio | TRUE=等比縮放,FALSE=強制尺寸(裁剪時用) | 兩階段切換 |
x_axis/y_axis | 裁剪起始座標(左上角為 0,0) | 居中計算:floor((長邊-目標)/2) |
quality | JPEG 輸出質量 0-100 | 90(平衡體積/清晰度) |
function makeSquareThumb(string $srcPath, string $destPath, int $size = 180, int $quality = 90): bool {
list($w, $h, $type) = getimagesize($srcPath);
$create = match($type) {
IMAGETYPE_JPEG => 'imagecreatefromjpeg',
IMAGETYPE_PNG => 'imagecreatefrompng',
IMAGETYPE_GIF => 'imagecreatefromgif',
default => throw new Exception('Unsupported type'),
};
$srcImg = $create($srcPath);
// 透明背景處理 (PNG/GIF)
if ($type !== IMAGETYPE_JPEG) {
imagealphablending($srcImg, false);
imagesavealpha($srcImg, true);
}
// 目標畫布
$thumb = imagecreatetruecolor($size, $size);
if ($type !== IMAGETYPE_JPEG) {
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
$transparent = imagecolorallocatealpha($thumb, 0,0,0,127);
imagefill($thumb, 0, 0, $transparent);
} else {
$white = imagecolorallocate($thumb, 255,255,255);
imagefill($thumb, 0, 0, $white);
}
// 計算縮放比例與裁剪區域
if ($w >= $h) {
$newW = round($w * $size / $h);
$srcX = floor(($newW - $size) / 2);
$srcY = 0;
$srcW = $size; $srcH = $size;
$dstW = $newW; $dstH = $size;
} else {
$newH = round($h * $size / $w);
$srcX = 0;
$srcY = floor(($newH - $size) / 2);
$srcW = $size; $srcH = $size;
$dstW = $size; $dstH = $newH;
}
// 先縮放到臨時畫布
$temp = imagecreatetruecolor($dstW, $dstH);
imagecopyresampled($temp, $srcImg, 0,0, 0,0, $dstW, $dstH, $w, $h);
// 再從臨時畫布居中裁剪到最終畫布
imagecopyresampled($thumb, $temp, 0,0, $srcX, $srcY, $size, $size, $size, $size);
// 輸出
$out = match($type) {
IMAGETYPE_JPEG => fn() => imagejpeg($thumb, $destPath, $quality),
IMAGETYPE_PNG => fn() => imagepng($thumb, $destPath, 9),
IMAGETYPE_GIF => fn() => imagegif($thumb, $destPath),
};
$ok = $out();
imagedestroy($srcImg); imagedestroy($temp); imagedestroy($thumb);
return $ok;
}
ini_set('memory_limit', '256M'),或分塊處理(Imagick 支援流式)。imagewebp) 節省 30%+ 體積。