php-Imagick 实现密集文字水印工具

2025-09-10 php 水印

ImageWatermark

一个基于 Imagick 的图片加水印工具,支持从本地文件或远程 URL 加载图片并插入文字水印。

安装要求

使用方法

从远程 URL 添加水印

1
2
3
4
5
6
7
8
9
use App\Helper\ImageWatermark;
try {
$outputFile = __DIR__ . '/output.jpg';

ImageWatermark::applyWatermarkFromUrl(__DIR__,'https://example.com/demo.jpg');
echo "水印已生成: $outputFile\n";
} catch (\Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}

从本地文件添加水印

1
2
3
4
5
6
7
8
9
10
use App\Helper\ImageWatermark;
try {
$sourceFile = __DIR__ . '/source.jpg';
$outputFile = __DIR__ . '/output.jpg';

ImageWatermark::applyWatermark($sourceFile,$outputFile);
echo "水印已生成: $outputFile\n";
} catch (\Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}

参数说明

参数名类型默认值说明
$tmpPathstring临时目录路径(仅 applyWatermarkFromUrl 使用,用于保存下载的图片)
$srcURLstring远程图片 URL(仅 applyWatermarkFromUrl 使用)
$srcImagestring源图片路径(本地文件)
$savePathstring | nullnull输出路径,传 null 则直接输出图片到浏览器
$textstring‘照片来源于pixabay’水印文字
$colorstring‘#050629’十六进制颜色值,如 #FF0000
$alphafloat0.2 或 0.3透明度,范围 0~1
$angleint-24水印文字旋转角度(度)
$spacefloat0.5行间距倍数,用于控制文字密度
$sizeint | nullnull字体大小,默认自动根据图片尺寸计算
$fontFilestring | null内置字体路径字体文件路径,如果为空则使用默认字体

下载字体步骤

  1. 访问官网:打开浏览器,访问 https://www.alibabafonts.com
  2. 选择字体:在首页,您将看到“阿里巴巴普会体”字体。点击该字体的“下载”按钮。
  3. 选择版本:根据您的需求,选择适合的字体版本进行下载。
  4. 下载并解压:下载完成后,解压缩文件,您将获得字体文件(通常为 .ttf.otf 格式)。

ImageWatermark源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php

namespace App\Helper;

use Imagick;
use ImagickDraw;
use ImagickPixel;

class ImageWatermark
{
static function applyWatermarkFromUrl($tmpPath, $srcURL,$text="自定义水印", $color = '#050629', $alpha = 0.2, $angle = -24, $space = 0.5, $size = null, $fontFile = null)
{
$path = parse_url($srcURL, PHP_URL_PATH);
$fileInfo = pathinfo($path);
$filename = $fileInfo['filename'] . '.' . $fileInfo['extension'];
$newFileName = $fileInfo['filename'] . '-water.png';
// 判断目录是否存在
if (!is_dir($tmpPath)) {
if (!mkdir($tmpPath, 0755, true)) {
throw new \Exception("创建目录失败" . $tmpPath);
}
}
// 下载文件
$content = @file_get_contents($srcURL);
if ($content === false) {
throw new \Exception("获取远程文件失败" . $srcURL);
}
// 保存文件
$oldFileName = $tmpPath . $filename;
if (file_put_contents($oldFileName, $content) === false) {
throw new \Exception("下载远程文件失败 " . $srcURL);
}
$savePath = $tmpPath . $newFileName;
return self::applyWatermark($oldFileName, $savePath, $text, $color, $alpha, $angle, $space, $size, $fontFile);
}

/**
* 生成密集文字水印图片
*
* @param string $srcImage 源图片路径
* @param string|null $savePath 保存路径,如果为 null 则直接输出下载
* @param string $text 水印文字
* @param string $color 十六进制颜色
* @param float $alpha 透明度 0~1
* @param int $angle 旋转角度
* @param float $space 间距倍数
* @param string|null $fontFile 字体路径
*
* @return bool|string 返回保存路径或 false
* @throws \ImagickDrawException
* @throws \ImagickException
* @throws \ImagickPixelException
*/
static function applyWatermark($srcImage, $savePath = null, $text="自定义水印", $color = '#050629', $alpha = 0.3, $angle = -24, $space = 0.5, $size = null, $fontFile = null)
{
// 检查源图片是否存在,不存在直接返回 false
if (!file_exists($srcImage)) {
throw new \Exception("文件不存在 " . $srcImage);
}
// 创建 Imagick 对象并获取图片宽高
$im = new Imagick($srcImage);
$width = $im->getImageWidth();
$height = $im->getImageHeight();
// 创建绘图对象
$draw = new ImagickDraw();
// 如果未指定字体文件,使用默认字体 FZCHS.TTF
if (!$fontFile) $fontFile = __DIR__ . '/AlibabaPuHuiTi-3-105-Heavy.ttf';
// 检查字体文件是否存在
if (!file_exists($fontFile)) {
throw new \Exception("字体不存在 " . $fontFile);
}
if (is_null($size)) {
// 动态计算字体大小
$size = max(4, min($width, $height) / 30);
}
$draw->setFont($fontFile);
$draw->setFontSize($size);
// 设置文字颜色及透明度,alpha 范围 0~1
$draw->setFillColor(new ImagickPixel(sprintf(
'rgba(%d,%d,%d,%f)',
hexdec(substr($color, 1, 2)), // 红色
hexdec(substr($color, 3, 2)), // 绿色
hexdec(substr($color, 5, 2)), // 蓝色
$alpha // 透明度
)));

// 获取文字度量信息
$metrics = $im->queryFontMetrics($draw, $text);
$textWidth = $metrics['textWidth']; // 文字宽度
$textHeight = $metrics['textHeight']; // 文字高度

// 计算旋转角度对应的文字外接矩形
$rad = deg2rad(abs($angle));
$rotW = $textWidth * cos($rad) + $textHeight * sin($rad); // 旋转后的宽
$rotH = $textWidth * sin($rad) + $textHeight * cos($rad); // 旋转后的高

// 设置横纵间距
$hSpacing = $rotW + 5; // 横向间距,加 5px padding
$vSpacing = $rotH * $space; // 纵向间距,乘以行间距倍数

// 计算需要填充的列数和行数
$cols = ceil($width / $hSpacing);
$rows = ceil($height / $vSpacing);

// 双重循环填充文字
for ($i = 0; $i <= $cols; $i++) {
for ($j = 0; $j <= $rows; $j++) {
$x = $i * $hSpacing; // 横坐标
$y = $j * $vSpacing; // 纵坐标
$im->annotateImage($draw, $x, $y, $angle, $text); // 绘制文字
}
}

// 输出或保存图片
if ($savePath) {
$im->setImageFormat('png'); // 确保 PNG 输出
$im->writeImage($savePath); // 保存到指定路径
$im->destroy(); // 释放内存
return $savePath; // 返回保存路径
} else {
header('Content-Type: image/png'); // 输出到浏览器
echo $im;
$im->destroy(); // 释放内存
exit;
}

}
}