class ImageUploader {
private $maxWidth = 800;
private $maxHeight = 600;
private $thumbWidth = 200;
private $thumbHeight = 200;
private $saveDir = 'uploads/';
private $allowedTypes = ['image/jpeg','image/png','image/gif','image/webp'];
private $allowedExt = ['jpg','jpeg','png','gif','webp'];
public function upload($fileInputName = 'file') {
if (!isset($_FILES[$fileInputName]) || $_FILES[$fileInputName]['error'] !== UPLOAD_ERR_OK) {
throw new Exception('上傳失敗或無文件');
}
$file = $_FILES[$fileInputName];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $this->allowedExt) || !in_array($file['type'], $this->allowedTypes)) {
throw new Exception('不支援的文件類型');
}
if (!is_dir($this->saveDir)) mkdir($this->saveDir, 0755, true);
$filename = uniqid('img_', true).'.'.$ext;
$dest = $this->saveDir.$filename;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
throw new Exception('移動文件失敗');
}
// 原圖若超過限制,先壓縮
$this->resizeIfNeeded($dest, $this->maxWidth, $this->maxHeight);
// 生成縮略圖
$thumbName = 'thumb_'.$filename;
$thumbPath = $this->saveDir.$thumbName;
$this->createThumbnail($dest, $thumbPath, $this->thumbWidth, $this->thumbHeight);
return ['original'=>$dest, 'thumbnail'=>$thumbPath];
}
private function resizeIfNeeded($path, $maxW, $maxH) {
list($w, $h, $type) = getimagesize($path);
if ($w <= $maxW && $h <= $maxH) return;
$ratio = min($maxW/$w, $maxH/$h);
$newW = (int)($w*$ratio); $newH = (int)($h*$ratio);
$this->resample($path, $path, $newW, $newH, $type);
}
private function createThumbnail($src, $dst, $maxW, $maxH, $bgColor = [255,255,255]) {
list($w, $h, $type) = getimagesize($src);
$ratio = min($maxW/$w, $maxH/$h);
$newW = (int)($w*$ratio); $newH = (int)($h*$ratio);
$canvas = imagecreatetruecolor($maxW, $maxH);
$bg = imagecolorallocate($canvas, $bgColor[0], $bgColor[1], $bgColor[2]);
imagefill($canvas, 0, 0, $bg);
$x = ($maxW - $newW) / 2; $y = ($maxH - $newH) / 2;
$srcIm = $this->createFromType($src, $type);
imagecopyresampled($canvas, $srcIm, $x, $y, 0, 0, $newW, $newH, $w, $h);
$this->saveByType($canvas, $dst, $type);
imagedestroy($srcIm); imagedestroy($canvas);
}
private function resample($src, $dst, $newW, $newH, $type) {
$srcIm = $this->createFromType($src, $type);
$dstIm = imagecreatetruecolor($newW, $newH);
imagecopyresampled($dstIm, $srcIm, 0, 0, 0, 0, $newW, $newH, imagesx($srcIm), imagesy($srcIm));
$this->saveByType($dstIm, $dst, $type);
imagedestroy($srcIm); imagedestroy($dstIm);
}
private function createFromType($path, $type) {
switch ($type) {
case IMAGETYPE_JPEG: return imagecreatefromjpeg($path);
case IMAGETYPE_PNG: return imagecreatefrompng($path);
case IMAGETYPE_GIF: return imagecreatefromgif($path);
case IMAGETYPE_WEBP: return imagecreatefromwebp($path);
default: throw new Exception('Unsupported type');
}
}
private function saveByType($im, $path, $type) {
switch ($type) {
case IMAGETYPE_JPEG: imagejpeg($im, $path, 90); break;
case IMAGETYPE_PNG: imagepng($im, $path, 8); break;
case IMAGETYPE_GIF: imagegif($im, $path); break;
case IMAGETYPE_WEBP: imagewebp($im, $path, 90); break;
}
}
}
// 使用
$uploader = new ImageUploader();
try {
$result = $uploader->upload('avatar');
echo json_encode(['code'=>0,'data'=>$result]);
} catch (Exception $e) {
echo json_encode(['code'=>1,'msg'=>$e->getMessage()]);
}
| 步驟 | 函數 | 說明 |
|---|---|---|
| 讀取圖片 | imagecreatefromjpeg/png/gif/webp | 根據 getimagesize() 返回的類型常量選擇 |
| 建立畫布 | imagecreatetruecolor() | 真彩色畫布,支援透明度(PNG/GIF 需額外處理) |
| 等比縮放 | imagecopyresampled() | 雙線性插值,品質優於 imagecopyresized() |
| 填充背景 | imagefill() | 縮略圖畫布預填白色,居中貼圖 |
| 輸出 | imagejpeg/png/gif/webp() | JPEG 品質 90,PNG 壓縮 8 |
// 在 createThumbnail 中,若原圖有透明通道
if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF) {
imagealphablending($canvas, false);
imagesavealpha($canvas, true);
$transparent = imagecolorallocatealpha($canvas, 0,0,0,127);
imagefill($canvas,0,0,$transparent);
// ... 複製貼圖
}
封裝 ImageUploader 類,統一處理「驗證 → 原圖壓縮 → 縮略圖生成」。核心 API:getimagesize() 獲取屬性 → imagecreatetruecolor() 建畫布 → imagecopyresampled() 高質量縮放 → imagefill() 背景填充 → 對應輸出函數落盤。可直接投入生產或擴展為 Laravel/ThinkPHP 服務。