From effdfd7f732b75ec761d2e511462e5bf5d5e20e5 Mon Sep 17 00:00:00 2001 From: lirtual Date: Sat, 21 Dec 2024 21:32:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BE=E7=89=87=E5=8E=8B=E7=BC=A9?= =?UTF-8?q?=E7=AE=97=E6=B3=95=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/optimize-images.js | 128 +++++++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 12 deletions(-) diff --git a/scripts/optimize-images.js b/scripts/optimize-images.js index 18225cabd32..9bb405e12ce 100644 --- a/scripts/optimize-images.js +++ b/scripts/optimize-images.js @@ -147,37 +147,143 @@ if (!isMainThread) { const sharpOptions = { failOnError: false, limitInputPixels: false, - sequentialRead: !isRamDisk, // 如果不是RAM disk,使用顺序读取 + sequentialRead: !isRamDisk, }; let sharpInstance = sharp(inputPath, sharpOptions); + // 获取图像信息 + const metadata = await sharpInstance.metadata(); + const isTransparent = metadata.hasAlpha; + const { width, height } = metadata; + const isLargeImage = width > 1000 || height > 1000; + + // 智能压缩策略 if (isSmallFile) { + // 小文件使用相对保守的压缩 await sharpInstance .png({ compressionLevel: 9, effort: 10, palette: true, - colors: 128, - dither: 0.4 + colors: 256, + quality: 90, + dither: 0.6 }) .toFile(tempPath); + } else if (isTransparent) { + // 包含透明通道的图片 + const optimizedPng = sharpInstance.clone() + .png({ + quality: 75, + compressionLevel: 9, + effort: 10, + palette: true, + colors: isLargeImage ? 196 : 256, + dither: 0.8 + }); + + // 对于透明图片也尝试使用带Alpha通道的WebP + const webpVersion = sharpInstance.clone() + .webp({ + quality: 80, + alphaQuality: 85, + effort: 6, + lossless: false, + nearLossless: false, + smartSubsample: true, + reductionEffort: 6 + }); + + const [pngBuffer, webpBuffer] = await Promise.all([ + optimizedPng.toBuffer(), + webpVersion.toBuffer() + ]); + + // 选择更小的格式 + if (webpBuffer.length < pngBuffer.length && webpBuffer.length < inputStats.size) { + await fs.writeFile(tempPath, webpBuffer); + } else { + await fs.writeFile(tempPath, pngBuffer); + } } else { - await sharpInstance + // 不透明图片,使用更激进的压缩 + const optimizedPng = sharpInstance.clone() .png({ quality: 70, compressionLevel: 9, + effort: 10, palette: true, - colors: 128, - dither: 0.4, - effort: 10 - }) - .toFile(tempPath); + colors: isLargeImage ? 128 : 196, + dither: 0.6 + }); + + // 对于不透明图片尝试多种格式 + const webpVersion = sharpInstance.clone() + .webp({ + quality: 75, + effort: 6, + lossless: false, + nearLossless: false, + smartSubsample: true, + reductionEffort: 6 + }); + + // 对于照片类型的图片,也尝试JPEG格式 + const jpegVersion = isLargeImage ? + sharpInstance.clone() + .jpeg({ + quality: 82, + progressive: true, + mozjpeg: true, + chromaSubsampling: '4:2:0' + }) : null; + + const bufferPromises = [ + optimizedPng.toBuffer(), + webpVersion.toBuffer() + ]; + + if (jpegVersion) { + bufferPromises.push(jpegVersion.toBuffer()); + } + + const buffers = await Promise.all(bufferPromises); + const [pngBuffer, webpBuffer, jpegBuffer] = buffers; + + // 选择最小的格式 + let smallestBuffer = pngBuffer; + let smallestSize = pngBuffer.length; + + if (webpBuffer.length < smallestSize) { + smallestBuffer = webpBuffer; + smallestSize = webpBuffer.length; + } + + if (jpegBuffer && jpegBuffer.length < smallestSize) { + smallestBuffer = jpegBuffer; + smallestSize = jpegBuffer.length; + } + + if (smallestSize < inputStats.size) { + await fs.writeFile(tempPath, smallestBuffer); + } else { + // 如果所有格式都没有达到更好的压缩效果,尝试最后的优化 + await sharpInstance + .png({ + quality: 65, + compressionLevel: 9, + effort: 10, + palette: true, + colors: 128, + dither: 0.5 + }) + .toFile(tempPath); + } } const outputStats = await fs.stat(tempPath); if (outputStats.size < inputStats.size) { - // 如果优化后的文件更小,则替换原文件 await fs.rename(tempPath, inputPath); return { success: true, @@ -186,7 +292,6 @@ if (!isMainThread) { path: relativePath }; } else { - // 如果优化后的文件更大,则删除临时文件 await fs.unlink(tempPath); return { success: true, @@ -197,7 +302,6 @@ if (!isMainThread) { }; } } catch (error) { - // 清理临时文件 try { await fs.unlink(tempPath); } catch {}