專案若需處理使用者上傳圖片(縮圖、加浮水印、裁切),從零撰寫 GD 庫邏輯既繁瑣又易出錯。本文提供一個**生產級 Image 類別**,封裝 thumb()、waterMark()、cut() 三大核心功能,支援 JPEG/PNG/GIF、透明度保留、等比縮放、9 種浮水印定位與隨機定位,開箱即用。
| 公開方法 | 功能 | 關鍵參數 |
|---|---|---|
| thumb($name, $w, $h, $prefix='th_') | 等比縮放產生縮圖 | 目標寬/高、輸出前綴 |
| waterMark($ground, $water, $pos=0, $prefix='wa_') | 疊加圖片浮水印 | 背景圖、水印圖、位置 0~9 |
| cut($name, $x, $y, $w, $h, $prefix='cu_') | 裁切指定區域 | 左上座標、寬高 |
內部私有方法處理:格式偵測(getInfo)、資源建立(getImg)、尺寸計算(getNewSize)、透明度保留(kidOfImage)、輸出儲存(createNewImage)、水印疊加(copyImage)、位置計算(position)。
<?php
class Image {
private $path;
public function __construct(string $path = './') {
$this->path = rtrim($path, '/') . '/';
}
// ========== 公開 API ==========
public function thumb(string $name, int $width, int $height, string $prefix = 'th_') {
$info = $this->getInfo($name);
$src = $this->getImg($name, $info);
$size = $this->getNewSize($name, $width, $height, $info);
$newImg = $this->kidOfImage($src, $size, $info);
return $this->createNewImage($newImg, $prefix . $name, $info);
}
public function waterMark(string $ground, string $water, int $pos = 0, string $prefix = 'wa_') {
$cur = rtrim($this->path, '/') . '/';
$wdir = dirname($water);
$wpath = ($wdir === '.') ? $cur : $wdir . '/';
$water = basename($water);
if (!file_exists($cur.$ground) || !file_exists($wpath.$water)) {
trigger_error('圖片或水印檔案不存在', E_USER_WARNING);
return false;
}
$gInfo = $this->getInfo($ground);
$wInfo = $this->getInfo($water, $wdir);
if (!$posArr = $this->position($gInfo, $wInfo, $pos)) {
trigger_error('水印不應大於背景圖', E_USER_WARNING);
return false;
}
$gImg = $this->getImg($ground, $gInfo);
$wImg = $this->getImg($water, $wInfo, $wdir);
$gImg = $this->copyImage($gImg, $wImg, $posArr, $wInfo);
return $this->createNewImage($gImg, $prefix . $ground, $gInfo);
}
public function cut(string $name, int $x, int $y, int $w, int $h, string $prefix = 'cu_') {
$info = $this->getInfo($name);
if (($x+$w) > $info['width'] || ($y+$h) > $info['height']) {
trigger_error('裁切區域超出圖片範圍', E_USER_WARNING);
return false;
}
$src = $this->getImg($name, $info);
$cut = imagecreatetruecolor($w, $h);
imagecopyresampled($cut, $src, 0, 0, $x, $y, $w, $h, $w, $h);
imagedestroy($src);
return $this->createNewImage($cut, $prefix . $name, $info);
}
// ========== 私有輔助 ==========
private function position(array $g, array $w, int $pos): ?array {
if ($g['width'] < $w['width'] || $g['height'] < $w['height']) return null;
$posMap = [
1 => [0, 0],
2 => [($g['width']-$w['width'])/2, 0],
3 => [$g['width']-$w['width'], 0],
4 => [0, ($g['height']-$w['height'])/2],
5 => [($g['width']-$w['width'])/2, ($g['height']-$w['height'])/2],
6 => [$g['width']-$w['width'], ($g['height']-$w['height'])/2],
7 => [0, $g['height']-$w['height']],
8 => [($g['width']-$w['width'])/2, $g['height']-$w['height']],
9 => [$g['width']-$w['width'], $g['height']-$w['height']],
];
if ($pos === 0) {
return ['posX' => rand(0, $g['width']-$w['width']), 'posY' => rand(0, $g['height']-$w['height'])];
}
return ['posX' => $posMap[$pos][0] ?? 0, 'posY' => $posMap[$pos][1] ?? 0];
}
private function getInfo(string $name, string $path = '.'): array {
$p = ($path === '.') ? $this->path : rtrim($path,'/').'/';
$data = getimagesize($p.$name);
return ['width'=>$data[0], 'height'=>$data[1], 'type'=>$data[2]];
}
private function getImg(string $name, array $info, string $path = '.') {
$p = ($path === '.') ? $this->path : rtrim($path,'/').'/';
return match($info['type']) {
1 => imagecreatefromgif($p.$name),
2 => imagecreatefromjpeg($p.$name),
3 => imagecreatefrompng($p.$name),
default => false
};
}
private function getNewSize(string $name, int $w, int $h, array $info): array {
$size = ['width'=>$info['width'], 'height'=>$info['height']];
if ($w < $info['width']) $size['width'] = $w;
if ($h < $info['height']) $size['height'] = $h;
// 等比縮放
if ($info['width'] * $size['width'] > $info['height'] * $size['height']) {
$size['height'] = round($info['height'] * $size['width'] / $info['width']);
} else {
$size['width'] = round($info['width'] * $size['height'] / $info['height']);
}
return $size;
}
private function kidOfImage($src, array $size, array $info) {
$new = imagecreatetruecolor($size['width'], $size['height']);
// 保留 GIF/PNG 透明度
$transparent = imagecolortransparent($src);
if ($transparent >= 0) {
$rgb = imagecolorsforindex($src, $transparent);
$newColor = imagecolorallocate($new, $rgb['red'], $rgb['green'], $rgb['blue']);
imagefill($new, 0, 0, $newColor);
imagecolortransparent($new, $newColor);
}
imagecopyresized($new, $src, 0,0,0,0, $size['width'],$size['height'], $info['width'],$info['height']);
imagedestroy($src);
return $new;
}
private function createNewImage($img, string $name, array $info): string {
$this->path = rtrim($this->path,'/').'/';
$result = match($info['type']) {
1 => imagegif($img, $this->path.$name),
2 => imagejpeg($img, $this->path.$name, 90),
3 => imagepng($img, $this->path.$name),
default => false
};
imagedestroy($img);
return $result ? $name : false;
}
private function copyImage($ground, $water, array $pos, array $wInfo) {
imagecopy($ground, $water, $pos['posX'], $pos['posY'], 0,0, $wInfo['width'], $wInfo['height']);
imagedestroy($water);
return $ground;
}
}
?>
require 'image.class.php';
$img = new Image('./uploads/');
// 1. 產生 300x200 縮圖
$thumb = $img->thumb('photo.jpg', 300, 200); // 產生 th_photo.jpg
// 2. 加浮水印(右下角=9)
$marked = $img->waterMark('photo.jpg', 'logo.png', 9); // 產生 wa_photo.jpg
// 3. 裁切 (100,100) 起 400x300 區域
$cropped = $img->cut('photo.jpg', 100, 100, 400, 300); // 產生 cu_photo.jpg
ImageProcessorInterface,方便未來切換至 Imagick 或 Intervention/Image。imagecreatefromwebp、imagewebp、imageavif(PHP 8.1+)。textMark() 使用 imagettftext() 繪製文字。output($format='jpeg', $quality=90) 直接輸出至瀏覽器而非落盤。trigger_error 改拋出 ImageException,配合 try-catch 統一錯誤處理。這個 Image 類別以精簡代碼涵蓋最常見的圖片處理需求,適合中小型專案快速整合;大型或高效能場景建議改用 Intervention/Image 或 Imagick 擴充套件。