简单图床添加类似阿里云OSS动态水印功能

前言

以前觉得阿里云的OSS当图床非常的适合,配合CDN更是爽,博主更喜欢的是它提供了动态URL添加图片水印功能,适合一图多站点使用,通过不同的URL参数使用不同的水印,直到被5分钟刷了三千多费用后,博主回归了自建图床,使用简单图床虽然可以设置上传图片添加水印,但没办法实现一张图多用,像博主多个网站的,就要每个网站建一个图床,费事费空间,还不好管理

思路

由于简单图床是开源项目,可以二次开发,就把这个思路让AI跑了一下,都不用几分钟就跑通了,今天分享出来,有需要的朋友可以直接拿去用

效果

原图:https://img.mailberry.com.cn/i/2026/easyimageURLwatermark.webp

URL参数:?x-oss-process=image/watermark,text_TWFpbEJlcnJ5LmNvbS5jbg,type_ZmFuZ3poZW5naGVpdGk,size_18,shadow_50,t_70,g_se,x_10,y_10,color_ffffff

动态水印图:https://img.mailberry.com.cn/i/2026/easyimageURLwatermark.webp?x-oss-process=image/watermark,text_TWFpbEJlcnJ5LmNvbS5jbg,type_ZmFuZ3poZW5naGVpdGk,size_18,shadow_50,t_70,g_se,x_10,y_10,color_ffffff

效果图:

参数含义:

text_      水印base64代码 
size_18       字号 18
 shadow_50     阴影透明度 50
 t_70           文字透明度 70
 g_se           右下角
 x_10           距右边 10px
 y_10           距底部 10px
 color_ffffff   白色

更多参数请参考阿里云说明文档

https://help.aliyun.com/zh/oss/user-guide/add-watermarks

实现

第一步,创建一个PHP文件

路径和文件名/app/oss-process.php

复制代码

<?php
/**
 * EasyImages2.0 动态水印处理
 * 用法:
 * /picgo/2024/04/a.jpg?x-oss-process=image/watermark,text_TWFpbEJlcnJ5LmNvbS5jbg,size_18,t_70,g_se,x_10,y_10,color_ffffff
 */

error_reporting(0);

define('APP_ROOT', dirname(__DIR__));

$img = isset($_GET['img']) ? $_GET['img'] : '';
$process = isset($_GET['x-oss-process']) ? $_GET['x-oss-process'] : '';

if (!$img || !$process) {
    http_response_code(400);
    exit('Bad Request');
}

// 只允许站内图片,防止任意文件读取
$img = parse_url($img, PHP_URL_PATH);
$img = urldecode($img);
$img = '/' . ltrim($img, '/');

$realPath = realpath(APP_ROOT . $img);
$rootPath = realpath(APP_ROOT);

if (!$realPath || strpos($realPath, $rootPath) !== 0 || !is_file($realPath)) {
    http_response_code(404);
    exit('Image Not Found');
}

$ext = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
$allow = ['jpg', 'jpeg', 'png', 'webp'];

if (!in_array($ext, $allow)) {
    http_response_code(415);
    exit('Unsupported Image Type');
}

// 解析 x-oss-process
$params = parseOssProcess($process);

$text = isset($params['text']) ? base64url_decode($params['text']) : 'MailBerry.com.cn';
$text = trim($text);
$text = mb_substr($text, 0, 60, 'UTF-8');

$size = isset($params['size']) ? intval($params['size']) : 18;
$opacity = isset($params['t']) ? intval($params['t']) : 70;
$gravity = isset($params['g']) ? strtolower($params['g']) : 'se';
$x = isset($params['x']) ? intval($params['x']) : 10;
$y = isset($params['y']) ? intval($params['y']) : 10;
$color = isset($params['color']) ? preg_replace('/[^0-9a-fA-F]/', '', $params['color']) : 'ffffff';
$shadow = isset($params['shadow']) ? intval($params['shadow']) : 0;

$size = max(8, min($size, 120));
$opacity = max(0, min($opacity, 100));
$x = max(0, min($x, 1000));
$y = max(0, min($y, 1000));
$shadow = max(0, min($shadow, 100));

// 字体文件:可换成你自己的中文字体
$font = APP_ROOT . '/public/static/fonts/fangzhengheiti.ttf';

// 没有字体就用 DejaVuSans 兜底,Linux 常见
if (!is_file($font)) {
    $font = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf';
}

if (!is_file($font)) {
    http_response_code(500);
    exit('Font Not Found');
}

// 缓存,避免每次都生成
$cacheDir = APP_ROOT . '/i/cache/oss-process/';
if (!is_dir($cacheDir)) {
    mkdir($cacheDir, 0755, true);
}

$cacheKey = md5($realPath . '|' . filemtime($realPath) . '|' . $process);
$cacheFile = $cacheDir . $cacheKey . '.' . $ext;

if (is_file($cacheFile)) {
    outputImage($cacheFile, $ext);
    exit;
}

$image = openImage($realPath, $ext);
if (!$image) {
    http_response_code(500);
    exit('Image Open Failed');
}

imagealphablending($image, true);
imagesavealpha($image, true);

$width = imagesx($image);
$height = imagesy($image);

$rgb = hexToRgb($color);
$alpha = 127 - round($opacity * 127 / 100);
$textColor = imagecolorallocatealpha($image, $rgb[0], $rgb[1], $rgb[2], $alpha);

