纯前端实现 PNG 图片压缩 | UPNG.js

在线 Demo 体验地址 →: https://demos.sugarat.top/pages/png-compress/

前言

最近在迭代自己的 图床 应用,由于使用时间的累计,存储空间占用越来越大了,在做 Web 应用的时候会随手拿 tinypng 压缩一下图片。

想着给咱图床也加个压缩的功能,这样上传/访问也能省点 💰。

图片类型众多,常用的主要就是PNG/JPG/GIF

个人使用频率最高的场景是截图上传,格式为PNG,就先拿 PNG 试手。调研了一圈开源里最流行的就是使用 UPNG.js 进行 PNG 的压缩。

如何判断图片是 PNG

第一步当然是判断图片类型,不然 UPNG.js 就不能正常工作咯,通过文件后缀 .png 判断肯定是不靠谱的。

搜索了解了一下,可以使用 魔数 判断:一个PNG文件的前8个字节是固定的

PNG 的前 8 个字节是(16进制表示):89 50 4E 47 0D 0A 1A 0A

我们可以拿工具看一下,我这里用 VS Code 插件 Hex Editor 查看一个 PNG 图片的 16 进制表示信息。

可以看到前八个字节和上面表示的一样。

于是可以根据这个特性判断,于是就有如下的判断代码。

ts 复制代码
async function isPNG(file: File) {
  // 提取前8个字节
  const arraybuffer = await file.slice(0, 8).arrayBuffer()

  // PNG 的前8字节16进制表示
  const signature = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
  // const signature = [137, 80, 78, 71, 13, 10, 26, 10]

  // 转为 8位无符号整数数组 方便对比
  // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
  const source = new Uint8Array(arraybuffer)

  // 逐个字节对比
  for (let i = 0; i < signature.length; i++) {
    if (source[i] !== signature[i]) {
      return false
    }
  }
  return true
}

UPNG.js

简介

一个轻量且极速的 PNG/APNG 编码和解码库,Photopea 图像编辑器的主要 PNG 引擎。

npm 加载

官方提供了 npm 包,简单引入即可使用。

安装依赖

sh 复制代码
npm install upng-js

核心方法就 3 个,依次调用即可

  • UPNG.decode(buffer)
  • UPNG.toRGBA8(img)
  • UPNG.encode(imgs, w, h, cnum, [dels])
    • cnum:0 表示无损压缩,256表示有损,可以调整这个值来控制压缩质量。

注意:压缩并不意味着一定小,对于一些已经很简单且小的图片,压缩后可能反而更大。

下面是这个方法的最简实现。

ts 复制代码
import UPNG from 'upng-js'

async function compressPNG(file: File) {
  const arrayBuffer = await file.arrayBuffer()
  const decoded = UPNG.decode(arrayBuffer)
  const rgba8 = UPNG.toRGBA8(decoded)

  // 关键的压缩方法
  // 这里 保持宽高不变,保持80%的质量(接近于 tinypng 的压缩效果)
  const compressed = UPNG.encode(
    rgba8,
    decoded.width,
    decoded.height,
    256 * 0.8
  )
  return new File([compressed], file.name, { type: 'image/png' })
}

其中压缩后的宽高,压缩质量都是可以调整的。

可配置封装

下面方法(TS 实现),提供了一些常用的配置选项。

ts 复制代码
import UPNG from 'upng-js'

interface CompressOptions {
  /**
   * 压缩质量([0,1])
   * @default 0.8
   */
  quality?: number
  /**
   * 压缩后更大是否使用原图
   * @default true
   */
  noCompressIfLarger?: boolean
  /**
   * 压缩后的新宽度
   * @default 原尺寸
   */
  width?: number
  /**
   * 压缩后新高度
   * @default 原尺寸
   */
  height?: number
}
async function compressPNGImage(file: File, ops: CompressOptions = {}) {
  const { width, height, quality = 0.8, noCompressIfLarger = true } = ops

  const arrayBuffer = await file.arrayBuffer()
  const decoded = UPNG.decode(arrayBuffer)
  const rgba8 = UPNG.toRGBA8(decoded)

  const compressed = UPNG.encode(
    rgba8,
    width || decoded.width,
    height || decoded.height,
    256 * quality
  )

  const newFile = new File([compressed], file.name, { type: 'image/png' })

  if (!noCompressIfLarger) {
    return newFile
  }

  return file.size > newFile.size ? newFile : file
}

CDN 加载

不通过 npm 安装,也可以使用 <script> 标签的方式进行全局引入。

可以使用Static file提供的 CDN 资源。

只需在 HTML 模板顶部 head 中加入如下资源即可使用。

html 复制代码
<head>
  <script src="https://cdn.staticfile.net/pako/1.0.5/pako.min.js"></script>
  <script src="https://cdn.staticfile.net/upng-js/2.1.0/UPNG.min.js"></script>
</head>

PNG 格式化使用 Inflate 算法。这部分调用 Pako.js 实现,所以需要额外前置引入。

引入后,将在 window 上绑定 UPNG 变量,使用和上述 npm 给到的例子完全一致。

代码里调用方式如下

js 复制代码
window.UPNG.encode

// 省略 window
UPNG.encode

完整 demo

笔者将本节内容整理成了一个 Demo,可以直接在线体验。

在线 Demo 体验地址 →: https://demos.sugarat.top/pages/png-compress/

大概界面如下:

纯血 HTML/CSS/JS,复制粘贴就能运行。

完整源码见:GitHub:ATQQ/demos - png-compress

最后

后续将继续学习&探索一下其它格式的纯前端压缩实现(JPG,GIF,MP4转GIF)。

相关推荐
guojikun17 小时前
一键配置 Web 前端开发环境(PowerShell 自动化脚本)
windows·web前端·powershell
程序设计实验室2 个月前
在Next.js中集成swagger文档
web前端·next.js
丁同亚的博客2 个月前
echarts大屏项目指南
echarts·可视化·js·web前端·大屏
程序设计实验室2 个月前
主流 nodejs 包管理器 pnpm vs bun vs npm vs yarn 简单横评
web前端
程序设计实验室4 个月前
视频中台解决方案:组织树组件+多路视频直播界面开发
web前端·项目完成小结
Ashley的成长之路4 个月前
对接C端、B端和G端项目的注意事项及坑点总结
经验总结·b端·多端项目·c端·g端·坑点总结
牧码岛5 个月前
Web前端之隐藏元素方式的区别、Vue循环标签的时候在同一标签上隐藏元素的解决办法、hidden、display、visibility
前端·css·vue·html·web·web前端
牧码岛6 个月前
Web前端之Vue+Element实现表格动态不同列合并多行、localeCompare、forEach、table、push、sort、Map
前端·javascript·elementui·vue·web·web前端
little_kid_pea9 个月前
网站上的图片无法使用右键“图片另存为”
web前端·图片下载
程序设计实验室10 个月前
StarBlog博客Vue前端开发笔记:(4)使用FontAwesome图标库
web前端·starblog-vue