2025-06-17 09:46:57 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Service;
|
|
|
|
|
|
|
|
|
|
use App\Models\ImagesModel;
|
|
|
|
|
use Illuminate\Http\UploadedFile;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
|
|
class ImageService
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* 处理上传的图片
|
|
|
|
|
*
|
|
|
|
|
* @param UploadedFile $imageFile
|
|
|
|
|
* @return array 包含三个路径的数组
|
|
|
|
|
*/
|
2025-06-17 14:50:28 +08:00
|
|
|
public function processImage($imageFile, $pid = 1)
|
2025-06-17 09:46:57 +08:00
|
|
|
{
|
|
|
|
|
// 1. 保存原始图片
|
2025-06-17 15:15:08 +08:00
|
|
|
$originalPath = $this->storeOriginalImage($imageFile);
|
2025-06-17 09:46:57 +08:00
|
|
|
|
|
|
|
|
// 2. 无损压缩图片
|
|
|
|
|
$compressedPath = $this->compressToWebp($originalPath);
|
|
|
|
|
|
|
|
|
|
// 3. 添加水印
|
|
|
|
|
$watermarkedPath = $this->addWatermark($compressedPath);
|
|
|
|
|
|
|
|
|
|
ImagesModel::create([
|
2025-06-17 14:50:28 +08:00
|
|
|
'photo_album_id' => $pid,
|
2025-06-17 09:46:57 +08:00
|
|
|
'title' => date('Y-m-d H:i:s'),
|
|
|
|
|
'original' => $originalPath,
|
|
|
|
|
'compressed' => $compressedPath,
|
|
|
|
|
'watermarked' => $watermarkedPath,
|
|
|
|
|
'created_at' => time()
|
|
|
|
|
]);
|
|
|
|
|
return [
|
|
|
|
|
'original' => $originalPath,
|
|
|
|
|
'compressed' => $compressedPath,
|
|
|
|
|
'watermarked' => $watermarkedPath
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 保存原始图片
|
|
|
|
|
*/
|
|
|
|
|
protected function storeOriginalImage($imageFile)
|
|
|
|
|
{
|
2025-06-17 15:06:22 +08:00
|
|
|
$path = 'images/original/cc_'. date('Ymdhis') . Str::random(40) . '.' . $imageFile->getClientOriginalExtension();
|
2025-06-17 15:15:08 +08:00
|
|
|
Storage::put($path, file_get_contents($imageFile));
|
2025-06-17 09:46:57 +08:00
|
|
|
return $path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用WebP格式高效压缩图片
|
|
|
|
|
*
|
|
|
|
|
* @param string $originalPath 原始图片路径
|
|
|
|
|
* @param int $quality WebP压缩质量(1-100)
|
|
|
|
|
* @param int|null $maxWidth 最大宽度(可选)
|
|
|
|
|
* @param int|null $maxHeight 最大高度(可选)
|
|
|
|
|
* @return string 压缩后的图片路径
|
|
|
|
|
*/
|
|
|
|
|
protected function compressToWebp($originalPath, $quality = 85, $maxWidth = null, $maxHeight = null)
|
|
|
|
|
{
|
|
|
|
|
// 获取原始图片内容
|
|
|
|
|
$imageContent = Storage::get($originalPath);
|
|
|
|
|
$image = imagecreatefromstring($imageContent);
|
|
|
|
|
|
|
|
|
|
// 获取图片信息
|
|
|
|
|
$info = getimagesizefromstring($imageContent);
|
|
|
|
|
$width = $info[0];
|
|
|
|
|
$height = $info[1];
|
|
|
|
|
$mime = $info['mime'];
|
|
|
|
|
|
|
|
|
|
// 调整图片尺寸(如果指定了最大宽高)
|
|
|
|
|
if ($maxWidth || $maxHeight) {
|
|
|
|
|
// 计算新尺寸,保持宽高比
|
|
|
|
|
$originalRatio = $width / $height;
|
|
|
|
|
|
|
|
|
|
// 如果只指定了一个维度,自动计算另一个
|
|
|
|
|
if (!$maxWidth) {
|
|
|
|
|
$maxWidth = (int)($maxHeight * $originalRatio);
|
|
|
|
|
} elseif (!$maxHeight) {
|
|
|
|
|
$maxHeight = (int)($maxWidth / $originalRatio);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算最终尺寸(不超过原始尺寸)
|
|
|
|
|
$newWidth = min($width, $maxWidth);
|
|
|
|
|
$newHeight = min($height, $maxHeight);
|
|
|
|
|
|
|
|
|
|
// 创建新尺寸的画布
|
|
|
|
|
$resizedImage = imagecreatetruecolor($newWidth, $newHeight);
|
|
|
|
|
|
|
|
|
|
// 保留透明度 (PNG/GIF)
|
|
|
|
|
if ($mime == 'image/png' || $mime == 'image/gif') {
|
|
|
|
|
imagealphablending($resizedImage, false);
|
|
|
|
|
imagesavealpha($resizedImage, true);
|
|
|
|
|
$transparent = imagecolorallocatealpha($resizedImage, 255, 255, 255, 127);
|
|
|
|
|
imagefilledrectangle($resizedImage, 0, 0, $newWidth, $newHeight, $transparent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调整图片大小
|
|
|
|
|
imagecopyresampled($resizedImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
|
|
|
|
|
|
|
|
|
|
// 替换原始图片资源
|
|
|
|
|
imagedestroy($image);
|
|
|
|
|
$image = $resizedImage;
|
|
|
|
|
$width = $newWidth;
|
|
|
|
|
$height = $newHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成压缩后的WebP图片
|
|
|
|
|
$compressedPath = 'images/compressed/' . Str::random(40) . '.webp';
|
|
|
|
|
|
|
|
|
|
// 保存WebP图片
|
|
|
|
|
ob_start();
|
|
|
|
|
imagewebp($image, null, $quality);
|
|
|
|
|
$compressedContent = ob_get_clean();
|
|
|
|
|
|
|
|
|
|
Storage::put($compressedPath, $compressedContent);
|
|
|
|
|
|
|
|
|
|
// 释放内存
|
|
|
|
|
imagedestroy($image);
|
|
|
|
|
|
|
|
|
|
return $compressedPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 添加水印
|
|
|
|
|
*/
|
|
|
|
|
protected function addWatermark($imagePath)
|
|
|
|
|
{
|
|
|
|
|
$imageContent = Storage::get($imagePath);
|
|
|
|
|
$image = imagecreatefromstring($imageContent);
|
|
|
|
|
|
|
|
|
|
// 获取图片信息
|
|
|
|
|
$info = getimagesizefromstring($imageContent);
|
|
|
|
|
$width = $info[0];
|
|
|
|
|
$height = $info[1];
|
|
|
|
|
$mime = $info['mime'];
|
|
|
|
|
|
|
|
|
|
// 水印文字设置
|
|
|
|
|
$text = '@ Li Qi';
|
|
|
|
|
$fontSize = 20;
|
|
|
|
|
$fontFile = public_path('fonts/arial.ttf'); // 确保字体文件存在
|
|
|
|
|
// $fontFile = ''; // 确保字体文件存在
|
|
|
|
|
$textColor = imagecolorallocatealpha($image, 255, 255, 255, 50);
|
|
|
|
|
|
|
|
|
|
// 计算文字位置 (右下角)
|
|
|
|
|
$textBox = imagettfbbox($fontSize, 0, $fontFile, $text);
|
|
|
|
|
$textWidth = $textBox[2] - $textBox[0];
|
|
|
|
|
$textHeight = $textBox[7] - $textBox[1];
|
|
|
|
|
$x = $width - $textWidth - 40;
|
|
|
|
|
$y = $height - $textHeight - 40;
|
|
|
|
|
|
|
|
|
|
// 添加文字水印
|
|
|
|
|
imagettftext($image, $fontSize, 0, $x, $y, $textColor, $fontFile, $text);
|
|
|
|
|
|
|
|
|
|
// 生成带水印的路径
|
|
|
|
|
$extension = pathinfo($imagePath, PATHINFO_EXTENSION);
|
|
|
|
|
$watermarkedPath = 'images/watermarked/' . Str::random(40) . '.' . $extension;
|
|
|
|
|
|
|
|
|
|
// 保存带水印的图片
|
|
|
|
|
ob_start();
|
|
|
|
|
switch ($mime) {
|
|
|
|
|
case 'image/jpeg':
|
|
|
|
|
imagejpeg($image, null, 100);
|
|
|
|
|
break;
|
|
|
|
|
case 'image/png':
|
|
|
|
|
imagepng($image, null, 9);
|
|
|
|
|
break;
|
|
|
|
|
case 'image/gif':
|
|
|
|
|
imagegif($image);
|
|
|
|
|
break;
|
|
|
|
|
case 'image/webp':
|
|
|
|
|
imagewebp($image, null, 100);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
$watermarkedContent = ob_get_clean();
|
|
|
|
|
|
|
|
|
|
Storage::put($watermarkedPath, $watermarkedContent);
|
|
|
|
|
|
|
|
|
|
// 释放内存
|
|
|
|
|
imagedestroy($image);
|
|
|
|
|
|
|
|
|
|
return $watermarkedPath;
|
|
|
|
|
}
|
|
|
|
|
}
|