在線 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 進製表示信息。
可以看到前八個字節和上面表示的一樣。
於是可以根據這個特性判斷,於是就有如下的判斷代碼。
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
包,簡單引入即可使用。
安裝依賴
npm install upng-js
核心方法就 3 個,依次調用即可
- UPNG.decode(buffer)
- UPNG.toRGBA8(img)
- UPNG.encode(imgs, w, h, cnum, [dels])
- cnum:0 表示無損壓縮,256表示有損,可以調整這個值來控制壓縮質量。
注意:壓縮並不意味着一定小,對於一些已經很簡單且小的圖片,壓縮後可能反而更大。
下面是這個方法的最簡實現。
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 實現),提供了一些常用的配置選項。
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 中加入如下資源即可使用。
<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 給到的例子完全一致。
代碼裏調用方式如下
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)。