这段时间网站里的文章逐渐多起来了,但是我注意到加载多图文章的时候速度十分感人
在前端看了一下,发现Typecho默认的上传图片居然是PNG,一点没有压缩过的原图
这样本就让我20Mbps的服务器更加雪上加霜了
于是痛定思痛,决定把图片全部换成webp并且加上水印防止偷图,所以我在Github上找到了一个压缩加水印插件
xxhzm/Watermark: 一个功能强大的Typecho图片处理插件,支持自动添加文字水印、WebP格式转换等功能

我安装上以后发现完全没用,于是又找了一大堆的其他插件,依然没用
后来反应过来,我用的是富文本编辑器UEditor,它的上传接口不是Typecho的默认接口
于是乎,我翻了翻插件的配置文件,注意到了
// 服务器统一请求接口路径 //serverUrl: "/ueditor-plus/_demo_server/handle.php", serverUrl: URL + "php/controller.php",
这个玩意
这就说明,UEditor Plus For Typecho这个插件的上传接口在这里
打开后终于发现了图片处理类的相关代码,在action_upload.php里

我在原来的action_upload.php的末尾添加了一些额外的处理
// 如果上传成功且是图片,则进行图片处理
if ($fileInfo['state'] === 'SUCCESS' && in_array($_GET['action'], ['uploadimage', 'uploadscrawl'])) {
// 获取插件配置
$pluginConfig = \Typecho\Widget::widget('Widget_Options')->plugin('UEditorPlus');
// 获取文件的完整路径
$filePath = $_SERVER['DOCUMENT_ROOT'] . $fileInfo['url'];
// 检查文件是否存在
if (file_exists($filePath)) {
$imageProcessor = new ImageProcessor();
// 处理图片(水印 + WebP转换)
$result = $imageProcessor->processImage($filePath, $pluginConfig, $fileInfo);
// 如果处理成功,更新文件信息
if ($result && isset($result['url'])) {
$fileInfo['url'] = $result['url'];
$fileInfo['title'] = $result['title'];
$fileInfo['type'] = $result['type'];
$fileInfo['size'] = $result['size'];
}
}
}
/* 返回数据 */
return json_encode($fileInfo);再在相同位置创建一个imageprocessor.php文件用于处理水印和压缩相关的操作,大体思路是,先检测图片的大小有没有到阈值,如果到了,就将其压缩为webp格式并添加上水印,然后给这些字添加个黑色边框,防止在纯色背景下看不到
但注意,转换为Webp格式需要PHP有GD拓展,不过宝塔默认的PHP已经把这个拓展安装好了,就不需要额外操作了
于是叫AI猛地一通操作,就把imageprocessor.php给造出来了,最后回到这个插件本身的Plugin.php里,添加一些必要的配置项
<?php
/**
* 图片处理类
* 功能:WebP转换、添加水印
* 注意:只是演示代码,无法直接copy过去用喔
*/
class ImageProcessor
{
/**
* 处理上传的图片
* @param string $filePath 图片文件路径
* @param object $config 插件配置
* @param array $fileInfo 文件信息
* @return array|false 处理结果
*/
public function processImage($filePath, $config, $fileInfo)
{
// 检查是否为图片文件
if (!$this->isImage($filePath)) {
return false;
}
// 获取图片信息
$imageInfo = getimagesize($filePath);
if (!$imageInfo) {
return false;
}
$width = $imageInfo[0];
$height = $imageInfo[1];
$type = $imageInfo[2];
// 检查图片尺寸是否达到处理阈值
$minWidth = isset($config->minWidth) ? intval($config->minWidth) : 200;
$minHeight = isset($config->minHeight) ? intval($config->minHeight) : 200;
if ($width < $minWidth || $height < $minHeight) {
return false;
}
// 创建图片资源
$image = $this->createImageResource($filePath, $type);
if (!$image) {
return false;
}
// 添加水印
if (isset($config->watermarkEnable) && $config->watermarkEnable == '1') {
$image = $this->addWatermark($image, $width, $height, $config);
}
$result = $fileInfo;
// WebP转换
if (isset($config->webpEnable) && $config->webpEnable == '1' && function_exists('imagewebp')) {
$webpPath = $this->convertToWebP($image, $filePath, $config);
if ($webpPath) {
// 安全修改:移除直接使用服务器文档根目录的代码
$result['url'] = $this->getRelativePath($webpPath); // 使用相对路径
$result['title'] = basename($webpPath);
$result['type'] = '.webp';
$result['size'] = filesize($webpPath);
// 是否删除原图
if (!isset($config->webpKeepOriginal) || $config->webpKeepOriginal != '1') {
unlink($filePath);
}
}
} else {
// 不转WebP,但需要保存添加水印后的图片
if (isset($config->watermarkEnable) && $config->watermarkEnable == '1') {
$this->saveImage($image, $filePath, $type);
$result['size'] = filesize($filePath);
}
}
// 清理内存
imagedestroy($image);
return $result;
}
/**
* 检查是否为图片文件
*/
private function isImage($filePath)
{
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
return in_array($ext, ['jpg', 'jpeg', 'png', 'gif']);
}
/**
* 创建图片资源
*/
private function createImageResource($filePath, $type)
{
switch ($type) {
case IMAGETYPE_JPEG:
return imagecreatefromjpeg($filePath);
case IMAGETYPE_PNG:
return imagecreatefrompng($filePath);
case IMAGETYPE_GIF:
return imagecreatefromgif($filePath);
default:
return false;
}
}
/**
* 添加水印
*/
private function addWatermark($image, $width, $height, $config)
{
// 检查是否有水印图片
if (!empty($config->watermarkImage) && file_exists($config->watermarkImage)) {
return $this->addImageWatermark($image, $width, $height, $config);
}
// 使用文字水印
elseif (!empty($config->watermarkText)) {
return $this->addTextWatermark($image, $width, $height, $config);
}
return $image;
}
/**
* 添加图片水印
*/
private function addImageWatermark($image, $width, $height, $config)
{
$watermarkPath = $config->watermarkImage;
$watermarkInfo = getimagesize($watermarkPath);
if (!$watermarkInfo) {
return $image;
}
// 创建水印图片资源
$watermark = $this->createImageResource($watermarkPath, $watermarkInfo[2]);
if (!$watermark) {
return $image;
}
$wmWidth = $watermarkInfo[0];
$wmHeight = $watermarkInfo[1];
// 计算水印位置
$position = $this->calculatePosition($width, $height, $wmWidth, $wmHeight, $config);
// 获取透明度
$opacity = isset($config->watermarkOpacity) ? intval($config->watermarkOpacity) : 50;
// 添加水印
$this->imagecopymerge_alpha($image, $watermark, $position['x'], $position['y'], 0, 0, $wmWidth, $wmHeight, $opacity);
imagedestroy($watermark);
return $image;
}
/**
* 添加文字水印(带黑边效果,不加粗)
*/
private function addTextWatermark($image, $width, $height, $config)
{
$text = $config->watermarkText;
$fontSize = isset($config->watermarkFontSize) ? intval($config->watermarkFontSize) : 24;
// 获取不透明度设置
$opacity = isset($config->watermarkOpacity) ? intval($config->watermarkOpacity) : 100;
$alpha = 127 - intval($opacity * 127 / 100);
// 定义颜色:白色文字,黑色边框
$textColor = imagecolorallocatealpha($image, 255, 255, 255, $alpha);
$borderColor = imagecolorallocatealpha($image, 0, 0, 0, $alpha);
// 安全修改:字体文件路径应从配置获取,而非硬编码
$fontPath = isset($config->fontPath) ? $config->fontPath : null;
$useTTF = !empty($fontPath) && file_exists($fontPath);
if ($useTTF) {
// 使用TTF字体计算文字尺寸
$textBox = imagettfbbox($fontSize, 0, $fontPath, $text);
$textWidth = abs($textBox[4] - $textBox[0]);
$textHeight = abs($textBox[5] - $textBox[1]);
} else {
// 使用内置字体
$fontSize = 5;
$textWidth = strlen($text) * 10;
$textHeight = 15;
}
// 计算文字位置
$position = $this->calculatePosition($width, $height, $textWidth, $textHeight, $config);
$x = $position['x'];
$y = $position['y'];
// 绘制文字水印
if ($useTTF) {
$baseY = $y + $textHeight;
// 1. 绘制黑边(8方向描边)
$borderOffsets = [
[-1, -1], [0, -1], [1, -1],
[-1, 0], [1, 0],
[-1, 1], [0, 1], [1, 1]
];
foreach ($borderOffsets as $offset) {
imagettftext($image, $fontSize, 0, $x + $offset[0], $baseY + $offset[1], $borderColor, $fontPath, $text);
}
// 2. 绘制主文字
imagettftext($image, $fontSize, 0, $x, $baseY, $textColor, $fontPath, $text);
} else {
// 使用内置字体
// 1. 绘制黑边
$borderOffsets = [
[-1, -1], [0, -1], [1, -1],
[-1, 0], [1, 0],
[-1, 1], [0, 1], [1, 1]
];
foreach ($borderOffsets as $offset) {
imagestring($image, $fontSize, $x + $offset[0], $y + $offset[1], $text, $borderColor);
}
// 2. 绘制主文字
imagestring($image, $fontSize, $x, $y, $text, $textColor);
}
return $image;
}
/**
* 计算水印位置
*/
private function calculatePosition($imgWidth, $imgHeight, $wmWidth, $wmHeight, $config)
{
$margin = isset($config->watermarkMargin) ? intval($config->watermarkMargin) : 10;
$position = isset($config->watermarkPosition) ? $config->watermarkPosition : 'bottom-right';
switch ($position) {
case 'top-left':
return ['x' => $margin, 'y' => $margin];
case 'top-right':
return ['x' => $imgWidth - $wmWidth - $margin, 'y' => $margin];
case 'bottom-left':
return ['x' => $margin, 'y' => $imgHeight - $wmHeight - $margin];
case 'bottom-right':
return ['x' => $imgWidth - $wmWidth - $margin, 'y' => $imgHeight - $wmHeight - $margin];
case 'center':
return ['x' => ($imgWidth - $wmWidth) / 2, 'y' => ($imgHeight - $wmHeight) / 2];
default:
return ['x' => $imgWidth - $wmWidth - $margin, 'y' => $imgHeight - $wmHeight - $margin];
}
}
/**
* 支持透明度的图片合并函数
*/
private function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct)
{
if (!isset($pct)) {
return false;
}
$pct /= 100;
// 获取图片的宽度和高度
$w = imagesx($src_im);
$h = imagesy($src_im);
// 创建一个临时图片
$cut = imagecreatetruecolor($src_w, $src_h);
// 拷贝源图片的相应部分到临时图片
imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
// 拷贝要合并的图片到临时图片
imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
// 设置临时图片为混合模式
imagealphablending($dst_im, true);
// 将临时图片合并到目标图片
imagecopymerge($dst_im, $cut, $dst_x, $dst_y, 0, 0, $src_w, $src_h, $pct * 100);
imagedestroy($cut);
}
/**
* 转换为WebP格式
*/
private function convertToWebP($image, $originalPath, $config)
{
$quality = isset($config->webpQuality) ? intval($config->webpQuality) : 80;
$webpPath = preg_replace('/\.(jpg|jpeg|png|gif)$/i', '.webp', $originalPath);
// 设置WebP支持透明度
imagesavealpha($image, true);
if (imagewebp($image, $webpPath, $quality)) {
return $webpPath;
}
return false;
}
/**
* 保存图片
*/
private function saveImage($image, $filePath, $type)
{
switch ($type) {
case IMAGETYPE_JPEG:
return imagejpeg($image, $filePath, 90);
case IMAGETYPE_PNG:
imagesavealpha($image, true);
return imagepng($image, $filePath);
case IMAGETYPE_GIF:
return imagegif($image, $filePath);
default:
return false;
}
}
/**
* 安全修改:获取相对路径的方法
* 替代直接使用 $_SERVER['DOCUMENT_ROOT']
*/
private function getRelativePath($fullPath)
{
// 这里应该根据实际情况实现路径转换逻辑
// 示例实现 - 实际使用时需要根据项目结构调整
$basePath = defined('BASE_UPLOAD_PATH') ? BASE_UPLOAD_PATH : '/uploads/';
return $basePath . basename($fullPath);
}
} public static function config(Form $form) {
// WebP转换设置
$webpEnable = new Radio('webpEnable', array('1' => '启用', '0' => '禁用'), '0', 'WebP转换', '启用后会自动将上传的PNG、JPG图片转换为WebP格式');
$form->addInput($webpEnable);
$webpQuality = new Text('webpQuality', null, '80', 'WebP质量', '设置WebP图片质量,范围1-100,数值越高质量越好但文件越大');
$form->addInput($webpQuality);
$webpKeepOriginal = new Radio('webpKeepOriginal', array('1' => '保留', '0' => '删除'), '0', '保留原图', '转换为WebP后是否保留原始图片文件');
$form->addInput($webpKeepOriginal);
// 水印设置
$watermarkEnable = new Radio('watermarkEnable', array('1' => '启用', '0' => '禁用'), '0', '图片水印', '启用后会为上传的图片添加水印');
$form->addInput($watermarkEnable);
$watermarkImage = new Text('watermarkImage', null, '', '水印图片路径', '水印图片的完整路径,支持PNG格式(推荐使用透明背景)');
$form->addInput($watermarkImage);
$watermarkText = new Text('watermarkText', null, '', '文字水印', '如果不设置水印图片,可以使用文字水印');
$form->addInput($watermarkText);
$watermarkFontSize = new Text('watermarkFontSize', null, '24', '水印字体大小', '设置文字水印的字体大小(像素),默认24');
$form->addInput($watermarkFontSize);
$watermarkPosition = new Select('watermarkPosition', array(
'top-left' => '左上角',
'top-right' => '右上角',
'bottom-left' => '左下角',
'bottom-right' => '右下角',
'center' => '居中'
), 'bottom-right', '水印位置', '选择水印在图片中的位置');
$form->addInput($watermarkPosition);
$watermarkOpacity = new Text('watermarkOpacity', null, '100', '水印不透明度', '设置水印不透明度,范围0-100,0为完全透明,100为完全不透明');
$form->addInput($watermarkOpacity);
$watermarkMargin = new Text('watermarkMargin', null, '10', '水印边距', '水印距离图片边缘的距离(像素)');
$form->addInput($watermarkMargin);
// 图片处理阈值设置
$minWidth = new Text('minWidth', null, '200', '最小处理宽度', '只有宽度大于此值的图片才会被处理(像素)');
$form->addInput($minWidth);
$minHeight = new Text('minHeight', null, '200', '最小处理高度', '只有高度大于此值的图片才会被处理(像素)');
$form->addInput($minHeight);
}就大功告成了,这下不用担心水管被图片挤爆了
评论