引言
图片压缩在项目中是一个常见的需求, 通过减小图片文件的大小, 降低存储和传输成本, 提高网页加载速度, 以及减少带宽占用!
个人站点 《仿 Mac 个人网站开发 |项目复盘》目前是使用 TinyPNG 提供的 API
来压缩图片的, 实际上目前系统就我一个人在使用, 所以 TinyPNG 一个月 500次
的免费额度还完全够用!!! 唯一的遗憾就是 TinyPNG 不支持 GIF
图片压缩, 但是考虑到大部分 GIF
录制软件导出的图片都是可以通过设置参数, 达到压缩的效果, 所以也就这样先用着咯....
直到有幸看到 神光大大 的文章 《Nest + Sharp 实现了一个 gif 压缩工具, 帮我省不少钱》, 原来 Node
完全可以使用 Sharp 来完成图片的压缩、包括 GIF
, 所以就生出了改造项目的想法, 而本文就是整个改造过程的一个简单记录....
一、现有方案「TinyPNG」介绍
正如引言所述, 目前个人站点压缩图片用的是 TinyPNG 所以在开始前, 容我简单介绍下它!!!
1.1 是什么?
正如官网所说, TinyPNG
是一个强大的图片压缩工具, 它使用智能有损压缩技术可以将我们的的 WebP
、PNG
、JPEG
等图片的文件大小进行一个压缩, 它可以通过选择性的减少图片中的颜色, 使得只需要很少的字节数就能保存数据, 在视觉上压缩的图片前后影响几乎不可见, 但是在文件大小上却有着显著效果!!!
同时 TinyPNG 官方客户端代码库支持各种语言, 通过官方提供的 SDK
我们可以轻松调用它们的 API
, 实现图片压缩功能!!!
当然天下没有免费的午餐, TinyPNG API 每月是会有 500次
的免费额度, 超过部分则需要收费咯!!! 当然个人使用的话 500次
基本就够用了!!
1.2 怎么用?
WEB
版: 直接进入 TinyPNG 首页, 如图, 将图片拖拽到指定区域或者直接点击(会弹出文件选择窗)选择图片, 选择完图片将自动压缩
压缩完成会有下载入口, 直接下载即可
Node
环境使用: 首先需要申请一个API
密钥, 这里需要先登录(填写邮件, 邮箱会有个登录按钮, 点击就可以登录); 登录后进入「开发者 API」页面, 然后填写信息获取API
密钥
这里也会发一个邮箱, 点击邮箱邮件中按钮会来到 API
管理页面, 这里我们就可以看到一个 API
密钥了
有了 API
密钥, 在 Node
中我们就可以使用 tinify 来完成图片的压缩了
js
import tinify from 'tinify'
// 1. 设置 api key
tinify.key = "YOUR_API_KEY";
// 2. 方法一: 输入为 buffer, 输出为 buffer
tinify.fromBuffer(buffer).toBuffer((err, resultData) => {
error = err;
resolve(err ? buffer : resultData);
});
// 3. 方法一: 输入为「图片路径」, 输出为「图片路径」
tinify.fromFile("unoptimized.png").toFile("optimized.png");
1.3 为什么要放弃?
- 每个月只有
500次
的免费额度, 虽然目前够用, 但未来就不一定咯(毕竟砸们也是有很大愿景的) - 无法压缩
GIF
- 既然
Node
可以自己做, 又何必受限于 TinyPNG
二、新方案「Sharp」
新方案就是使用 Sharp 来实现, 有了它我们就可以在 Node
中实现图片的压缩, 可以将常见格式的大图转换为较小的、网络友好的不同尺寸的 JPEG
、PNG
、WebP
、GIF
和 AVIF
图像, 同时除了压缩图片, 它还提供了很多强大的图片处理能力....
Sharp 使用起来也炒鸡简单, 如下所示, 只需要一行代码即可完成图片的压缩, 其中 quality
是压缩图片常见的一个参数, 它指的是图片压缩后的 质量
, 该值可选范围为 1 ~ 100
, 数值越高图片质量越好相应的压缩比例也就越低, 该值通常建议为 75-80
, 不建议小于 60
js
import sharp from 'sharp'
sharp('1.png').png({ quality: 75 }).toFile('2.png')
来测试下效果: 随便找了个图, 测试了下, 直接将从 112KB
压缩到了 31KB
, 这压缩率还是很给力的!!!!
特别的是 gif
、raw
、tile
是不能调整 quality
参数的, 所以如果想要对 GIF
进行压缩就得另外处理了, 如下代码所示:
animated
: 设置为true
可读取动画图像(GIF、WebP、TIFF)的所有帧, 否则默认只会读取第一帧limitInputPixels
: 默认情况下 Sharp 能处理的像素数是有限制的(268402689
), 所以图片如果太大将会报错, 如果要解除该限制就可以将该参数设置为false
colours
: 设置调色板(基础颜色)的最大数量, 它的值介于2
到256
之间, 数值越小那么绘制出来的gif
画质越差, 但是体积相对也越小!! 所以这里得自己多做些尝试, 找到一个合适的数值!!
js
sharp('ScreenFlow.gif', {
animated: true,
limitInputPixels: false
}).gif({
colours: 2,
}).toFile('2.image.gif')
如下图, 是在 colours
设置为 2
情况下的对比图, 可以发现画质严重损坏了, 但是文件体积直接从 36.3MB
-> 2.1MB
这里我们还是那句话需要耐心得多做些测试, 找到一个合适的数值, 下面是 128
的一个情况, 只要不放大看还算能接受, 体积也是从 36.3MB
-> 3.3MB
三、项目改造
- 先安装
sharp
依赖
sh
npm i sharp
- 下面我们使用
sharp
来封装一个通用的方法, 由于gif
、raw
、tile
是不能调整quality
参数, 并且针对gif
我们还需要特殊处理, 所以就需要对图片进行判断处理, 具体实现如下:
- 函数的入参和出参都是文件流(
stream
)数据, 所以这里针对stream
和buffer
做了些转换工作 switch
根据不同文件格式
设置不同的options
、formatOptions
await sharp(imagesBuffer).metadata()
使用sharp
获取文件的meta
数据
js
import sharp from 'sharp';
import { streamToBuffer, bufferToStream } from '#utils/fs';
/**
* 压缩图片
*
* @param {stream} stream 要压缩图片流
* @returns {stream} 返回的是压缩后的流
*/
export default async (stream) => {
const imagesBuffer = await streamToBuffer(stream);
const metadata = await sharp(imagesBuffer).metadata();
let options = {}; // sharp 配置
let formatOptions = {}; // 不同格式方法参数
// 根据文件格式, 设置不同的配置
switch (metadata.format) {
case 'gif':
options = {
animated: true,
limitInputPixels: false,
};
formatOptions = { colours: 128 };
break;
case 'raw':
case 'tile':
break;
default:
formatOptions = { quality: 75 };
}
// 压缩: 调用 sharp
const newBuffer = await sharp(imagesBuffer, options)
?.[metadata.format](formatOptions)
.toBuffer();
// 返回文件流
return bufferToStream(newBuffer);
};
最后补充两个 stream
和 buffer
相互转换的方法
js
// #utils/fs
// Stream 转 Buffer
export const streamToBuffer = (stream) => new Promise((resolve, reject) => {
const buffers = [];
stream.on('error', reject);
stream.on('data', (data) => buffers.push(data));
stream.on('end', () => resolve(Buffer.concat(buffers)));
});
// Buffer 转 Stream
export const bufferToStream = (buffer) => {
const stream = new Duplex();
stream.push(buffer);
stream.push(null);
return stream;
};
- 压缩效果: 最后看下
png
和gif
的一个压缩效果吧, 压缩率还是很给力, 图片质量还是ok
的
- 最后卸载
tinify
, 清理相关代码
sh
npm uninstall tinify