pokerogue/scripts/optimize-images.js

133 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sharp from 'sharp';
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { glob } from 'glob';
import os from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const sourceDir = 'public/images';
const outputDir = 'dist/images';
// 统计数据
let totalFiles = 0;
let totalOriginalSize = 0;
let totalOptimizedSize = 0;
let successCount = 0;
let failCount = 0;
let startTime = Date.now();
// 文件大小阈值(字节)
const SMALL_FILE_THRESHOLD = 5 * 1024; // 5KB
async function optimizeImage(inputPath) {
const relativePath = path.relative(sourceDir, inputPath);
const outputPath = path.join(outputDir, relativePath);
const outputDirPath = path.dirname(outputPath);
await fs.mkdir(outputDirPath, { recursive: true });
try {
const inputStats = await fs.stat(inputPath);
const isSmallFile = inputStats.size < SMALL_FILE_THRESHOLD;
// 根据文件大小选择不同的压缩策略
let sharpInstance = sharp(inputPath);
if (isSmallFile) {
// 小文件使用无损压缩策略
await sharpInstance
.png({
compressionLevel: 9,
effort: 10,
palette: false // 不使用调色板模式
})
.toFile(outputPath);
} else {
// 大文件使用有损压缩策略
await sharpInstance
.png({
quality: 80,
compressionLevel: 9,
palette: true,
colors: 256,
dither: 0.5,
effort: 10
})
.toFile(outputPath);
}
const outputStats = await fs.stat(outputPath);
const savings = ((inputStats.size - outputStats.size) / inputStats.size * 100).toFixed(2);
// 如果优化后文件更大,使用原始文件
if (outputStats.size > inputStats.size) {
await fs.copyFile(inputPath, outputPath);
console.log(`⚠️ ${relativePath} (使用原始文件)`);
} else {
console.log(`${relativePath}`);
}
// 更新统计数据
totalOriginalSize += inputStats.size;
totalOptimizedSize += Math.min(inputStats.size, outputStats.size);
successCount++;
console.log(` 原始大小: ${(inputStats.size / 1024).toFixed(2)}KB`);
console.log(` 优化大小: ${(Math.min(inputStats.size, outputStats.size) / 1024).toFixed(2)}KB`);
console.log(` 节省: ${((inputStats.size - Math.min(inputStats.size, outputStats.size)) / inputStats.size * 100).toFixed(2)}%\n`);
} catch (error) {
console.error(`✗ 处理 ${relativePath} 时出错:`, error);
failCount++;
}
}
function printReport() {
const endTime = Date.now();
const duration = ((endTime - startTime) / 1000).toFixed(2);
const totalSavings = ((totalOriginalSize - totalOptimizedSize) / totalOriginalSize * 100).toFixed(2);
console.log('\n========== 优化结果报告 ==========');
console.log(`处理总文件数: ${totalFiles}`);
console.log(`成功处理: ${successCount} 个文件`);
console.log(`处理失败: ${failCount} 个文件`);
console.log(`原始总大小: ${(totalOriginalSize / 1024 / 1024).toFixed(2)}MB`);
console.log(`优化后总大小: ${(totalOptimizedSize / 1024 / 1024).toFixed(2)}MB`);
console.log(`总体积减少: ${totalSavings}%`);
console.log(`处理耗时: ${duration}`);
console.log(`CPU核心数: ${os.cpus().length}`);
console.log('================================\n');
}
async function processImages() {
try {
// 清空输出目录
await fs.rm(outputDir, { recursive: true, force: true });
await fs.mkdir(outputDir, { recursive: true });
// 获取所有PNG文件
const files = await glob(path.join(sourceDir, '**/*.png'));
totalFiles = files.length;
console.log(`找到 ${files.length} 个PNG文件需要优化\n`);
// 使用工作池并行处理图片限制并发数为CPU核心数
const concurrency = os.cpus().length;
const chunks = [];
for (let i = 0; i < files.length; i += concurrency) {
chunks.push(files.slice(i, i + concurrency));
}
for (const chunk of chunks) {
await Promise.all(chunk.map(file => optimizeImage(file)));
}
printReport();
} catch (error) {
console.error('处理过程中发生错误:', error);
}
}
processImages();