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

PHP GD 给图片添加水印:图片水印/文字水印/透明度/九宫格位置完整实现

     編輯:PHP 綜合知識

核心函数

  • imagecopymerge():图片水印,支持透明度 (0-100)
  • imagecopy():图片水印,保留源图 Alpha 通道 (PNG 透明)
  • imagettftext():文字水印,需 TTF 字体

通用水印类

<?php
/**
 * GD 图片水印处理器
 * 支持:图片水印、文字水印、九宫格位置、透明度、平铺
 */
class ImageWatermark
{
    private $srcImage;
    private $width;
    private $height;
    private $mime;

    public function __construct(string $sourcePath)
    {
        if (!file_exists($sourcePath)) {
            throw new \InvalidArgumentException("源文件不存在: $sourcePath");
        }
        $info = getimagesize($sourcePath);
        $this->width = $info[0];
        $this->height = $info[1];
        $this->mime = $info['mime'];

        $this->srcImage = $this->createFromSource($sourcePath);
        if (!$this->srcImage) {
            throw new \RuntimeException("无法创建图像资源: $sourcePath");
        }
    }

    /** 添加图片水印 */
    public function addImageWatermark(
        string $watermarkPath,
        int $position = 9,        // 1-9 九宫格,5=中心
        int $opacity = 50,        // 0-100 透明度 (仅 imagecopymerge)
        int $offsetX = 10,
        int $offsetY = 10
    ): self {
        $wmInfo = getimagesize($watermarkPath);
        $wmWidth = $wmInfo[0];
        $wmHeight = $wmInfo[1];
        $wmMime = $wmInfo['mime'];

        $wmResource = $this->createFromSource($watermarkPath);
        if (!$wmResource) throw new \RuntimeException("水印图片加载失败");

        // 计算坐标
        [$destX, $destY] = $this->calcPosition($wmWidth, $wmHeight, $position, $offsetX, $offsetY);

        // 判断水印是否带 Alpha 通道 (PNG/GIF)
        $hasAlpha = in_array($wmMime, ['image/png', 'image/gif']);

        if ($hasAlpha && $opacity >= 100) {
            // 保留原透明度,直接复制
            imagecopy($this->srcImage, $wmResource, $destX, $destY, 0, 0, $wmWidth, $wmHeight);
        } else {
            // 使用 merge 控制整体透明度
            imagecopymerge($this->srcImage, $wmResource, $destX, $destY, 0, 0, $wmWidth, $wmHeight, $opacity);
        }

        imagedestroy($wmResource);
        return $this;
    }

    /** 添加文字水印 */
    public function addTextWatermark(
        string $text,
        string $fontPath,
        int $fontSize = 20,
        array $color = [255, 255, 255],
        int $position = 9,
        int $opacity = 80,
        int $offsetX = 10,
        int $offsetY = 10,
        int $angle = 0
    ): self {
        if (!file_exists($fontPath)) throw new \InvalidArgumentException("字体文件不存在: $fontPath");

        // 创建临时图层绘制文字 (支持透明度)
        $textBox = imagettfbbox($fontSize, $angle, $fontPath, $text);
        $textWidth = $textBox[2] - $textBox[0];
        $textHeight = $textBox[1] - $textBox[7];

        [$destX, $destY] = $this->calcPosition($textWidth, $textHeight, $position, $offsetX, $offsetY);

        // 分配颜色
        $textColor = imagecolorallocatealpha($this->srcImage, $color[0], $color[1], $color[2], 127 - (int)($opacity * 1.27));

        // 绘制 (基线坐标调整)
        $baselineY = $destY + $textHeight;
        imagettftext($this->srcImage, $fontSize, $angle, $destX, $baselineY, $textColor, $fontPath, $text);

        return $this;
    }

    /** 平铺水印 (防盗图) */
    public function addTiledWatermark(string $watermarkPath, int $opacity = 30, int $spacing = 50): self
    {
        $wmInfo = getimagesize($watermarkPath);
        $wmWidth = $wmInfo[0];
        $wmHeight = $wmInfo[1];
        $wmResource = $this->createFromSource($watermarkPath);

        for ($y = 0; $y < $this->height; $y += $wmHeight + $spacing) {
            for ($x = 0; $x < $this->width; $x += $wmWidth + $spacing) {
                imagecopymerge($this->srcImage, $wmResource, $x, $y, 0, 0, $wmWidth, $wmHeight, $opacity);
            }
        }
        imagedestroy($wmResource);
        return $this;
    }

