首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 圖片添加水印完整實戰:GD 庫實現圖片/文字水印、透明度控制與位置計算

     編輯:PHP 相關知識

前言:為什麼要在服務端加水印?

前端 Canvas 可加水印,但用戶可繞過前端直接下載原圖。服務端(PHP GD/ImageMagick)處理上傳/輸出時強制添加水印,是保護圖片版權、品牌曝光的必要防線。本文提供圖片水印、文字水印、透明度控制、九宮格位置、批量處理的完整封裝類。

核心封裝類:Watermarker

class Watermarker {
    private $sourcePath;
    private $waterPath;      // 水印圖片路徑
    private $waterText;      // 水印文字
    private $fontPath;       // TTF 字體路徑
    private $fontSize = 20;
    private $fontColor = [255,255,255]; // RGB
    private $opacity = 50;   // 0-100
    private $position = 8;   // 1-9 九宮格,5=居中
    private $offset = 10;    // 邊距
    private $outputPath;
    private $quality = 90;   // JPEG 質量

    public function __construct($sourcePath) {
        $this->sourcePath = $sourcePath;
    }

    // 鏈式設置
    public function setWaterImage($path) { $this->waterPath = $path; return $this; }
    public function setWaterText($text, $fontPath) { $this->waterText = $text; $this->fontPath = $fontPath; return $this; }
    public function setOpacity($opacity) { $this->opacity = max(0, min(100, $opacity)); return $this; }
    public function setPosition($pos) { $this->position = max(1, min(9, $pos)); return $this; }
    public function setOffset($offset) { $this->offset = $offset; return $this; }
    public function setFont($size, $color) { $this->fontSize = $size; $this->fontColor = $color; return $this; }
    public function setOutput($path, $quality = 90) { $this->outputPath = $path; $this->quality = $quality; return $this; }

    public function apply() {
        // 1. 載入源圖(自動檢測類型)
        $srcInfo = getimagesize($this->sourcePath);
        if (!$srcInfo) throw new Exception('源圖不存在或格式不支持');
        $srcMime = $srcInfo['mime'];
        $createFunc = 'imagecreatefrom' . ($srcMime === 'image/jpeg' ? 'jpeg' : ($srcMime === 'image/png' ? 'png' : 'gif'));
        if (!function_exists($createFunc)) throw new Exception('GD 不支持該格式: ' . $srcMime);
        $srcImg = $createFunc($this->sourcePath);
        $srcW = imagesx($srcImg); $srcH = imagesy($srcImg);

        // 2. 準備水印圖層
        if ($this->waterPath) {
            $waterInfo = getimagesize($this->waterPath);
            $waterCreate = 'imagecreatefrom' . ($waterInfo['mime'] === 'image/jpeg' ? 'jpeg' : ($waterInfo['mime'] === 'image/png' ? 'png' : 'gif'));
            $waterImg = $waterCreate($this->waterPath);
            $waterW = imagesx($waterImg); $waterH = imagesy($waterImg);
        } elseif ($this->waterText) {
            if (!$this->fontPath || !file_exists($this->fontPath)) throw new Exception('字體文件不存在');
            // 計算文字邊界框
            $box = imagettfbbox($this->fontSize, 0, $this->fontPath, $this->waterText);
            $waterW = $box[2] - $box[0]; $waterH = $box[3] - $box[5];
            // 創建透明畫布繪製文字
            $waterImg = imagecreatetruecolor($waterW, $waterH);
            imagesavealpha($waterImg, true);
            $transparent = imagecolorallocatealpha($waterImg, 0, 0, 0, 127);
            imagefill($waterImg, 0, 0, $transparent);
            $textColor = imagecolorallocatealpha($waterImg, $this->fontColor[0], $this->fontColor[1], $this->fontColor[2], 127 * (100 - $this->opacity) / 100);
            imagettftext($waterImg, $this->fontSize, 0, 0, $waterH - $box[5], $textColor, $this->fontPath, $this->waterText);
        } else {
            throw new Exception('必須設置水印圖片或文字');
        }

        // 3. 計算貼合位置 (九宮格)
        $posMap = [
            1 => [$this->offset, $this->offset],                                    // 左上
            2 => [($srcW - $waterW) / 2, $this->offset],                            // 頂中
            3 => [$srcW - $waterW - $this->offset, $this->offset],                  // 右上
            4 => [$this->offset, ($srcH - $waterH) / 2],                            // 左中
            5 => [($srcW - $waterW) / 2, ($srcH - $waterH) / 2],                    // 居中
            6 => [$srcW - $waterW - $this->offset, ($srcH - $waterH) / 2],          // 右中
            7 => [$this->offset, $srcH - $waterH - $this->offset],                  // 左下
            8 => [($srcW - $waterW) / 2, $srcH - $waterH - $this->offset],          // 底中 (默認)
            9 => [$srcW - $waterW - $this->offset, $srcH - $waterH - $this->offset],// 右下
        ];
        list($dstX, $dstY) = $posMap[$this->position];
        $dstX = max(0, min($srcW - $waterW, (int)$dstX));
        $dstY = max(0, min($srcH - $waterH, (int)$dstY));

        // 4. 合成:開啟 Alpha 混合
        imagealphablending($srcImg, true);
        imagesavealpha($srcImg, true);
        // 水印圖片自身透明度已在創建時設置,此處直接 copy
        imagecopy($srcImg, $waterImg, $dstX, $dstY, 0, 0, $waterW, $waterH);

        // 5. 輸出
        $outPath = $this->outputPath ?? $this->sourcePath;
        $outMime = $srcMime;
        if (preg_match('/\.jpe?g$/i', $outPath)) $outMime = 'image/jpeg';
        elseif (preg_match('/\.png$/i', $outPath)) $outMime = 'image/png';
        elseif (preg_match('/\.gif$/i', $outPath)) $outMime = 'image/gif';

        $outputFunc = 'image' . ($outMime === 'image/jpeg' ? 'jpeg' : ($outMime === 'image/png' ? 'png' : 'gif'));
        if ($outMime === 'image/jpeg') {
            $outputFunc($srcImg, $outPath, $this->quality);
        } else {
            $outputFunc($srcImg, $outPath);
        }

        // 6. 清理
        imagedestroy($srcImg);
        imagedestroy($waterImg);
        return $outPath;
    }
}

