这段时间网站里的文章逐渐多起来了,但是我注意到加载多图文章的时候速度十分感人
在前端看了一下,发现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); }
就大功告成了,这下不用担心水管被图片挤爆了
评论