很多写作平台和论坛 例如 知乎、掘金、微博 都会为上传的图片添加 Logo 和文字水印。我个人是不太喜欢给图片加水印的,有的图片加了水印可能会挡住一部分内容,比较影响观看。前段时间我的博客被一个采集站采集,这个采集站用的程序还能把我文章中出现的有关我网站的链接替换为他网站的链接,图片也能下载到他的服务器,最终我只能到 Google 举报。虽然我的网站不盈利,也没有什么高深的内容,但也不喜欢被随便采集和复制。

对于采集站来说,文章中出现的站内链接和文本都很容易替换,只有图片是不容易替换的,除非手动去水印。有了图片水印,至少能在采集站留下一些本站的信息,虽然搜索引擎无法识别,但是也能让人看到。

能给图片添加水印的软件有很多,基本上每个图片处理软件都能添加水印,但是我不可能手动给每张图片添加水印,需要批量添加。能批量添加水印的软件我目前找到的有格式工厂,但是我的图片大小不一,有的宽度能达到 1400,有的只有 100,格式工厂添加的水印会出现过大或过小的情况,如果水印在右下方的话还会出现宽度不够导致水印缺失的情况。

我的需求是水印要能根据图片的大小改变位置和内容,但是水印字体大小不能改变。能满足我需求的就只能是自己编写一个程序来添加水印,在我会的编程语言中 PHP 是比较方便的,这里就简单写一下给图片添加文字水印和图片水印的过程。

文字水印

我博客中的图片使用的是文字水印,下面就以我用的代码为例:

// 获取 src 目录下的所有文件
$imgFileList = glob('src/*');
// 如果没有获取文件就不再往下执行
if (count($imgFileList) < 1) exit('没有图片文件');

$count = 0;  // 记录成功添加水印的图片数量
$neglect = 0;  // 记录跳过的图片数量
$imgType = array('image/jpeg', 'image/jpg', 'image/png');  // 支持的图片类型

foreach ($imgFileList as $imgFile) {
    // 获取图片宽高和类型
    $imgInfo = getimagesize($imgFile);
    // 判断是否是支持的图片类型
    if (!in_array($imgInfo['mime'], $imgType)) {
        echo basename($imgFile) . " 不支持的图片类型!\n";
        continue;
    }

    // 根据图片类型创建图像画布
    if ($imgInfo['mime'] == 'image/jpeg' or $imgInfo['mime'] == 'image/jpg') {
        $img = imagecreatefromjpeg($imgFile);
    }else {
        $img = imagecreatefrompng($imgFile);
    }

    $text = 'Mr. Ma\'s Blog  misterma.com';  // 水印文本内容
    $textColor = imagecolorallocate($img, 255, 34, 34);  // 水印颜色
    $fontFile = 'D:\laragon\www\test\font\arial.ttf';  // 水印字体文件
    $textX = $imgInfo[0] - 194;  // 文字的横向起始位置
    $textY = $imgInfo[1] - 5;  // 文字的纵向基线位置

    // 如果图片的宽度小于 194 就改变水印内容和位置
    if ($imgInfo[0] <= 194 && $imgInfo[0] >= 96) {
        $textX = $imgInfo[0] - 96;
        $text = 'misterma.com';
    }

    // 如果图片宽度小于 96 就直接跳过
    if ($imgInfo[0] < 96) {
        $neglect ++;
        echo basename($imgFile) . " 宽度 < 88 已跳过!\n";
        continue;
    }

    // 添加文字水印
    imagettftext($img, 16, 0, $textX, $textY, $textColor, $fontFile, $text);

    // 根据图片类型输出图片文件
    if ($imgInfo['mime'] == 'image/jpeg' or $imgInfo['mime'] == 'image/jpg') {
        if (!imagejpeg($img, 'dist/' . basename($imgFile), 80)) {
            echo basename($imgFile) . " 导出失败!";
        }else {
            $count ++;
        }
    }else {
        if (!imagepng($img, 'dist/' . basename($imgFile))) {
            echo basename($imgFile) . " 导出失败!";
        }else {
            $count ++;
        }
    }
    // 销毁图像画布
    imagedestroy($img);
}

// 全部添加完毕后输出结果
echo "图片数量:" . count($imgFileList) . "\n";
echo "已添加水印:{$count}\n";
echo "已跳过:{$neglect}";

下面是详细的代码说明:

  1. 使用 glob 获取当前目录下的 src 目录下的所有文件。
  2. 判断一下文件列表数组是否为空,如果为空就不再往下执行,否则就循环添加水印。
  3. 使用 getimagesize 函数获取图片的宽高和类型。
  4. 使用 in_array$imgType 数组中搜索图片类型,如果搜索不到就直接跳过,否则就往下执行。
  5. 判断图片类型,如果是 jpgjpeg 就使用 imagecreatefromjpeg 创建图像画布,否则就使用 imagecreatefrompng 创建图像画布。
  6. 定义文字水印内容、文字颜色、字体文件、文字水印的左侧起始位置和纵向基线位置,我的水印左侧位置是图片的宽度 - 194,纵向基线是图片高度 - 5,这样就可以让文字水印保持在右下方。
  7. 如果图片宽度小于 194 但大于 94 就减少一部分水印内容和调整水印的左侧位置。
  8. 如果图片宽度小于 94 就直接跳过,不添加水印。
  9. 使用 imagettftext 函数给图片添加文字水印。
  10. 判断图片类型,如果是 jpegjpg 就使用 imagejpeg 输出图片,否则就使用 imagepng 输出图片,图片会被输出到当前目录下的 dist 目录,图片的文件名和原来是一样的。
  11. 使用 imagedestroy 销毁图像画布。
  12. 输出统计结果,因为我是在命令行运行,所以换行都是使用 \n