使用示例

1. 圖片水印(Logo 右下角,30% 透明度)

(new Watermarker('uploads/photo.jpg'))
    ->setWaterImage('assets/logo.png')
    ->setOpacity(30)
    ->setPosition(9) // 右下
    ->setOffset(20)
    ->setOutput('watermarked/photo.jpg')
    ->apply();

2. 文字水印(版權信息居中,白色半透明)

(new Watermarker('uploads/photo.jpg'))
    ->setWaterText('© 2024 MySite.com', 'fonts/NotoSansSC-Regular.otf')
    ->setFont(24, [255,255,255])
    ->setOpacity(50)
    ->setPosition(5) // 居中
    ->setOutput('watermarked/photo.jpg')
    ->apply();

3. 批量處理上傳目錄

foreach (glob('uploads/*.{jpg,png}', GLOB_BRACE) as $file) {
    $out = 'watermarked/' . basename($file);
    try {
        (new Watermarker($file))
            ->setWaterImage('assets/watermark.png')
            ->setPosition(8)
            ->setOutput($out)
            ->apply();
        echo "OK: $file\n";
    } catch (Exception $e) {
        echo "FAIL: $file - " . $e->getMessage() . "\n";
    }
}

關鍵技術點解析

技術點實現細節避坑提示
透明度控制水印圖層創建時用 imagecolorallocatealpha 設置 alpha 通道 (0-127),而非 imagecopymerge(後者不支持 Alpha 通道疊加)圖片水印需確保源文件含 Alpha 通道(PNG),JPEG 無透明度
九宮格定位預計算 9 個坐標,邊界檢查防止越界圖片較小時水印可能被裁剪,建議按比例縮放水印
文字繪製imagettfbbox 獲取邊界框 → 創建透明畫布 → imagettftext 繪製 → 作為水印層合成字體路徑需絕對路徑;中文必須用支持 CJK 的 TTF/OTF 字體
格式自動識別getimagesize() 獲取 MIME,動態調用對應 imagecreatefrom* / image*輸出格式建議與源一致,或統一轉 WebP (需 GD 支持)
內存管理及時 imagedestroy() 釋放 GD 資源,防止大批量處理 OOM單張大圖 (如 4000x3000) 佔用 ~48MB 內存,注意 memory_limit

進階:ImageMagick 版(性能更強、格式更全)

// 需安裝 php-imagick 擴展
$imagick = new Imagick('photo.jpg');
$watermark = new Imagick('logo.png');
$watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, 0.3, Imagick::ALPHA_CHANNEL); // 30% 不透明度
$imagick->compositeImage($watermark, Imagick::COMPOSITE_OVER, 100, 100); // 底中
$imagick->writeImage('watermarked.jpg');

ImageMagick 支持向量水印 (SVG)、ICC 色彩配置文件、並行處理,高併發場景推薦。

結語

PHP GD 庫雖屬基礎擴展,但功能完備,封裝得當即可構建生產級水印服務。核心在於Alpha 通道正確混合、九宮格靈活定位、資源及時釋放。建議將水印邏輯封裝為獨立服務/隊列任務,上傳時異步處理,返回原圖 URL,水印完成後通知前端更新,兼顧用戶體驗與版權保護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved