当前位置: 首页 > news >正文

用 Docker + Squoosh 打造图片压缩 API 服务

使用 Squoosh 的 Node.js 组件构建轻量图片压缩 API 服务,轻松集成到任意项目中,支持 JPG、PNG、WebP 等多种格式,压缩率高达 80%。

现在拍照设备拍的照片那是真的越来越大,网站如果涉及到很多图片,不去压缩的话存储空间和带宽是真吃不消。

于是就开始找解决办法,怎么使用程序压缩图片。试过很多方法,比如Pillow、OpenCV、Thumbnailator等,没有一款特别满意的,要么压缩后失真严重,要么压缩率不理想。

市面上在线的图片压缩工具压缩效果普遍不错,比如 TinyPNG、Squoosh、Optimizilla、Compress JPEG 等。我常用的是 TinyPNG,压缩率高、画质清晰,还提供 API 接口可以集成到自己的应用中。但问题是它收费太高,免费额度只有500张/月。

有一次无意间发现 Squoosh 有开源的 Node.js 组件,于是就动手写了一个 Node.js 服务,提供一个通用的图片压缩 API 给其他服务调用。

项目介绍

这个服务使用 Node.js 和 Squoosh CLI 构建,并通过 Docker 容器运行,方便快速部署到任意环境。

API 支持多种常见图片格式,包括JPG、PNG、WebP等。大部分图片压缩率能达到 80% 以上,而且几乎无损。

项目结构

├── Dockerfile
├── package.json
├── app.ts
└── src├── config.ts├── routers.ts└── imagecompress.ts

核心依赖

  • express- 提供 HTTP 服务
  • @squoosh/lib- 图片压缩核心组件
  • multer- 文件上传处理

Dockerfile 示例

