Imagick 是 PHP 呼叫 ImageMagick 函式庫的擴展介面。ImageMagick 是功能強大的圖片處理套件,支援 185+ 種格式(JPEG、PNG、GIF、TIFF、PDF、WebP、HEIC 等),提供縮放、裁切、旋轉、銳化、濾鏡、水印、動畫處理等專業級功能。
https://imagemagick.org/archive/binaries/ImageMagick-7.x.x-Q16-HDRI-x64-dll.exephp_imagick.dll(區分 TS/NTS、VC15/VS16)https://windows.php.net/downloads/pecl/releases/imagick/php/ext/,php.ini 加入 extension=imagickphpinfo() 確認 imagick 區塊出現# Ubuntu/Debian
sudo apt update && sudo apt install imagemagick libmagickwand-dev
sudo pecl install imagick
echo "extension=imagick.so" | sudo tee /etc/php/8.x/mods-available/imagick.ini
sudo phpenmod imagick
sudo systemctl restart apache2
# CentOS/RHEL
sudo yum install ImageMagick ImageMagick-devel
sudo pecl install imagick
# 同樣啟用擴展並重啟服務
<?php
header('Content-Type: image/jpeg');
$im = new Imagick('image.jpg');
$im->thumbnailImage(100, 0); // 寬 100px,高度自動等比
echo $im;
?>
<?php
$images = new Imagick(glob('images/*.JPG'));
foreach ($images as $image) {
$image->thumbnailImage(1024, 0); // 長邊 1024px
}
$images->writeImages(); // 覆寫原檔
?>
<?php
$im = new Imagick("animation.gif");
foreach ($im as $frame) {
$frame->thumbnailImage(50, 50);
$frame->setImagePage(50, 50, 0, 0); // 修正虛擬畫布
}
$im->writeImages("animation_small.gif", true); // true = 多幀寫入
?>
<?php
$im = new Imagick('photo.jpg');
// 邊框
$color = new ImagickPixel('rgb(220,220,220)');
$im->borderImage($color, 10, 10);
// 模糊(半徑 5, sigma 3)
$im->blurImage(5, 3);
// 高斯模糊
$im->gaussianBlurImage(3, 2);
// 色階調整
$im->levelImage(0.2, 1.0, 0.8); // 黑點、伽瑪、白點
// 亮度/飽和度/色調
$im->modulateImage(120, 110, 100); // 百分比
// 特效:素描
$im->charcoalImage(3, 1);
// 特效:油畫
$im->oilPaintImage(4);
// 翻轉
$im->flipImage(); // 垂直
$im->flopImage(); // 水平
// 輸出
header('Content-Type: image/jpeg');
echo $im;
?>
以下類庫封裝了裁切、縮放、水印、文字、格式轉換等常用操作,並自動處理 GIF 多幀問題:
<?php
/**
* 通用圖片處理類 - 基於 Imagick
* 支援:裁切、縮放(多種模式)、水印圖/文字、GIF 動畫保持
*/
class ImageProcessor {
private $imagick = null;
private $format = null;
private $isAnimated = false;
private $frameCount = 0;
public function __construct() {}
public function __destruct() {
if ($this->imagick !== null) $this->imagick->destroy();
}
// 載入圖片
public function load(string $path): bool {
try {
$this->imagick = new Imagick($path);
$this->format = strtolower($this->imagick->getImageFormat());
$this->isAnimated = ($this->format === 'gif' && $this->imagick->getNumberImages() > 1);
$this->frameCount = $this->imagick->getNumberImages();
return true;
} catch (ImagickException $e) {
error_log("Image load failed: " . $e->getMessage());
return false;
}
}
// 裁切
public function crop(int $x, int $y, int $width, int $height): self {
if ($this->isAnimated) {
$this->applyToAllFrames(function($frame) use ($x, $y, $width, $height) {
$frame->cropImage($width, $height, $x, $y);
$frame->setImagePage($width, $height, 0, 0);
});
} else {
$this->imagick->cropImage($width, $height, $x, $y);
$this->imagick->setImagePage($width, $height, 0, 0);
}
return $this;
}
// 縮放 - 多種模式
public function resize(int $width, int $height, string $mode = 'fit', array $bgColor = [255,255,255,0]): self {
$modes = [
'fit' => fn($w, $h) => $this->imagick->thumbnailImage($w, $h, true), // 等比縮放至框內
'fill' => fn($w, $h) => $this->imagick->thumbnailImage($w, $h, false), // 強制填滿(可能變形)
'crop' => fn($w, $h) => $this->smartCrop($w, $h), // 智能裁切填滿
'stretch' => fn($w, $h) => $this->imagick->resizeImage($w, $h, Imagick::FILTER_LANCZOS, 1), // 非等比拉伸
];
if ($this->isAnimated) {
$canvas = new Imagick();
foreach ($this->imagick as $frame) {
$newFrame = clone $frame;
$modes[$mode]($width, $height);
if ($mode === 'fit' && $bgColor) {
// 填充背景邏輯...
}
$canvas->addImage($newFrame);
$canvas->setImageDelay($newFrame->getImageDelay());
$canvas->setImagePage($width, $height, 0, 0);
}
$this->imagick->destroy();
$this->imagick = $canvas;
} else {
$modes[$mode]($width, $height);
}
return $this;
}
private function smartCrop(int $targetW, int $targetH): void {
$srcW = $this->imagick->getImageWidth();
$srcH = $this->imagick->getImageHeight();
$ratio = max($targetW/$srcW, $targetH/$srcH);
$newW = (int)($srcW * $ratio);
$newH = (int)($srcH * $ratio);
$this->imagick->thumbnailImage($newW, $newH, true);
$x = (int)(($newW - $targetW) / 2);
$y = (int)(($newH - $targetH) / 2);
$this->imagick->cropImage($targetW, $targetH, $x, $y);
$this->imagick->setImagePage($targetW, $targetH, 0, 0);
}
private function applyToAllFrames(callable $fn): void {
$frames = $this->imagick->coalesceImages();
$canvas = new Imagick();
foreach ($frames as $frame) {
$fn($frame);
$canvas->addImage($frame);
$canvas->setImageDelay($frame->getImageDelay());
}
$this->imagick->destroy();
$this->imagick = $canvas;
}
// 加浮水印圖片
public function watermark(string $watermarkPath, int $x, int $y, float $opacity = 1.0): self {
$wm = new Imagick($watermarkPath);
$wm->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity, Imagick::CHANNEL_ALPHA);
$draw = new ImagickDraw();
$draw->composite($wm->getImageCompose(), $x, $y, $wm->getImageWidth(), $wm->getImageHeight(), $wm);
if ($this->isAnimated) {
$this->applyToAllFrames(fn($f) => $f->drawImage($draw));
} else {
$this->imagick->drawImage($draw);
}
return $this;
}
// 加文字水印
public function text(string $text, int $x, int $y, array $style = []): self {
$draw = new ImagickDraw();
$draw->setFont($style['font'] ?? 'DejaVu-Sans');
$draw->setFontSize($style['size'] ?? 20);
$draw->setFillColor($style['color'] ?? 'white');
if (isset($style['stroke'])) {
$draw->setStrokeColor($style['stroke']);
$draw->setStrokeWidth($style['stroke_width'] ?? 1);
}
if ($this->isAnimated) {
$this->applyToAllFrames(fn($f) => $f->annotateImage($draw, $x, $y, $style['angle'] ?? 0, $text));
} else {
$this->imagick->annotateImage($draw, $x, $y, $style['angle'] ?? 0, $text);
}
return $this;
}
// 設定輸出格式
public function setFormat(string $format): self {
$this->format = $format;
$this->imagick->setImageFormat($format);
return $this;
}
// 儲存至檔案
public function save(string $path): bool {
try {
if ($this->isAnimated || $this->frameCount > 1) {
$this->imagick->writeImages($path, true);
} else {
$this->imagick->writeImage($path);
}
return true;
} catch (ImagickException $e) {
error_log("Save failed: " . $e->getMessage());
return false;
}
}
// 直接輸出到瀏覽器
public function output(): void {
header('Content-Type: ' . $this->getMimeType());
echo $this->imagick->getImagesBlob();
}
private function getMimeType(): string {
return match($this->format) {
'jpg', 'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => 'application/octet-stream'
};
}
}
// 使用範例
$processor = new ImageProcessor();
$processor->load('input.gif')
->resize(300, 300, 'crop')
->watermark('logo.png', 10, 10, 0.7)
->text('© MySite', 15, 30, ['size' => 14, 'color' => 'white', 'stroke' => 'black'])
->save('output.gif');
?>
| 問題 | 原因 | 解決 |
|---|---|---|
| GIF 縮放後變靜態 | 未使用 coalesceImages() 處理幀 | 遍歷每幀處理,保持 delay 與 page 設定 |
| 透明背景變黑 | 新建畫布預設黑色 | 使用 newImage() 指定背景色或 setImageBackgroundColor() |
| 記憶體耗盡 | 大圖/多幀載入記憶體 | 設定 imagick::setResourceLimit()、分批處理、銷毀不用物件 |
| 字體找不到 | ImageMagick 字體路徑未設定 | 使用絕對路徑 setFont('/path/to/font.ttf') 或設定 MAGICK_FONT_PATH |
| PDF 處理失敗 | 安全策略禁止 PDF 委派 | 修改 /etc/ImageMagick-6/policy.xml 允許 PDF 讀寫 |
thumbnailImage() 而非 resizeImage() 產生縮圖(更快、記憶體較低)destroy() 釋放記憶體Imagick::pingImage() 僅讀取元資料不載入像素Imagick 將專業級圖片處理能力帶入 PHP,配合合理的封裝與錯誤處理,能滿足從簡單縮圖到複雜動畫處理的各種業務需求。