UPNG.js 详细介绍:高性能 PNG / APNG 编解码库/无损/有损压缩完全指南
UPNG.js 是一款轻量级、纯 JavaScript 实现的 PNG/APNG 编码与解码库,作为专业在线图像编辑器 Photopea 的核心 PNG 引擎,它在实际生产环境中经过了大量验证。该库不依赖任何第三方依赖,可同时运行在浏览器端和 Node.js 环境中,支持无损压缩和有损压缩两种模式,能够处理包括动画 APNG、调色板图像、16 位深度图像在内的多种复杂 PNG 格式。
该项目由 Photopea 团队维护,源码体积小巧,压缩后仅约 20KB,却提供了完整的 PNG 规范实现。与其他同类库相比,UPNG.js 在压缩率和编解码速度之间取得了较好的平衡,尤其在 PNG 有损压缩方面表现突出,能够在视觉质量可接受的前提下,显著减小文件体积。
核心特性
UPNG.js 提供了丰富的功能特性,覆盖了 PNG 图像处理的主要场景:
- 完整的 PNG 解码支持 :支持所有标准 PNG 颜色类型,包括灰度、RGB、索引色、灰度+Alpha、RGBA,位深度从 1 位到 16 位均可正确解析。
- PNG 编码能力 :提供高级编码接口
UPNG.encode()和低级编码接口UPNG.encodeLL(),前者适用于常规场景,后者允许精细控制通道数和位深度等参数。 - APNG 动画支持 :支持解码和编码 APNG 动画图像,可读取帧延迟、帧偏移等元数据,也可将多帧像素数据编码为完整的 APNG 文件。
- 无损与有损压缩 :通过
cnum参数控制颜色数量,设置为 0 时启用无损压缩,设置为具体数值时启用有损压缩,数值越小文件体积越小。 - 零依赖 :整个库不依赖任何外部库,可直接引入使用,减少了项目依赖体积和潜在的兼容性问题。
- 跨环境兼容 :同时支持浏览器环境和 Node.js 环境,在浏览器中可直接通过
<script>标签引入,在 Node.js 中可通过 npm 安装使用。
安装与引入
npm 安装
对于使用模块化开发的项目,可以通过 npm 安装 UPNG.js:
npm install upng-js安装完成后,使用 ES Module 或 CommonJS 方式引入:
// ES Module
import UPNG from 'upng-js';
// CommonJS
const UPNG = require('upng-js');浏览器直接引入
对于传统的前端项目,可以直接下载 UPNG.js 源码文件,通过 <script> 标签引入到页面中:
<script src="UPNG.js"></script>引入后,全局对象 UPNG 即可在脚本中直接使用。
核心 API 详解
UPNG.decode(buffer)
decode 方法用于解码 PNG 或 APNG 文件,接收一个包含 PNG 数据的 ArrayBuffer 作为参数,返回一个图像对象,包含以下属性:
width:图像宽度,单位为像素height:图像高度,单位为像素depth:位深度,常见值为 8 或 16ctype:颜色类型,1 表示灰度,2 表示 RGB,3 表示索引色,4 表示灰度+Alpha,6 表示 RGBAframes:帧数据数组,单帧 PNG 只有一个元素,APNG 包含多个帧tabs:PNG 文件中的辅助数据块,如 pHYs、iCCP 等
UPNG.toRGBA8(img)
toRGBA8 方法将解码后的图像对象转换为 RGBA8 格式的像素数据数组。每个元素是一个 ArrayBuffer ,包含对应帧的像素数据,每个像素占 4 个字节,依次为 R、G、B、A 四个通道,每个通道 8 位。
对于单帧 PNG,返回的数组只有一个元素;对于 APNG 动画,返回的数组长度等于帧数。该方法是连接解码和编码的桥梁,通常在解码后调用,将像素数据转换为统一的 RGBA8 格式,便于后续处理或重新编码。
UPNG.encode(imgs, w, h, cnum, [dels])
encode 方法是最常用的编码接口,用于将 RGBA8 格式的像素数据编码为 PNG 或 APNG 文件,返回一个 ArrayBuffer 。参数说明如下:
imgs:像素数据数组,每个元素是一个ArrayBuffer,包含一帧的 RGBA8 像素数据。单帧 PNG 传入单元素数组,APNG 传入多元素数组。w:图像宽度,单位为像素h:图像高度,单位为像素cnum:颜色数量,控制压缩质量。设置为 0 时启用无损压缩,保留所有颜色;设置为具体数值时启用有损压缩,数值越小颜色越少,文件体积越小,画质损失越大。常用值为 256,对应 8 位索引色。dels:可选参数,帧延迟数组,单位为毫秒,仅在编码 APNG 时使用。数组长度应与帧数相同,每个元素对应一帧的显示时长。
UPNG.encodeLL(imgs, w, h, cc, ac, depth, [dels])
encodeLL 是低级编码接口,提供了更精细的控制能力,适用于需要指定位深度、通道数等专业场景。参数说明如下:
imgs:像素数据数组,与encode方法相同w:图像宽度h:图像高度cc:颜色通道数,1 表示灰度,3 表示 RGBac:Alpha 通道数,0 表示无 Alpha,1 表示有 Alphadepth:位深度,可设置为 1、2、4、8、16dels:可选参数,帧延迟数组,与encode方法相同
通过组合 cc 、 ac 和 depth 参数,可以生成不同颜色类型和位深度的 PNG 文件,例如灰度 8 位、RGB 16 位、RGBA 8 位等。
实战示例代码
PNG 解码并在 Canvas 上显示
以下示例演示如何使用 UPNG.js 解码一个 PNG 文件,并将其绘制到 Canvas 元素上:
// 获取 Canvas 上下文
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 加载并解码 PNG 文件
fetch('example.png')
.then(response => response.arrayBuffer())
.then(buffer => {
// 解码 PNG 数据
const img = UPNG.decode(buffer);
// 转换为 RGBA8 格式
const rgba8 = UPNG.toRGBA8(img);
// 设置 Canvas 尺寸
canvas.width = img.width;
canvas.height = img.height;
// 创建 ImageData 并填充像素数据
const imageData = ctx.createImageData(img.width, img.height);
const data = new Uint8Array(rgba8[0]);
imageData.data.set(data);
// 绘制到 Canvas
ctx.putImageData(imageData, 0, 0);
});PNG 无损压缩
以下示例演示如何对 Canvas 中的图像进行无损压缩,生成 PNG 文件并下载:
function compressPNGLossless(canvas) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 获取像素数据的 ArrayBuffer
const pixelBuffer = imageData.data.buffer;
// 使用无损模式编码(cnum = 0)
const pngBuffer = UPNG.encode(
[pixelBuffer],
canvas.width,
canvas.height,
0
);
// 创建 Blob 并生成下载链接
const blob = new Blob([pngBuffer], { type: 'image/png' });
const url = URL.createObjectURL(blob);
// 触发下载
const link = document.createElement('a');
link.href = url;
link.download = 'compressed.png';
link.click();
// 释放 URL 对象
URL.revokeObjectURL(url);
}PNG 有损压缩
以下示例演示如何对用户上传的 PNG 图片进行有损压缩,并显示压缩前后的大小对比:
async function compressPNGLossy(file, quality = 0.8) {
// 读取文件为 ArrayBuffer
const arrayBuffer = await file.arrayBuffer();
// 解码原始 PNG
const decoded = UPNG.decode(arrayBuffer);
const rgba8 = UPNG.toRGBA8(decoded);
// 计算颜色数量,quality 取值 0-1,值越小压缩越强
const cnum = Math.floor(256 * quality);
// 使用有损模式编码
const compressed = UPNG.encode(
rgba8,
decoded.width,
decoded.height,
cnum
);
// 计算压缩率
const originalSize = arrayBuffer.byteLength;
const compressedSize = compressed.byteLength;
const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(2);
console.log(`原始大小: ${originalSize} 字节`);
console.log(`压缩后大小: ${compressedSize} 字节`);
console.log(`压缩率: ${ratio}%`);
return compressed;
}
// 使用示例
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file && file.type === 'image/png') {
compressPNGLossy(file, 0.7);
}
});APNG 动画编码
以下示例演示如何将多帧图像数据编码为 APNG 动画文件:
function createAPNG(frames, delays, width, height) {
// frames: 像素数据 ArrayBuffer 数组
// delays: 帧延迟数组,单位为毫秒
// width, height: 图像尺寸
// 编码为 APNG
const apngBuffer = UPNG.encode(
frames,
width,
height,
0, // 无损压缩
delays
);
// 创建 Blob
const blob = new Blob([apngBuffer], { type: 'image/png' });
return blob;
}
// 生成示例帧数据
function generateSampleFrames(width, height) {
const frames = [];
const colors = [
[255, 0, 0, 255], // 红色
[0, 255, 0, 255], // 绿色
[0, 0, 255, 255], // 蓝色
[255, 255, 0, 255] // 黄色
];
for (let c = 0; c < colors.length; c++) {
const frame = new Uint8Array(width * height * 4);
const color = colors[c];
for (let i = 0; i < frame.length; i += 4) {
frame[i] = color[0]; // R
frame[i + 1] = color[1]; // G
frame[i + 2] = color[2]; // B
frame[i + 3] = color[3]; // A
}
frames.push(frame.buffer);
}
return frames;
}
// 使用示例
const width = 200;
const height = 200;
const frames = generateSampleFrames(width, height);
const delays = [500, 500, 500, 500]; // 每帧显示 500ms
const apngBlob = createAPNG(frames, delays, width, height);
const url = URL.createObjectURL(apngBlob);
// 可以将 url 赋值给 img 元素的 src 属性查看效果APNG 动画解码与播放
以下示例演示如何解码 APNG 动画并在 Canvas 上逐帧播放:
class APNGPlayer {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.frames = [];
this.delays = [];
this.currentFrame = 0;
this.playing = false;
this.timer = null;
}
async load(url) {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
// 解码 APNG
const apng = UPNG.decode(buffer);
const rgba8 = UPNG.toRGBA8(apng);
// 设置 Canvas 尺寸
this.canvas.width = apng.width;
this.canvas.height = apng.height;
// 存储帧数据
this.frames = rgba8.map(data => new Uint8Array(data));
this.delays = apng.frames.map(frame => frame.delay);
// 显示第一帧
this.showFrame(0);
}
showFrame(index) {
if (index < 0 || index >= this.frames.length) return;
this.currentFrame = index;
const imageData = this.ctx.createImageData(
this.canvas.width,
this.canvas.height
);
imageData.data.set(this.frames[index]);
this.ctx.putImageData(imageData, 0, 0);
}
play() {
if (this.playing || this.frames.length === 0) return;
this.playing = true;
const nextFrame = () => {
if (!this.playing) return;
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
this.showFrame(this.currentFrame);
const delay = this.delays[this.currentFrame] || 100;
this.timer = setTimeout(nextFrame, delay);
};
const delay = this.delays[this.currentFrame] || 100;
this.timer = setTimeout(nextFrame, delay);
}
pause() {
this.playing = false;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
stop() {
this.pause();
this.showFrame(0);
}
}
// 使用示例
const canvas = document.getElementById('apngCanvas');
const player = new APNGPlayer(canvas);
player.load('animation.png').then(() => {
player.play();
});
// 暂停播放
// player.pause();
// 停止播放
// player.stop();技术原理
UPNG.js 的实现遵循 PNG 规范,解码过程主要包括以下步骤:首先读取 PNG 文件头和数据块,解析 IHDR 块获取图像基本信息,然后根据颜色类型和位深度解析 IDAT 块中的压缩数据,经过 Deflate 解压、滤波处理、调色板查找等步骤,最终还原为像素数据。
编码过程则是解码的逆过程:将像素数据进行滤波处理,然后使用 Deflate 算法压缩,封装为 PNG 文件格式的各个数据块。对于有损压缩,UPNG.js 内置了颜色量化算法,能够将真彩色图像量化为指定数量的调色板颜色,通过减少颜色数量来提高压缩率。
在 APNG 支持方面,UPNG.js 能够解析 acTL、fcTL、fdAT 等 APNG 专用数据块,提取动画的帧数、循环次数、每帧的延迟和偏移等信息。编码时则会按照 APNG 规范生成相应的数据块,确保生成的动画文件能够被支持 APNG 的浏览器和图像查看器正确播放。
性能特点
UPNG.js 在性能方面表现出色,主要体现在以下几个方面:
编解码速度较快,对于常规尺寸的图像能够在毫秒级完成处理,适合在前端实时处理场景中使用。压缩率表现良好,尤其是在有损压缩模式下,通过颜色量化和优化的 Deflate 参数,能够在视觉质量损失较小的情况下,实现较高的压缩比。内存占用较低,处理过程中不会产生大量的临时对象,适合处理较大尺寸的图像。
由于采用纯 JavaScript 实现,UPNG.js 的性能受 JavaScript 引擎执行效率的影响。在现代浏览器中,得益于 V8 等引擎的 JIT 编译优化,性能能够满足大多数应用场景的需求。对于特别大的图像或批量处理场景,可能需要考虑使用 Web Worker 来避免阻塞主线程。
适用场景
UPNG.js 适用于多种前端图像处理场景,包括但不限于:
前端图片压缩工具,在用户上传图片前进行压缩,减少上传流量和服务器存储压力。Canvas 图像导出,将 Canvas 绘制内容导出为 PNG 文件,相比浏览器原生的 toDataURL 方法,能够获得更高的压缩率和更多的控制选项。APNG 动画制作与播放,在网页中实现轻量级的动画效果,替代部分 GIF 动画场景,获得更好的画质和更小的文件体积。图像格式转换,将其他格式的图像转换为 PNG,或在不同 PNG 格式之间进行转换。在线图像编辑器,作为 PNG 格式的底层处理引擎,支持图像的打开、编辑和保存操作。
注意事项
使用 UPNG.js 时,有几个方面需要注意:
encode 方法的 imgs 参数要求传入 ArrayBuffer 数组,而不是 Uint8Array 数组。如果手中只有 Uint8Array ,需要通过 .buffer 属性获取底层的 ArrayBuffer 。有损压缩并不总是能减小文件体积,对于颜色数量本来就很少的简单图像,有损压缩后文件可能反而变大,建议根据实际图像内容选择合适的压缩模式。APNG 的帧延迟单位是毫秒,但 PNG 规范中实际存储的是百分之一秒,UPNG.js 在内部进行了转换,使用时直接传入毫秒值即可。处理大尺寸图像时,可能会占用较多内存,建议根据实际情况限制图像尺寸或采用分块处理的方式。
UPNG.js 是一个专注于 PNG 处理的专业库,它的出现为前端开发者提供了一个强大的图像处理工具。无论是简单的图片压缩,还是复杂的动画制作,UPNG.js 都能提供稳定可靠的支持。随着 Web 应用的功能越来越丰富,这类纯 JavaScript 实现的图像处理库将在更多场景中发挥作用。
发布评论
评论列表 0