FROM node:16.20.0-bullseye-slim# 执行命令,创建文件夹
RUN mkdir -p /home/compress# 设置工作目录
WORKDIR /home/compressCOPY . /home/compressRUN npm install && \npm run build && \rm -rf /home/compress/src && \rm -rf /home/compress/*.ts && \rm -rf /home/compress/*.json && \rm -rf /home/compress/Dockerfile# 配置环境变量
ENV HOST 0.0.0.0
ENV PORT 8866# 容器对外暴露的端口号
EXPOSE 8866#CMD ["npm", "run", "dev"]
CMD ["node", "/home/compress/dist/app.js"]

imagecompress.ts示例

import {  ImagePool, encoders, preprocessors }  from "@squoosh/lib";
import { cpus } from "os";
import { CompressOptions } from "@/types/config";const imagePool = new ImagePool(cpus().length);export async function compress(buffer:Buffer, ext: string, options: CompressOptions): Promise<Buffer> {let startTime = new Date().getTime();console.log("Start compressing pictures...", options);let image = imagePool.ingestImage(buffer);let preprocessOptions: any = {};let targetWidth = options.width && options.width > 0 ? options.width : undefined;let targetHeight = options.height && options.height > 0 ? options.height : undefined;if (targetWidth || targetHeight) {preprocessOptions.resize = {width: targetWidth,height: targetHeight}}let encodeOptions: any = {};await image.preprocess(preprocessOptions);await image.encode(encodeOptions);let targetFormat = options.target ? options.target.toLocaleLowerCase() : ext ? ext.replace(".", "").toLocaleLowerCase() : undefined;let result;if (targetFormat === "png") {encodeOptions.oxipng = {level: options.quality ? options.quality : 75};await image.encode(encodeOptions);result = await image.encodedWith.oxipng;} else if (targetFormat === "jxl") {encodeOptions.jxl = {quality: options.quality ? options.quality : 75};await image.encode(encodeOptions);result = await image.encodedWith.jxl;} else if (targetFormat === "avif") {await image.encode(encodeOptions);result = await image.encodedWith.avif;} else if (targetFormat === "webp") {encodeOptions.webp = {quality: options.quality ? options.quality : 75};await image.encode(encodeOptions);result = await image.encodedWith.webp;} else {encodeOptions.mozjpeg = {quality: options.quality ? options.quality : 75};await image.encode(encodeOptions);result = await image.encodedWith.mozjpeg;}if (!result) {return Buffer.alloc(0);}//const {extension, binary } = await image.encodedWith.mozjpeg;let array = new Uint8Array(result.binary.buffer);let buf: Buffer = Buffer.alloc(array.length);for (let nIndex = 0; nIndex < buf.length; nIndex++) {buf[nIndex] = array[nIndex];}console.log("Image compression completed! time consuming: " + (new Date().getTime() - startTime) + " ms.");console.log("Original image size: " + buffer.length + " Bytes, Compressed image size: " + buf.length + " Bytes, Compression ratio: " + Math.round(((buffer.length - buf.length) * 100 / buffer.length) * 100) / 100 + "%.");return buf;
}

routers.ts对外接口示例

router.post('/compress', upload.single('file'), function(req: Request, res: Response) {if (!req.file) {return res.status(400).send("No file uploaded!");} else if (req.file.size > 10485760) {return res.status(400).send("The maximum image size cannot exceed 10M!");}let options: CompressOptions = {quality: req.body.quality ? parseInt(req.body.quality) : undefined,width: req.body.width ? parseInt(req.body.width) : undefined,height: req.body.height ? parseInt(req.body.height) : undefined,target: req.body.target,extra: req.body.options};if (req.body.ct && req.body.ct === "attachment") {//download attachmentres.setHeader("Content-Type", "application/octet-stream; charset=utf-8");res.setHeader("Content-Disposition", "attachment; filename=" + encodeURIComponent(req.file.originalname));} else {//Picture Preview//res.setHeader("Content-Type", req.file?.mimetype);}compress(req.file.buffer, path.extname(req.file.originalname).toLocaleLowerCase(), options).then((buffer: Buffer) => {res.end(buffer);});
});

使用方式

  1. 构建镜像:

    docker build -t imagecompress .
  2. 启动服务:

    docker run -d -p 8866:8866 imagecompress
  3. 上传并压缩图片:

    curl --location 'http://localhost:8867/compress' \
    --form 'file=@"/C:/Users/luoxudong/Pictures/1.png"' \
    --form 'quality="75"' \
    --form 'width="1000"' \
    --form 'ct="attachment"' \
    --form 'target="jpg"'

参数说明:
file: (必传) 文件内容,文件的key值为”file”。
quality: (选填) 图片压缩质量,不传时默认为75。
width: (选填) 设置压缩后输出图片的宽度,高度不设置时按等比例压缩。
height: (选填) 设置压缩后输出图片的高度,宽度不设置时按等比例压缩。
ct: (选填) 设置输出图片的Content-Type,“attachment”为以附件方式输出,其他为以预览图片方式输出。
target: (选填) 压缩后的图片格式,默认跟附件后缀一致,支持的值:”jpg” | “png” | “webp” | “avif” | “jxl。

压缩效果

实际测试中,使用 mozjpeg 或 WebP 编码方式时,图片压缩率普遍在 70%-85% 之间,肉眼几乎看不出差别。docker镜像已发布到dockerhub上,欢迎大家体验!

总结

相比第三方收费服务,自建的 Docker + Node.js + Squoosh 压缩服务更灵活、更可控,能轻松集成到自己的网站、后台管理或内容处理系统中。

后续可以进一步优化支持异步任务、批量压缩、分布式处理等功能,让图片资源管理更加高效。

本文首发于 东哥小栈(EastStack) · 书写不止是记录,更是思考的延伸。

http://www.dtcms.com/a/481859.html

相关文章:

  • 仙桃网站设计公司易拉罐手工制作大全
  • 企业级DevOps选型新思维:从“工具堆砌”到“平台赋能”
  • ThinkPHP8集成RabbitMQ的完整案例实现 原创
  • 一份关于语言模型对齐的技术论述:从基于PPO的RLHF到直接偏好优化
  • 扬州市建设厅网站网站空间在哪里
  • 开源 C++ QT QML 开发(十九)多媒体--音频录制
  • json转excel python
  • 在传输数据时,网络中会出现的问题
  • jenkins在使用中遇到的问题
  • 第8章 zynq uboot更新系统镜像并引导启动和个人心得
  • 网站系统升级建设合同汽车之家官网首页网页
  • 电销外包公司有哪些seo学习网站
  • 基于弱监督病灶增强的模型展开式快速磁共振成像|文献速递-文献分享
  • 十四、OpenCV中的形态学操作
  • 算法279. 完全平方数
  • Prometheus pushgateway学习
  • MySQL索引结构:B树与B+树
  • 进程的基本认识
  • Webpack 打包优化与骨架屏结合:双管齐下提升前端性能与用户体验
  • 鸿蒙:在沙箱目录下压缩或解压文件
  • 智能SQL客户端Chat2DB技术解析
  • 电影网站推广什么是网络营销的主要职能之一
  • Transformers库用法示例:解锁预训练模型的强大能力
  • 大气污染扩散calpuff模型:数据预处理、Calmet气象模块、Post Tools 后处理工具及绘图工具
  • 用气安全与能效优化平台
  • 02117 信息组织【第三章】
  • 自己建设淘宝客网站需要备案么wordpress插件 投票
  • Wireshark 4.4.9 设置为中文界面方法
  • 极限AI Coding,腾讯云“黑客松”大赛回顾(内有作品开源)
  • 【工具分享】Dota游戏平台助手