153 lines
4.9 KiB
PHP
153 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use App\Service\ImageService;
|
|
|
|
class OptimizeImageCompression extends Command
|
|
{
|
|
protected $signature = 'images:optimize-compression
|
|
{--max-size=1024 : 目标最大文件大小 (KB)}
|
|
{--quality=85 : 初始压缩质量}
|
|
{--min-quality=40 : 最低压缩质量}
|
|
{--max-iterations=10 : 最大压缩次数}';
|
|
|
|
protected $description = '优化压缩目录下的图片,压缩到指定大小以下';
|
|
|
|
public function handle()
|
|
{
|
|
// 不限制内存
|
|
ini_set('memory_limit', -1);
|
|
$disk = Storage::disk('public');
|
|
$directory = 'images/compressed';
|
|
$maxSizeKB = (int)$this->option('max-size');
|
|
$maxSizeBytes = $maxSizeKB * 1024;
|
|
|
|
$this->info("正在优化 {$directory} 目录下的图片...");
|
|
$this->info("目标文件大小: {$maxSizeKB}KB");
|
|
|
|
$images = $disk->allFiles($directory);
|
|
$total = count($images);
|
|
$count = 0;
|
|
|
|
$progressBar = $this->output->createProgressBar($total);
|
|
$progressBar->start();
|
|
|
|
foreach ($images as $imagePath) {
|
|
$originalSize = $disk->size($imagePath);
|
|
$fileExtension = pathinfo($imagePath, PATHINFO_EXTENSION);
|
|
|
|
// 只处理WEBP格式文件
|
|
if (strtolower($fileExtension) !== 'webp') {
|
|
$this->warn("跳过非WEBP文件: $imagePath");
|
|
$progressBar->advance();
|
|
continue;
|
|
}
|
|
|
|
// 如果已经小于目标大小,则跳过
|
|
if ($originalSize <= $maxSizeBytes) {
|
|
$progressBar->advance();
|
|
continue;
|
|
}
|
|
|
|
// 获取文件的绝对路径
|
|
$filePath = $disk->path($imagePath);
|
|
|
|
// 创建压缩实例
|
|
$imageService = app(ImageService::class);
|
|
$tempPath = storage_path('app/temp/'.basename($imagePath));
|
|
|
|
// 创建目标目录
|
|
if (!file_exists(dirname($tempPath))) {
|
|
mkdir(dirname($tempPath), 0755, true);
|
|
}
|
|
|
|
// 多轮压缩策略
|
|
$currentSize = $originalSize;
|
|
$iterations = 0;
|
|
$currentQuality = (int)$this->option('quality');
|
|
$minQuality = (int)$this->option('min-quality');
|
|
$maxIterations = (int)$this->option('max-iterations');
|
|
|
|
while ($currentSize > $maxSizeBytes && $iterations < $maxIterations && $currentQuality >= $minQuality) {
|
|
// 使用服务类进行压缩
|
|
$optimizedContent = $this->recompressImage(
|
|
$filePath,
|
|
$currentQuality
|
|
);
|
|
|
|
file_put_contents($tempPath, $optimizedContent);
|
|
$newSize = filesize($tempPath);
|
|
|
|
// 如果新文件大小没有减小,则停止优化
|
|
if ($newSize >= $currentSize) {
|
|
$this->warn("无法进一步压缩: $imagePath (质量: $currentQuality)");
|
|
break;
|
|
}
|
|
|
|
$currentSize = $newSize;
|
|
$iterations++;
|
|
|
|
// 降低质量进行下一轮尝试
|
|
if ($currentSize > $maxSizeBytes) {
|
|
$currentQuality -= 5;
|
|
}
|
|
}
|
|
|
|
// 替换原始文件
|
|
if ($currentSize < $originalSize) {
|
|
rename($tempPath, $filePath);
|
|
$this->info("优化成功: $imagePath 从 {$this->formatBytes($originalSize)} 压缩到 {$this->formatBytes($currentSize)}");
|
|
} else {
|
|
@unlink($tempPath);
|
|
$this->warn("压缩失败: $imagePath 无法达到目标大小");
|
|
}
|
|
|
|
$progressBar->advance();
|
|
$count++;
|
|
}
|
|
|
|
$progressBar->finish();
|
|
$this->info("\n{$count}/{$total} 个图片已优化完成");
|
|
}
|
|
|
|
/**
|
|
* 重新压缩图片
|
|
*/
|
|
protected function recompressImage($filePath, $quality)
|
|
{
|
|
// 读取原始图片
|
|
$image = imagecreatefromwebp($filePath);
|
|
|
|
// 捕捉错误
|
|
if (!$image) {
|
|
throw new \Exception("无法读取图片: $filePath");
|
|
}
|
|
|
|
// 捕捉输出
|
|
ob_start();
|
|
$success = imagewebp($image, null, $quality);
|
|
|
|
if (!$success) {
|
|
throw new \Exception("压缩失败: $filePath");
|
|
}
|
|
|
|
$compressedContent = ob_get_clean();
|
|
imagedestroy($image);
|
|
|
|
return $compressedContent;
|
|
}
|
|
|
|
/**
|
|
* 格式化字节大小
|
|
*/
|
|
protected function formatBytes($bytes)
|
|
{
|
|
if ($bytes < 1024) return $bytes . 'B';
|
|
if ($bytes < 1048576) return round($bytes / 1024, 1) . 'KB';
|
|
return round($bytes / 1048576, 1) . 'MB';
|
|
}
|
|
}
|