    /** 保存/输出 */
    public function save(string $destPath, int $quality = 90): bool
    {
        $dir = dirname($destPath);
        if (!is_dir($dir)) mkdir($dir, 0755, true);

        switch ($this->mime) {
            case 'image/jpeg':
                return imagejpeg($this->srcImage, $destPath, $quality);
            case 'image/png':
                return imagepng($this->srcImage, $destPath, 9); // 压缩级别 0-9
            case 'image/gif':
                return imagegif($this->srcImage, $destPath);
            case 'image/webp':
                return imagewebp($this->srcImage, $destPath, $quality);
            default:
                throw new \RuntimeException("不支持的输出格式: {$this->mime}");
        }
    }

    public function output(int $quality = 90): void
    {
        header('Content-Type: ' . $this->mime);
        switch ($this->mime) {
            case 'image/jpeg': imagejpeg($this->srcImage, null, $quality); break;
            case 'image/png':  imagepng($this->srcImage, null, 9); break;
            case 'image/gif':  imagegif($this->srcImage); break;
            case 'image/webp': imagewebp($this->srcImage, null, $quality); break;
        }
    }

    public function __destruct()
    {
        if ($this->srcImage) imagedestroy($this->srcImage);
    }

    // ===== 私有方法 =====
    private function createFromSource(string $path)
    {
        switch ($this->mime ?? mime_content_type($path)) {
            case 'image/jpeg': return imagecreatefromjpeg($path);
            case 'image/png':  return imagecreatefrompng($path);
            case 'image/gif':  return imagecreatefromgif($path);
            case 'image/webp': return imagecreatefromwebp($path);
            case 'image/bmp':  return imagecreatefrombmp($path);
            default: return false;
        }
    }

    private function calcPosition(int $wmW, int $wmH, int $pos, int $offX, int $offY): array
    {
        // 九宫格: 1左上 2上中 3右上 4左中 5中心 6右中 7左下 8下中 9右下
        $x = $offX;
        $y = $offY;
        switch ($pos) {
            case 1: $x = $offX; $y = $offY; break;
            case 2: $x = ($this->width - $wmW) / 2; $y = $offY; break;
            case 3: $x = $this->width - $wmW - $offX; $y = $offY; break;
            case 4: $x = $offX; $y = ($this->height - $wmH) / 2; break;
            case 5: $x = ($this->width - $wmW) / 2; $y = ($this->height - $wmH) / 2; break;
            case 6: $x = $this->width - $wmW - $offX; $y = ($this->height - $wmH) / 2; break;
            case 7: $x = $offX; $y = $this->height - $wmH - $offY; break;
            case 8: $x = ($this->width - $wmW) / 2; $y = $this->height - $wmH - $offY; break;
            case 9: $x = $this->width - $wmW - $offX; $y = $this->height - $wmH - $offY; break;
        }
        return [max(0, (int)$x), max(0, (int)$y)];
    }
}

使用示例

<?php
require_once 'ImageWatermark.php';

// 1. 单图片水印 (右下角,50% 透明度)
$watermark = new ImageWatermark('uploads/photo.jpg');
$watermark->addImageWatermark('assets/logo.png', 9, 50, 20, 20)
          ->save('uploads/photo_wm.jpg');

// 2. 文字水印 (中心,白色,80% 透明度,旋转 -30°)
$watermark2 = new ImageWatermark('uploads/photo.jpg');
$watermark2->addTextWatermark(
    '© 2024 MySite.com',
    'fonts/msyh.ttc',
    36,
    [255, 255, 255],
    5,  // 中心
    80,
    0, 0,
    -30 // 旋转角度
)->output(); // 直接浏览器输出

// 3. 平铺水印 (全覆盖,30% 透明度)
$watermark3 = new ImageWatermark('uploads/photo.jpg');
$watermark3->addTiledWatermark('assets/logo_small.png', 30, 100)
           ->save('uploads/photo_tiled.jpg');

关键技术点

场景函数选择注意事项
水印图片本身透明 (PNG)imagecopy()保留 Alpha 通道,opacity 参数无效
水印图片不透明 (JPG) 需整体半透明imagecopymerge()opacity 0-100,100 为不透明
文字水印带透明度imagecolorallocatealpha()alpha 0=不透明, 127=全透明,需换算
大图片内存溢出分块处理 / 升级 PHP / 用 ImageMagickGD 全内存解码,5000px 以上慎用

结语

GD 水印核心是 imagecopymerge (不透明源+透明度) 与 imagecopy (透明源) 的选择。封装为类后,链式调用支持图片/文字/平铺多种模式组合。生产环境大图/高并发建议迁移到 ImageMagick (imagick 扩展),支持流式处理、更低内存、更丰富特效。

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