字体文件位置我使用的是绝对路径,使用相对路径会出现无法找到字体文件的情况。Windows 的字体文件在 C:\Windows\Fonts 中,需要字体文件可以到 C:\Windows\Fonts 中拷贝,也可以到网上下载字体文件。

注意,如果你的网站是商业网站的话,需要看一下字体是否能免费商用!

我上面的文字水印位置也是写死的,只适用于我设置的文字内容和字体,如果是其他文字和字体的话就需要手动调整。

下面是一些参数比较多的函数说明:

imagecolorallocate() 函数:

创建图像颜色,包含 4 个参数:

  • image: 图像画布资源
  • red: 红色 0 - 255
  • green: 绿色 0 - 255
  • blue: 蓝色 0 - 255

返回一个颜色标识符。

imagettftext() 函数:

用 TrueType 字体向图像写入文本,包含 8 个参数:

  • image: 图像画布资源
  • size: 字体的尺寸
  • angle: 文字角度,0 为正常角度
  • x: 文字左下角的位置
  • y: 文字基线位置
  • color: 文字颜色,可以使用 imagecolorallocate 生成
  • fontFile: 字体文件的路径
  • text: 文字内容

imagejpeg() 函数:

输出图像到浏览器或文件,包含 3 个参数:

  • image: 图像画布资源
  • fileName: 输出的文件路径,如果为 null 会直接输出图像到浏览器
  • quality: 图片质量,从 0 到 100,默认为 75

成功会返回 true ,失败返回 false

imagepng() 函数:

输出 png 格式的图像到浏览器或文件,包含 3 个参数:

  • image: 图像画布资源
  • fileName: 输出文件路径,如果为 null 就直接在浏览器输出
  • quality: 图片质量,从 0 -到 9

成功返回 true ,失败返回 false

文字水印的效果可以参考我博客中的图片。

图片水印

下面是一张需要添加水印的大图和图片水印:

大图和P3D图片水印

下面就把 P3D 的图标添加到左侧的大图中:

$srcImgFile = 'src/screenshot.jpg';  // 大图的文件名
$logoImgFile = 'src/p3d_logo.png';  // Logo 图片的文件名

// 获取大图和 Logo 图片的信息
$srcImgInfo = getimagesize($srcImgFile);
$logoImgInfo = getimagesize($logoImgFile);

// 创建大图的图像画布
$srcImg = imagecreatefromjpeg($srcImgFile);
// 创建 Logo 图片的图像画布
$logoImg = imagecreatefrompng($logoImgFile);

// Logo 图像的拷贝参数
$srcX = 0;  // Logo 图片的左侧
$srcY = 0;  // Logo 图片的顶部
$srcW = $logoImgInfo[0];  // Logo 图片的宽度
$srcH = $logoImgInfo[1];  // Logo 图片的高度
// 大图的合并参数
$dstX = $srcImgInfo[0] - $logoImgInfo[0] - 20;  // 合并图像的横向起始位置
$dstY = $srcImgInfo[1] - $logoImgInfo[1] - 20;  // 合并图像的纵向起始位置
$pct = 50;  // 合并程度

// 把 Logo 图像拷贝合并到大图
imagecopymerge($srcImg, $logoImg, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $pct);

// 把图片输出到当前目录下的 dist 目录
imagejpeg($srcImg, 'dist/' . basename($srcImgFile), 80);

// 销毁图像画布
imagedestroy($srcImg);
imagedestroy($logoImg);

我上面只是简单演示,判断类型和批量添加可以参考文字水印的代码。

下面是详细的代码说明:

  1. $srcImgFile 是要添加水印的大图,$logoImgFile 是 水印 Logo。
  2. 使用 getimagesize 获取大图和水印 Logo 图片的信息。
  3. 使用 imagecreatefromjpegimagecreatefrompng 创建大图和水印图片的图像画布。
  4. 设置 水印 Logo 的拷贝参数和大图的合并参数。我上面设置的拷贝图像的起始位置就是从左上角开始,宽度和高度就是水印图片的宽高,合并位置是大图的宽高减水印图片的宽高减20,合并后水印就会在右下方。
  5. 使用 imagecopymerge 把水印 Logo 图像拷贝合并到大图。
  6. 使用 imagejpeg 把图片输出到当前目录下的 dist 目录。
  7. 使用 imagedestroy 销毁图像画布。

下面是一些参数比较多的函数说明:

imagecopymerge() 函数:

把一张图像拷贝合并到另一张图像,下面是参数说明:

  • dst_im: 合并的目标图像,也就是我上面的大图
  • src_im: 被拷贝的图像,也就是我上面的水印图像
  • dst_x: 拷贝图像的横向起始位置,如果为 0 就从最左侧开始
  • dst_y: 拷贝图像的纵向起始位置,如果为 0 就从最顶部开始
  • src_x: 合并的横向起始位置,如果为 0 图片将会在最左侧
  • src_y: 合并的纵向起始位置,如果为 0 图片将会在最顶部
  • src_w: 拷贝图像的宽度
  • src_h: 拷贝图像的高度
  • pct: 图像合并程度,如果为 0 覆盖的图像会完全透明,基本上等于什么都没做,100 为不透明

下面是合并后的效果:

合并后包含P3D图标的图片

我上面的合并程度设置为 50,也就是半透明,P3D 图标的背景原本是透明的,合并后背景会变成黑色。