// 计算文字大小
$box = imagettfbbox($size, 0, $font, $text);
$textWidth = abs($box[2] - $box[0]);
$textHeight = abs($box[7] - $box[1]);

list($posX, $posY) = getPosition($gravity, $width, $height, $textWidth, $textHeight, $x, $y);

// 阴影
if ($shadow > 0) {
    $shadowAlpha = 127 - round($shadow * 127 / 100);
    $shadowColor = imagecolorallocatealpha($image, 0, 0, 0, $shadowAlpha);
    imagettftext($image, $size, 0, $posX + 1, $posY + 1, $shadowColor, $font, $text);
}

// 文字水印
imagettftext($image, $size, 0, $posX, $posY, $textColor, $font, $text);

saveImage($image, $cacheFile, $ext);
imagedestroy($image);

outputImage($cacheFile, $ext);
exit;


function parseOssProcess($process)
{
    $result = [];

    $process = trim($process);

    if (strpos($process, 'image/watermark,') === 0) {
        $process = substr($process, strlen('image/watermark,'));
    }

    $parts = explode(',', $process);

    foreach ($parts as $part) {
        $pos = strpos($part, '_');
        if ($pos === false) {
            continue;
        }

        $key = substr($part, 0, $pos);
        $value = substr($part, $pos + 1);

        $result[$key] = $value;
    }

    return $result;
}

function base64url_decode($data)
{
    $data = str_replace(['-', '_'], ['+', '/'], $data);
    $pad = strlen($data) % 4;

    if ($pad) {
        $data .= str_repeat('=', 4 - $pad);
    }

    return base64_decode($data);
}

function hexToRgb($hex)
{
    $hex = ltrim($hex, '#');

    if (strlen($hex) === 3) {
        $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
    }

    if (strlen($hex) !== 6) {
        $hex = 'ffffff';
    }

    return [
        hexdec(substr($hex, 0, 2)),
        hexdec(substr($hex, 2, 2)),
        hexdec(substr($hex, 4, 2)),
    ];
}

function getPosition($gravity, $imgW, $imgH, $textW, $textH, $x, $y)
{
    switch ($gravity) {
        case 'nw':
            $posX = $x;
            $posY = $y + $textH;
            break;

        case 'n':
            $posX = intval(($imgW - $textW) / 2);
            $posY = $y + $textH;
            break;

        case 'ne':
            $posX = $imgW - $textW - $x;
            $posY = $y + $textH;
            break;

        case 'w':
            $posX = $x;
            $posY = intval(($imgH + $textH) / 2);
            break;

        case 'center':
        case 'c':
            $posX = intval(($imgW - $textW) / 2);
            $posY = intval(($imgH + $textH) / 2);
            break;

        case 'e':
            $posX = $imgW - $textW - $x;
            $posY = intval(($imgH + $textH) / 2);
            break;

        case 'sw':
            $posX = $x;
            $posY = $imgH - $y;
            break;

        case 's':
            $posX = intval(($imgW - $textW) / 2);
            $posY = $imgH - $y;
            break;

        case 'se':
        default:
            $posX = $imgW - $textW - $x;
            $posY = $imgH - $y;
            break;
    }

    return [max(0, $posX), max($textH, $posY)];
}

function openImage($file, $ext)
{
    switch ($ext) {
        case 'jpg':
        case 'jpeg':
            return imagecreatefromjpeg($file);

        case 'png':
            return imagecreatefrompng($file);

        case 'webp':
            if (function_exists('imagecreatefromwebp')) {
                return imagecreatefromwebp($file);
            }
            return false;
    }

    return false;
}

function saveImage($image, $file, $ext)
{
    switch ($ext) {
        case 'jpg':
        case 'jpeg':
            imagejpeg($image, $file, 90);
            break;

        case 'png':
            imagepng($image, $file, 6);
            break;

        case 'webp':
            imagewebp($image, $file, 90);
            break;
    }
}

function outputImage($file, $ext)
{
    $mime = [
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png' => 'image/png',
        'webp' => 'image/webp',
    ];

    header('Content-Type: ' . $mime[$ext]);
    header('Cache-Control: public, max-age=31536000');
    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT');

    readfile($file);
}

字体文件“fangzhengheiti.ttf” 可以更换自己喜欢的,不上传也会兜底。

第二步,添加伪静态

我使用的是宝塔面板

image-20260513140452360

代码

location ~* ^/i/.*\.(jpg|jpeg|png|webp)$ {
    if ($query_string ~* "(^|&)x-oss-process=") {
        rewrite ^ /app/oss-process.php?img=$uri&$query_string last;
    }

    try_files $uri =404;
}

如果没有使用面板的,可以直接修改nginx

第三步上传工具添加自动化(可选)

上传同具不同,设置可能有出入,但大体上是一样的,博主用的 免灵图床Plus

image-20260513142353466

在文件名后面加上URL参数,也可以直接替换法。

总结

只要简单两步,在APP目录创建一个PHP文件,添加一个伪静态,最后在上传工具加上参数或者直接替换法,以后就可以统一使用一个图床,只需要修改链接就有不同的水印了

相关推荐

暂无评论

发表评论

您的电子邮件地址不会被公开,必填项已用*标注。