在圖片版權保護、狀態標識(如「已售罄」「審核中」)、緩存縮略圖預覽等場景,常需在原圖上疊加一層灰色半透明遮罩。本文基於 GD 庫,詳解「創建同尺寸遮罩 → 填充半透明灰色 → imagecopy() 疊加 → 輸出」的完整流程,並提供生產級封裝函數、記憶體優化建議及 ImageMagick 對比方案。
<?php
/**
* 給圖片添加灰色半透明遮罩
* @param string $srcPath 源圖路徑 (支援 JPEG/PNG/GIF)
* @param string $dstPath 輸出路徑 (為空則直接輸出到瀏覽器)
* @param int $opacity 透明度 0-127 (0 不透明, 127 完全透明), 默認 75 ≈ 40% 可見度
* @param array $color RGB 顏色, 默認灰色 [0,0,0]
* @return bool 成功返回 true
*/
function applyGrayOverlay(string $srcPath, string $dstPath = '', int $opacity = 75, array $color = [0,0,0]): bool {
// 1. 根據副檔名/MIME 創建源圖資源
$info = @getimagesize($srcPath);
if (!$info) return false;
$mime = $info['mime'];
$createFrom = match ($mime) {
'image/jpeg' => 'imagecreatefromjpeg',
'image/png' => 'imagecreatefrompng',
'image/gif' => 'imagecreatefromgif',
'image/webp' => 'imagecreatefromwebp',
default => null,
};
if (!$createFrom) return false;
$srcImg = $createFrom($srcPath);
if (!$srcImg) return false;
// 2. 獲取尺寸
$w = imagesx($srcImg);
$h = imagesy($srcImg);
// 3. 創建真彩色遮罩畫布
$overlay = imagecreatetruecolor($w, $h);
if (!$overlay) { imagedestroy($srcImg); return false; }
// 4. 分配帶 Alpha 的顏色 (0-127)
$alpha = max(0, min(127, $opacity));
$colorId = imagecolorallocatealpha($overlay, $color[0], $color[1], $color[2], $alpha);
if ($colorId === false) { imagedestroy($srcImg); imagedestroy($overlay); return false; }
// 5. 填充整個遮罩
imagefill($overlay, 0, 0, $colorId);
// 6. 關鍵:啟用 Alpha 混合模式,否則會蓋住源圖
imagealphablending($srcImg, true);
imagesavealpha($srcImg, true); // 保存完整 Alpha 通道 (輸出 PNG 時必須)
// 7. 將遮罩疊加到源圖 (src -> dst)
// imagecopy(dst, src, dst_x, dst_y, src_x, src_y, w, h)
imagecopy($srcImg, $overlay, 0, 0, 0, 0, $w, $h);
// 8. 輸出
$success = false;
if ($dstPath) {
$ext = strtolower(pathinfo($dstPath, PATHINFO_EXTENSION));
$success = match ($ext) {
'png' => imagepng($srcImg, $dstPath, 9), // 壓縮級別 9
'jpg', 'jpeg' => imagejpeg($srcImg, $dstPath, 85),
'webp' => imagewebp($srcImg, $dstPath, 85),
default => false,
};
} else {
// 直接瀏覽器輸出
header('Content-Type: ' . $mime);
$success = match ($mime) {
'image/jpeg' => imagejpeg($srcImg, null, 85),
'image/png' => imagepng($srcImg, null, 9),
'image/gif' => imagegif($srcImg),
'image/webp' => imagewebp($srcImg, null, 85),
default => false,
};
}
// 9. 釋放資源 (極重要,防記憶體洩漏)
imagedestroy($srcImg);
imagedestroy($overlay);
return $success;
}
// === 使用範例 ===
// 保存文件
applyGrayOverlay('uploads/photo.jpg', 'uploads/photo_masked.png', 80); // 更透
// 直接輸出 (配合
)
if (isset($_GET['img'])) {
applyGrayOverlay('uploads/' . basename($_GET['img']));
}
imagecolorallocatealpha() Alpha 範圍 0-127:0 完全不透明,127 完全透明。75 ≈ 41% 不透明度,視覺上為「灰色半透明」。imagealphablending($dst, true) 必須開啟:否則 imagecopy 會直接複製像素覆蓋,而非混合。源圖 ($srcImg) 作為目標畫布需開啟混合。imagesavealpha($srcImg, true) 輸出 PNG 時必須:保證合成後的 Alpha 通道寫入文件,否則透明區域變黑。$h = imagesy($srcImg);
$maskH = (int)($h * 0.3);
$overlay = imagecreatetruecolor($w, $maskH);
// ... 填充顏色 ...
imagecopy($srcImg, $overlay, 0, $h - $maskH, 0, 0, $w, $maskH);
$overlay = imagecreatetruecolor($w, $h);
for ($y = 0; $y < $h; $y++) {
$alpha = (int)(127 * ($y / $h)); // 頂部透明 底部不透明
$colorId = imagecolorallocatealpha($overlay, 0, 0, 0, $alpha);
imageline($overlay, 0, $y, $w, $y, $colorId);
}
imagecopy($srcImg, $overlay, 0, 0, 0, 0, $w, $h);
// 在遮罩上寫文字
$font = __DIR__ . '/fonts/SourceHanSansCN-Regular.otf';
$text = '僅供預覽';
$bbox = imagettfbbox(24, 0, $font, $text);
$textW = $bbox[2] - $bbox[0];
$textH = $bbox[1] - $bbox[5];
$x = ($w - $textW) / 2;
$y = ($h + $textH) / 2;
$white = imagecolorallocatealpha($overlay, 255, 255, 255, 30); // 白色半透明文字
imagettftext($overlay, 24, 0, $x, $y, $white, $font, $text);
// 然後 imagecopy 疊加
| 優化點 | 說明 | 效果 |
|---|---|---|
及時 imagedestroy() | 每個 imagecreate* 必須配對銷毀,尤其批量處理時 | 避免記憶體洩漏,單張 4000x3000 JPEG 約佔 48MB 記憶體 |
| 限制最大尺寸 | 處理前檢查 imagesx() * imagesy() > 16_000_000 則縮放或拒絕 | 防止惡意大圖耗盡 memory_limit |
使用 imagecopyresampled() 先縮略再處理 | 若只需縮略圖預覽,先縮小至 800px 再疊加 | 速度提升 10-20 倍,記憶體降低 90% |
| 批量處理用 CLI + pcntl_fork / Symfony Process | 避免 Web 請求超時,並行利用多核 | 吞吐量提升 N 倍 |
| 生產環境考慮 ImageMagick (imagick 擴展) | $im->compositeImage($mask, Imagick::COMPOSITE_OVER, 0, 0); | 速度更快、格式支援更全、色彩管理專業 |
// ImageMagick 版本 (需安裝 php-imagick)
$im = new Imagick('photo.jpg');
$mask = new Imagick();
$mask->newPseudoImage($im->getImageWidth(), $im->getImageHeight(), 'canvas:gray');
$mask->setImageOpacity(0.4); // 0-1
$im->compositeImage($mask, Imagick::COMPOSITE_OVER, 0, 0);
$im->writeImage('photo_masked.png');
// 優勢:單行疊加、自動色彩空間、支援 CMYK/HEIC/AVIF、多線程
imagesavealpha($dst, true) 或輸出 JPEG 導致 Alpha 丟失。imagecolorallocatealpha 的 RGB 順序(紅、綠、藍),且源圖為真彩色 (imagecreatetruecolor),調色板模式不支援 Alpha 混合。memory_limit 過小,或未銷毀資源。大圖處理建議 ini_set('memory_limit', '512M')。imageantialias($overlay, true) 或使用 ImageMagick。GD 庫實現灰度半透明遮罩的核心是 「真彩色畫布 + Alpha 顏色分配 + 開啟混合模式 + imagecopy 疊加」。封裝為 applyGrayOverlay() 可滿足大多數業務需求。對於高併發、大尺寸、多格式、專業色彩管理場景,強烈建議遷移至 ImageMagick (imagick 擴展),代碼更簡潔、性能更優、功能更強。無論選哪種方案,imagedestroy() 釋放資源、限制輸入尺寸、錯誤處理都是生產環境的必修課。