圖片處理不用愁,給你十個小幫手
本文阿寶哥會為小伙伴們隆重介紹用于圖片處理的十個?「“小幫手”」,他們各個身懷絕技,擁有模糊、壓縮、裁剪、旋轉(zhuǎn)、合成、比對等技能。相信認識他們之后,你將能夠輕松應(yīng)對大多數(shù)的圖片處理場景。
不過在介紹?「“小幫手”」?前,阿寶哥會先介紹一些圖片相關(guān)的基礎(chǔ)知識。此外,為了讓小伙伴們能夠?qū)W習(xí)更多圖片相關(guān)的知識,阿寶哥精心準(zhǔn)備了?「“阿寶哥有話說”」?章節(jié)。該章節(jié)你將會學(xué)到以下知識:
- 如何區(qū)分圖片的類型(非文件后綴名);
- 如何獲取圖片的尺寸(非右鍵查看圖片信息);
- 如何預(yù)覽本地圖片(非圖片閱讀器);
- 如何實現(xiàn)圖片壓縮(非圖片壓縮工具);
- 如何操作位圖像素數(shù)據(jù)(非 PS 等圖片處理軟件);
- 如何實現(xiàn)圖片隱寫(非肉眼可見)。
十個圖片處理?「“小幫手”」?已經(jīng)已經(jīng)迫不及待想與你見面,還在猶豫什么?趕緊出發(fā)吧!
一、基礎(chǔ)知識
1.1 位圖
「位圖圖像(bitmap),亦稱為點陣圖像或柵格圖像,是由稱作像素(圖片元素)的單個點組成的?!?/strong>?這些點可以進行不同的排列和染色以構(gòu)成圖樣。當(dāng)放大位圖時,可以看見賴以構(gòu)成整個圖像的無數(shù)單個方塊。擴大位圖尺寸的效果是增大單個像素,從而使線條和形狀顯得參差不齊。
「用數(shù)碼相機拍攝的照片、掃描儀掃描的圖片以及計算機截屏圖等都屬于位圖?!?/strong>?位圖的特點是可以表現(xiàn)色彩的變化和顏色的細微過渡,產(chǎn)生逼真的效果,缺點是在保存時需要記錄每一個像素的位置和顏色值,占用較大的存儲空間。常用的位圖處理軟件有 Photoshop、Painter 和 Windows 系統(tǒng)自帶的畫圖工具等。
分辨率是位圖不可逾越的壁壘,在對位圖進行縮放、旋轉(zhuǎn)等操作時,無法生產(chǎn)新的像素,因此會放大原有的像素填補空白,這樣會讓圖片顯得不清晰。

(圖片來源:https://zh.wikipedia.org/wiki/%E4%BD%8D%E5%9B%BE)
圖中的小方塊被稱為像素,這些小方塊都有一個明確的位置和被分配的色彩數(shù)值,小方格顏色和位置就決定該圖像所呈現(xiàn)出來的樣子。
可以將像素視為整個圖像中不可分割的單位或者是元素。「不可分割的意思是它不能夠再切割成更小單位抑或是元素,它是以一個單一顏色的小格存在。」?每一個點陣圖像包含了一定量的像素,這些像素決定圖像在屏幕上所呈現(xiàn)的大小。
1.2 矢量圖
所謂矢量圖,就是使用直線和曲線來描述的圖形,構(gòu)成這些圖形的元素是一些點、線、矩形、多邊形、圓和弧線等,「它們都是通過數(shù)學(xué)公式計算獲得的,具有編輯后不失真的特點。」例如一幅畫的矢量圖形實際上是由線段形成外框輪廓,由外框的顏色以及外框所封閉的顏色決定畫顯示出的顏色。
「矢量圖以幾何圖形居多,圖形可以無限放大,不變色、不模糊?!?/strong>?常用于圖案、標(biāo)志、VI、文字等設(shè)計。常用軟件有:CorelDraw、Illustrator、Freehand、XARA、CAD 等。
這里我們以 Web 開發(fā)者比較熟悉的 SVG(「Scalable Vector Graphics —— 可縮放矢量圖形」)為例,來了解一下 SVG 的結(jié)構(gòu):

可縮放矢量圖形(英語:Scalable Vector Graphics,SVG)是一種基于可擴展標(biāo)記語言(XML),用于描述二維矢量圖形的圖形格式。SVG 由 W3C 制定,是一個開放標(biāo)準(zhǔn)。
SVG 主要支持以下幾種顯示對象:
- 矢量顯示對象,基本矢量顯示對象包括矩形、圓、橢圓、多邊形、直線、任意曲線等;
- 嵌入式外部圖像,包括 PNG、JPEG、SVG 等;
- 文字對象。
了解完位圖與矢量圖的區(qū)別,下面我們來介紹一下位圖的數(shù)學(xué)表示。
1.3 位圖的數(shù)學(xué)表示
位圖的像素都分配有特定的位置和顏色值。每個像素的顏色信息由 RGB 組合或者灰度值表示。
根據(jù)位深度,可將位圖分為1、4、8、16、24 及 32 位圖像等。每個像素使用的信息位數(shù)越多,可用的顏色就越多,顏色表現(xiàn)就越逼真,相應(yīng)的數(shù)據(jù)量越大。
「1.3.1 二值圖像」
位深度為 1 的像素位圖只有兩個可能的值(黑色和白色),所以又稱為二值圖像。二值圖像的像素點只有黑白兩種情況,因此每個像素點可以由 0 和 1 來表示。
比如一張 4 * 4 二值圖像:
1?1?0?1
1?1?0?1
1?0?0?0
1?0?1?0
「1.3.2 RGB 圖像」
RGB 圖像由三個顏色通道組成,其中 RGB 代表紅、綠、藍三個通道的顏色。8 位/通道的 RGB 圖像中的每個通道有 256 個可能的值,這意味著該圖像有 1600 萬個以上可能的顏色值。
有時將帶有 8 位/通道(bpc)的 RGB 圖像稱作 24 位圖像(8 位 x 3 通道 = 24 位數(shù)據(jù)/像素)。通常將使用 24 位 RGB 組合數(shù)據(jù)位表示的的位圖稱為真彩色位圖。
RGB 彩色圖像可由三種矩陣表示:一種代表像素中紅色的強度,一種代表綠色,另一種代表藍色。

(圖片來源:https://freecontent.manning.com/the-computer-vision-pipeline-part-2-input-images/)
「圖像處理的本質(zhì)實際上就是對這些像素矩陣進行計算?!?/strong>?其實位圖中的圖像類型,除了二值圖像和 RGB 圖像之外,還有灰度圖像、索引圖像和 YUV 圖像。這里我們不做過多介紹,感興趣的小伙伴,請自行查閱相關(guān)資料。
二、圖片處理庫
2.1 AlloyImage
?基于 HTML 5 的專業(yè)級圖像處理開源引擎。
https://github.com/AlloyTeam/AlloyImage
?
AlloyImage 基于 HTML5 技術(shù)的專業(yè)圖像處理庫,來自騰訊 AlloyTeam 團隊。它擁有以下功能特性:
- 基于多圖層操作 —— 一個圖層的處理不影響其他圖層;
- 與 PS 對應(yīng)的 17 種圖層混合模式 —— 便于 PS 處理教程的無縫遷移;
- 多種基本濾鏡處理效果 —— 基本濾鏡不斷豐富、可擴展;
- 基本的圖像調(diào)節(jié)功能 —— 色相、飽和度、對比度、亮度、曲線等;
- 簡單快捷的 API —— 鏈?zhǔn)教幚?、API 簡潔易用、傳參靈活;
- 多種組合效果封裝 —— 一句代碼輕松實現(xiàn)一種風(fēng)格;
- 接口一致的單、多線程支持 —— 單、多線程切換無需更改一行代碼,多線程保持快捷 API 特性。
對于該庫 AlloyTeam 團隊建議的使用場景如下:
- 桌面軟件客戶端內(nèi)嵌網(wǎng)頁運行方式 >>> 打包 Webkit 內(nèi)核:用戶較大頭像上傳風(fēng)格處理、用戶相冊風(fēng)格處理(處理時間平均 < 1s);
- Win8 Metro 應(yīng)用 >>> 用戶上傳頭像,比較小的圖片風(fēng)格處理后上傳(Win8 下 IE 10 支持多線程);
- Mobile APP >>> Andriod 平臺、iOS 平臺小圖風(fēng)格 Web 處理的需求,如 PhoneGap 應(yīng)用,在線頭像上傳時的風(fēng)格處理、Mobile Web 端分享圖片時風(fēng)格處理等。
「使用示例」
//?$AI或AlloyImage初始化一個AlloyImage對象
var?ps?=?$AI(img,?600).save('jpg',?0.6);
//?save將合成圖片保存成base64格式字符串
var?string?=?AlloyImage(img).save('jpg',?0.8);
//?saveFile將合成圖片下載到本地
img.onclick?=?function(){
??AlloyImage(this).saveFile('處理后圖像.jpg',?0.8);
}
「在線示例」
?http://alloyteam.github.io/AlloyImage/
?

(圖片來源:http://alloyteam.github.io/AlloyImage/)
2.2 blurify
?blurify.js is a tiny(~2kb) library to blurred pictures, support graceful downgrade from?
css?mode to?canvas?mode.https://github.com/JustClear/blurify
?
blurify.js 是一個用于圖片模糊,很小的 JavaScript 庫(約 2 kb),并支持從 CSS 模式到 Canvas 模式的優(yōu)雅降級。該插件支持三種模式:
- css 模式:使用?
filter?屬性,默認模式; - canvas 模式:使用?
canvas?導(dǎo)出 base64; - auto 模式:優(yōu)先使用 css 模式,否則自動切換到 canvas 模式。
「使用示例」
import?blurify?from?'blurify';
new?blurify({
????images:?document.querySelectorAll('.blurify'),
????blur:?6,
????mode:?'css',
});
//?or?in?shorthand
blurify(6,?document.querySelectorAll('.blurify'));
「在線示例」
?https://justclear.github.io/blurify/
?

(圖片來源:https://justclear.github.io/blurify/)
看到這里是不是有些小伙伴覺得只是模糊處理而已,覺得不過癮,能不能來點更酷的。嘿嘿,有求必應(yīng)!阿寶哥立馬來個?「“酷炫叼”」?的庫 —— midori,該庫用于為背景圖創(chuàng)建動畫,使用 three.js 編寫并使用 WebGL。本來是想給個演示動圖,無奈單個 Gif 文件太大,只能放個體驗地址,感興趣的小伙伴自行體驗一下。
?midori 示例地址:https://aeroheim.github.io/midori/
?
2.3 cropperjs
?JavaScript image cropper.
https://github.com/fengyuanchen/cropperjs
?
Cropper.js 是一款非常強大卻又簡單的圖片裁剪工具,它可以進行非常靈活的配置,支持手機端使用,支持包括 IE9 以上的現(xiàn)代瀏覽器。它可以用于滿足諸如裁剪頭像上傳、商品圖片編輯之類的需求。
Cropper.js 支持以下特性:
- 支持 39 個配置選項;
- 支持 27 個方法;
- 支持 6 種事件;
- 支持 touch(移動端);
- 支持縮放、旋轉(zhuǎn)和翻轉(zhuǎn);
- 支持在畫布上裁剪;
- 支持在瀏覽器端通過畫布裁剪圖像;
- 支持處理 Exif 方向信息;
- 跨瀏覽器支持。
?可交換圖像文件格式(英語:Exchangeable image file format,官方簡稱 Exif),是專門為數(shù)碼相機的照片設(shè)定的文件格式,可以記錄數(shù)碼照片的屬性信息和拍攝數(shù)據(jù)。Exif 可以附加于 JPEG、TIFF、RIFF 等文件之中,為其增加有關(guān)數(shù)碼相機拍攝信息的內(nèi)容和索引圖或圖像處理軟件的版本信息。
Exif 信息以 0xFFE1 作為開頭標(biāo)記,后兩個字節(jié)表示 Exif 信息的長度。所以 Exif 信息最大為 64 kB,而內(nèi)部采用 TIFF 格式。
?
「使用示例」
//?import?'cropperjs/dist/cropper.css';
import?Cropper?from?'cropperjs';
const?image?=?document.getElementById('image');
const?cropper?=?new?Cropper(image,?{
??aspectRatio:?16?/?9,
??crop(event)?{
????console.log(event.detail.x);
????console.log(event.detail.y);
????console.log(event.detail.width);
????console.log(event.detail.height);
????console.log(event.detail.rotate);
????console.log(event.detail.scaleX);
????console.log(event.detail.scaleY);
??},
});
「在線示例」
?https://fengyuanchen.github.io/cropperjs/
?

2.4 compressorjs
?JavaScript image compressor.
https://github.com/fengyuanchen/compressorjs
?
compressorjs 是 JavaScript 圖像壓縮器。使用瀏覽器原生的?canvas.toBlob?API 進行壓縮工作,這意味著它是有損壓縮。通常的使用場景是,在瀏覽器端圖片上傳之前對其進行預(yù)壓縮。
在瀏覽器端要實現(xiàn)圖片壓縮,除了使用?canvas.toBlob?API 之外,還可以使用 Canvas 提供的另一個 API,即?toDataURL?API,它接收?type?和?encoderOptions?兩個可選參數(shù)。
其中?type?表示圖片格式,默認為?image/png。而?encoderOptions?用于表示圖片的質(zhì)量,在指定圖片格式為?image/jpeg?或?image/webp?的情況下,可以從 0 到 1 的區(qū)間內(nèi)選擇圖片的質(zhì)量。如果超出取值范圍,將會使用默認值?0.92,其他參數(shù)會被忽略。
相比?canvas.toDataURL?API 來說,canvas.toBlob?API 是異步的,因此多了個?callback?參數(shù),這個?callback?回調(diào)方法默認的第一個參數(shù)就是轉(zhuǎn)換好的?blob?文件信息。canvas.toBlob?的簽名如下:
canvas.toBlob(callback,?mimeType,?qualityArgument)
「使用示例」
import?axios?from?'axios';
import?Compressor?from?'compressorjs';
//?
document.getElementById('file').addEventListener('change',?(e)?=>?{
??const?file?=?e.target.files[0];
??if?(!file)?{
????return;
??}
??new?Compressor(file,?{
????quality:?0.6,
????success(result)?{
??????const?formData?=?new?FormData();
??????//?The?third?parameter?is?required?for?server
??????formData.append('file',?result,?result.name);
??????//?Send?the?compressed?image?file?to?server?with?XMLHttpRequest.
??????axios.post('/path/to/upload',?formData).then(()?=>?{
????????console.log('Upload?success');
??????});
????},
????error(err)?{
??????console.log(err.message);
????},
??});
});
「在線示例」
?https://fengyuanchen.github.io/compressorjs/
?

2.5 fabric.js
?Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser.
https://github.com/fabricjs/fabric.js
?
Fabric.js 是一個框架,可讓你輕松使用 HTML5 Canvas 元素。它是一個位于 Canvas 元素之上的交互式對象模型,同時也是一個?「SVG-to-canvas」?的解析器。
使用 Fabric.js,你可以在畫布上創(chuàng)建和填充對象。所謂的對象,可以是簡單的幾何形狀,比如矩形,圓形,橢圓形,多邊形,或更復(fù)雜的形狀,包含數(shù)百或數(shù)千個簡單路徑。然后,你可以使用鼠標(biāo)縮放,移動和旋轉(zhuǎn)這些對象。并修改它們的屬性 —— 顏色,透明度,z-index 等。此外你還可以一起操縱這些對象,即通過簡單的鼠標(biāo)選擇將它們分組。
Fabric.js 支持所有主流的瀏覽器,具體的兼容情況如下:
- Firefox 2+
- Safari 3+
- Opera 9.64+
- Chrome(所有版本)
- IE10,IE11,Edge
「使用示例」
<html>
<head>head>
<body>
????<canvas?id="canvas"?width="300"?height="300">canvas>
????<script?src="lib/fabric.js">script>
????<script>
????????var?canvas?=?new?fabric.Canvas('canvas');
????????var?rect?=?new?fabric.Rect({
????????????top?:?100,
????????????left?:?100,
????????????width?:?60,
????????????height?:?70,
????????????fill?:?'red'
????????});
????????canvas.add(rect);
????script>
body>
html>
「在線示例」
?http://fabricjs.com/kitchensink
?

(圖片來源:https://github.com/fabricjs/fabric.js)
2.6 Resemble.js
?Image analysis and comparison
https://github.com/rsmbl/Resemble.js
?
Resemble.js 使用 HTML Canvas 和 JavaScript 來實現(xiàn)圖片的分析和比較。兼容大于 8.0 的 Node.js 版本。
「使用示例」
//?比較兩張圖片
var?diff?=?resemble(file)
????.compareTo(file2)
????.ignoreColors()
????.onComplete(function(data)?{
????????console.log(data);
?????/*
?????{
????????misMatchPercentage?:?100,?//?%
????????isSameDimensions:?true,?//?or?false
????????dimensionDifference:?{?width:?0,?height:?-1?},?
????????getImageDataUrl:?function(){}
?????}
????*/
});
「在線示例」
?http://rsmbl.github.io/Resemble.js/
?

2.7 Pica
?Resize image in browser with high quality and high speed
https://github.com/nodeca/pica
?
Pica 可用于在瀏覽器中調(diào)整圖像大小,沒有像素化并且相當(dāng)快。它會自動選擇最佳的可用技術(shù):webworkers,webassembly,createImageBitmap,純 JS。
借助 Pica,你可以實現(xiàn)以下功能:
- 減小大圖像的上傳大小,節(jié)省上傳時間;
- 在圖像處理上節(jié)省服務(wù)器資源;
- 在瀏覽器中生成縮略圖。
「使用示例」
const?pica?=?require('pica')();
//?調(diào)整畫布/圖片的大小
pica.resize(from,?to,?{
??unsharpAmount:?80,
??unsharpRadius:?0.6,
??unsharpThreshold:?2
})
.then(result?=>?console.log('resize?done!'));
//?調(diào)整大小并轉(zhuǎn)換為Blob
pica.resize(from,?to)
??.then(result?=>?pica.toBlob(result,?'image/jpeg',?0.90))
??.then(blob?=>?console.log('resized?to?canvas?&?created?blob!'));
「在線示例」
?http://nodeca.github.io/pica/demo/
?

2.8 tui.image-editor
??? Full-featured photo image editor using canvas. It is really easy, and it comes with great filters.
https://github.com/nhn/tui.image-editor
?
tui.image-editor 是使用 HTML5 Canvas 的全功能圖像編輯器。它易于使用,并提供強大的過濾器。同時它支持對圖像進行裁剪、翻轉(zhuǎn)、旋轉(zhuǎn)、繪圖、形狀、文本、遮罩和圖片過濾等操作。
tui.image-editor 的瀏覽器兼容情況如下:
- Chrome
- Edge
- Safari
- Firefox
- IE 10+
「使用示例」
//?Image?editor
var?imageEditor?=?new?tui.ImageEditor("#tui-image-editor-container",?{
?????includeUI:?{
???????loadImage:?{
?????????path:?"img/sampleImage2.png",
?????????name:?"SampleImage",
???????},
???????theme:?blackTheme,?//?or?whiteTheme
?????????initMenu:?"filter",
?????????menuBarPosition:?"bottom",
???????},
???????cssMaxWidth:?700,
???????cssMaxHeight:?500,
???????usageStatistics:?false,
});
window.onresize?=?function?()?{
??imageEditor.ui.resizeEditor();
};
在線示例
?https://ui.toast.com/tui-image-editor/
?

2.9 gif.js
?JavaScript GIF encoding library
https://github.com/jnordberg/gif.js
?
gif.js 是運行在瀏覽器端的 JavaScript GIF 編碼器。它使用類型化數(shù)組和 Web Worker 在后臺渲染每一幀,速度真的很快。該庫可工作在支持:Web Workers,F(xiàn)ile API 和 Typed Arrays 的瀏覽器中。
gif.js 的瀏覽器兼容情況如下:
- Google Chrome
- Firefox 17
- Safari 6
- Internet Explorer 10
- Mobile Safari iOS 6
「使用示例」
var?gif?=?new?GIF({
??workers:?2,
??quality:?10
});
//?add?an?image?element
gif.addFrame(imageElement);
//?or?a?canvas?element
gif.addFrame(canvasElement,?{delay:?200});
//?or?copy?the?pixels?from?a?canvas?context
gif.addFrame(ctx,?{copy:?true});
gif.on('finished',?function(blob)?{
??window.open(URL.createObjectURL(blob));
});
gif.render();
「在線示例」
?http://jnordberg.github.io/gif.js/
?


2.10 Sharp
?High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images. Uses the libvips library.
https://github.com/lovell/sharp
?
Sharp 的典型應(yīng)用場景是將常見格式的大圖像轉(zhuǎn)換為尺寸較小,對網(wǎng)絡(luò)友好的 JPEG,PNG 和 WebP 格式的圖像。由于其內(nèi)部使用 libvips ,使得調(diào)整圖像大小通常比使用 ImageMagick 和 GraphicsMagick 設(shè)置快 4-5 倍 。除了支持調(diào)整圖像大小之外,Sharp 還支持旋轉(zhuǎn)、提取、合成和伽馬校正等功能。
Sharp 支持讀取 JPEG,PNG,WebP,TIFF,GIF 和 SVG 圖像。輸出圖像可以是 JPEG,PNG,WebP 和 TIFF 格式,也可以是未壓縮的原始像素數(shù)據(jù)。
「使用示例」
//?改變圖像尺寸
sharp(inputBuffer)
??.resize(320,?240)
??.toFile('output.webp',?(err,?info)?=>?{?...?});
???????
//?旋轉(zhuǎn)輸入圖像并改變圖片尺寸?????????????????????????????????????????
sharp('input.jpg')
??.rotate()
??.resize(200)
??.toBuffer()
??.then(?data?=>?{?...?})
??.catch(?err?=>?{?...?});?????????????????????????????????????????
「在線示例」
?https://segmentfault.com/a/1190000012903787
?
該示例是來自阿寶哥 18 年寫的 “Sharp 牛刀小試之生成專屬分享圖片” 這篇文章,主要是利用 Sharp 提供的圖片合成功能為每個用戶生成專屬的分享海報,感興趣的小伙伴可以閱讀一下原文喲。
const?sharp?=?require("sharp");
const?TextToSVG?=?require("text-to-svg");
const?path?=?require("path");
//?加載字體文件
const?textToSVG?=?TextToSVG.loadSync(path.join(__dirname,?"./simhei.ttf"));
//?創(chuàng)建圓形SVG,用于實現(xiàn)頭像裁剪
const?roundedCorners?=?new?Buffer(
??''
);
//?設(shè)置SVG文本元素相關(guān)參數(shù)
const?attributes?=?{?fill:?"white"?};
const?svgOptions?=?{
??x:?0,
??y:?0,
??fontSize:?32,
??anchor:?"top",
??attributes:?attributes
};
/**
?*?使用文本生成SVG
?*?@param?{*}?text?
?*?@param?{*}?options?
?*/
function?textToSVGFn(text,?options?=?svgOptions)?{
??return?textToSVG.getSVG(text,?options);
}
/**
?*?圖層疊加生成分享圖片
?*?@param?{*}?options?
?*?
?*/
async?function?genShareImage(options)?{
??const?{?backgroudPath,?avatarPath,?qrcodePath,?
????userName,?words,?likes,?outFilePath
??}?=?options;
??//?背景圖片
??const?backgroudBuffer?=?sharp(path.join(__dirname,?backgroudPath)).toBuffer({
????resolveWithObject:?true
??});
??const?backgroundImageInfo?=?await?backgroudBuffer;
??//?頭像圖片
??const?avatarBuffer?=?await?genCircleAvatar(path.join(__dirname,?avatarPath));
??//?二維碼圖片
??const?qrCodeBuffer?=?await?sharp(path.join(__dirname,?qrcodePath))
????.resize(180)
????.toBuffer({
??????resolveWithObject:?true
????});
??//?用戶名
??const?userNameSVG?=?textToSVGFn(userName);
??//?用戶數(shù)據(jù)
??const?userDataSVG?=?textToSVGFn(`寫了${words}個字???收獲${likes}個贊`);
??const?userNameBuffer?=?await?sharp(new?Buffer(userNameSVG)).toBuffer({
????resolveWithObject:?true
??});
??const?userDataBuffer?=?await?sharp(new?Buffer(userDataSVG)).toBuffer({
????resolveWithObject:?true
??});
??const?buffers?=?[avatarBuffer,?qrCodeBuffer,?userNameBuffer,?userDataBuffer];
??//?圖層疊加參數(shù)列表
??const?overlayOptions?=?[
????{?top:?150,?left:?230?},
????{?top:?861,?left:?227?},
????{
??????top:?365,
??????left:?(backgroundImageInfo.info.width?-?userNameBuffer.info.width)?/?2
????},
????{
??????top:?435,
??????left:?(backgroundImageInfo.info.width?-?userDataBuffer.info.width)?/?2
????}
??];
??//?組合多個圖層:圖片+文字圖層
??return?buffers
????.reduce((input,?overlay,?index)?=>?{
??????return?input.then(result?=>?{
????????console.dir(overlay.info);
????????return?sharp(result.data)
??????????.overlayWith(overlay.data,?overlayOptions[index])
??????????.toBuffer({?resolveWithObject:?true?});
??????});
????},?backgroudBuffer)
????.then((data)?=>?{
??????return?sharp(data.data).toFile(outFilePath);
????}).catch(error?=>?{
??????throw?new?Error('Generate?Share?Image?Failed.');
????});
}
/**
?*?生成圓形的頭像
?*?@param?{*}?avatarPath?頭像路徑
?*/
function?genCircleAvatar(avatarPath)?{
??return?sharp(avatarPath)
????.resize(180,?180)
????.overlayWith(roundedCorners,?{?cutout:?true?})
????.png()
????.toBuffer({
??????resolveWithObject:?true
????});
}
module.exports?=?{
??genShareImage
};
三、阿寶哥有話說
3.1 如何區(qū)分圖片的類型
「計算機并不是通過圖片的后綴名來區(qū)分不同的圖片類型,而是通過 “魔數(shù)”(Magic Number)來區(qū)分。」?對于某一些類型的文件,起始的幾個字節(jié)內(nèi)容都是固定的,跟據(jù)這幾個字節(jié)的內(nèi)容就可以判斷文件的類型。
常見圖片類型對應(yīng)的魔數(shù)如下表所示:
| 文件類型 | 文件后綴 | 魔數(shù) |
|---|---|---|
| JPEG | jpg/jpeg | 0xFFD8FF |
| PNG | png | 0x89504E47 |
| GIF | gif | 0x47494638(GIF8) |
| BMP | bmp | 0x424D |
這里我們以阿寶哥的頭像(abao.png)為例,驗證一下該圖片的類型是否正確:

在日常開發(fā)過程中,如果遇到檢測圖片類型的場景,我們可以直接利用一些現(xiàn)成的第三方庫。比如,你想要判斷一張圖片是否為 PNG 類型,這時你可以使用 is-png 這個庫,它同時支持瀏覽器和 Node.js,使用示例如下:
「Node.js」
//?npm?install?read-chunk
const?readChunk?=?require('read-chunk');?
const?isPng?=?require('is-png');
const?buffer?=?readChunk.sync('unicorn.png',?0,?8);
isPng(buffer);
//=>?true
「Browser」
(async?()?=>?{
?const?response?=?await?fetch('unicorn.png');
?const?buffer?=?await?response.arrayBuffer();
?isPng(new?Uint8Array(buffer));
?//=>?true
})();
3.2 如何獲取圖片的尺寸
圖片的尺寸、位深度、色彩類型和壓縮算法都會存儲在文件的二進制數(shù)據(jù)中,我們繼續(xù)以阿寶哥的頭像(abao.png)為例,來了解一下實際的情況:

?528(十進制) => 0x0210
560(十進制)=> 0x0230
?
因此如果想要獲取圖片的尺寸,我們就需要依據(jù)不同的圖片格式對圖片二進制數(shù)據(jù)進行解析。幸運的是,我們不需要自己做這件事,image-size 這個 Node.js 庫已經(jīng)幫我們實現(xiàn)了獲取主流圖片類型文件尺寸的功能:
「同步方式」
var?sizeOf?=?require('image-size');
var?dimensions?=?sizeOf('images/abao.png');
console.log(dimensions.width,?dimensions.height);
「異步方式」
var?sizeOf?=?require('image-size');
sizeOf('images/abao.png',?function?(err,?dimensions)?{
??console.log(dimensions.width,?dimensions.height);
});
image-size 這個庫功能還是蠻強大的,除了支持 PNG 格式之外,還支持 BMP、GIF、ICO、JPEG、SVG 和 WebP 等格式。
3.3 如何預(yù)覽本地圖片
利用 HTML FileReader API,我們也可以方便的實現(xiàn)圖片本地預(yù)覽功能,具體代碼如下:
<input?type="file"?accept="image/*"?onchange="loadFile(event)">
<img?id="output"/>
<script>
??const?loadFile?=?function(event)?{
????const?reader?=?new?FileReader();
????reader.onload?=?function(){
??????const?output?=?document.querySelector('output');
??????output.src?=?reader.result;
????};
????reader.readAsDataURL(event.target.files[0]);
??};
script>
在完成本地圖片預(yù)覽之后,可以直接把圖片對應(yīng)的 Data URLs 數(shù)據(jù)提交到服務(wù)器。針對這種情形,服務(wù)端需要做一些相關(guān)處理,才能正常保存上傳的圖片,這里以 Express 為例,具體處理代碼如下:
const?app?=?require('express')();
app.post('/upload',?function(req,?res){
????let?imgData?=?req.body.imgData;?//?獲取POST請求中的base64圖片數(shù)據(jù)
????let?base64Data?=?imgData.replace(/^data:image\/\w+;base64,/,?"");
????let?dataBuffer?=?Buffer.from(base64Data,?'base64');
????fs.writeFile("image.png",?dataBuffer,?function(err)?{
????????if(err){
??????????res.send(err);
????????}else{
??????????res.send("圖片上傳成功!");
????????}
????});
});
3.4 如何實現(xiàn)圖片壓縮
在一些場合中,我們希望在上傳本地圖片時,先對圖片進行一定的壓縮,然后再提交到服務(wù)器,從而減少傳輸?shù)臄?shù)據(jù)量。在前端要實現(xiàn)圖片壓縮,我們可以利用 Canvas 對象提供的?toDataURL()?方法,該方法接收?type?和?encoderOptions?兩個可選參數(shù)。
其中?type?表示圖片格式,默認為?image/png。而?encoderOptions?用于表示圖片的質(zhì)量,在指定圖片格式為?image/jpeg?或?image/webp?的情況下,可以從 0 到 1 的區(qū)間內(nèi)選擇圖片的質(zhì)量。如果超出取值范圍,將會使用默認值?0.92,其他參數(shù)會被忽略。
下面我們來看一下具體如何實現(xiàn)圖片壓縮:
function?compress(base64,?quality,?mimeType)?{
??let?canvas?=?document.createElement("canvas");
??let?img?=?document.createElement("img");
??img.crossOrigin?=?"anonymous";
??return?new?Promise((resolve,?reject)?=>?{
????img.src?=?base64;
????img.onload?=?()?=>?{
??????let?targetWidth,?targetHeight;
??????if?(img.width?>?MAX_WIDTH)?{
????????targetWidth?=?MAX_WIDTH;
????????targetHeight?=?(img.height?*?MAX_WIDTH)?/?img.width;
??????}?else?{
????????targetWidth?=?img.width;
????????targetHeight?=?img.height;
??????}
??????canvas.width?=?targetWidth;
??????canvas.height?=?targetHeight;
??????let?ctx?=?canvas.getContext("2d");
??????ctx.clearRect(0,?0,?targetWidth,?targetHeight);?//?清除畫布
??????ctx.drawImage(img,?0,?0,?canvas.width,?canvas.height);
??????let?imageData?=?canvas.toDataURL(mimeType,?quality?/?100);
??????resolve(imageData);
????};
??});
}
對于返回的 Data URL 格式的圖片數(shù)據(jù),為了進一步減少傳輸?shù)臄?shù)據(jù)量,我們可以把它轉(zhuǎn)換為 Blob 對象:
function?dataUrlToBlob(base64,?mimeType)?{
??let?bytes?=?window.atob(base64.split(",")[1]);
??let?ab?=?new?ArrayBuffer(bytes.length);
??let?ia?=?new?Uint8Array(ab);
??for?(let?i?=?0;?i?????ia[i]?=?bytes.charCodeAt(i);
??}
??return?new?Blob([ab],?{?type:?mimeType?});
}
在轉(zhuǎn)換完成后,我們就可以壓縮后的圖片對應(yīng)的 Blob 對象封裝在 FormData 對象中,然后再通過 AJAX 提交到服務(wù)器上:
function?uploadFile(url,?blob)?{
??let?formData?=?new?FormData();
??let?request?=?new?XMLHttpRequest();
??formData.append("image",?blob);
??request.open("POST",?url,?true);
??request.send(formData);
}
3.5 如何操作位圖像素數(shù)據(jù)
如果想要操作圖片像素數(shù)據(jù),我們可以利用 CanvasRenderingContext2D 提供的?getImageData?來獲取圖片像素數(shù)據(jù),其中 getImageData() 返回一個 ImageData 對象,用來描述 canvas 區(qū)域隱含的像素數(shù)據(jù),這個區(qū)域通過矩形表示,起始點為(sx, sy)、寬為 sw、高為 sh。其中?getImageData?方法的語法如下:
ctx.getImageData(sx,?sy,?sw,?sh);
相應(yīng)的參數(shù)說明如下:
- sx:將要被提取的圖像數(shù)據(jù)矩形區(qū)域的左上角 x 坐標(biāo)。
- sy:將要被提取的圖像數(shù)據(jù)矩形區(qū)域的左上角 y 坐標(biāo)。
- sw:將要被提取的圖像數(shù)據(jù)矩形區(qū)域的寬度。
- sh:將要被提取的圖像數(shù)據(jù)矩形區(qū)域的高度。
在獲取到圖片的像素數(shù)據(jù)之后,我們就可以對獲取的像素數(shù)據(jù)進行處理,比如進行灰度化或反色處理。當(dāng)完成處理后,若要在頁面上顯示處理效果,則我們需要利用 CanvasRenderingContext2D 提供的另一個 API ——?putImageData。
該 API 是 Canvas 2D API 將數(shù)據(jù)從已有的 ImageData 對象繪制到位圖的方法。如果提供了一個繪制過的矩形,則只繪制該矩形的像素。此方法不受畫布轉(zhuǎn)換矩陣的影響。putImageData 方法的語法如下:
void?ctx.putImageData(imagedata,?dx,?dy);
void?ctx.putImageData(imagedata,?dx,?dy,?dirtyX,?dirtyY,?dirtyWidth,?dirtyHeight);
相應(yīng)的參數(shù)說明如下:
- imageData:?
ImageData?,包含像素值的數(shù)組對象。 - dx:源圖像數(shù)據(jù)在目標(biāo)畫布中的位置偏移量(x 軸方向的偏移量)。
- dy:源圖像數(shù)據(jù)在目標(biāo)畫布中的位置偏移量(y 軸方向的偏移量)。
- dirtyX(可選):在源圖像數(shù)據(jù)中,矩形區(qū)域左上角的位置。默認是整個圖像數(shù)據(jù)的左上角(x 坐標(biāo))。
- dirtyY(可選):在源圖像數(shù)據(jù)中,矩形區(qū)域左上角的位置。默認是整個圖像數(shù)據(jù)的左上角(y 坐標(biāo))。
- dirtyWidth(可選):在源圖像數(shù)據(jù)中,矩形區(qū)域的寬度。默認是圖像數(shù)據(jù)的寬度。
- dirtyHeight(可選):在源圖像數(shù)據(jù)中,矩形區(qū)域的高度。默認是圖像數(shù)據(jù)的高度。
介紹完相關(guān)的 API,下面我們來舉一個實際例子:
<html?lang="zh-CN">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>圖片反色和灰度化處理title>
??head>
??<body?onload="loadImage()">
????<div>
??????<button?id="invertbtn">反色button>
??????<button?id="grayscalebtn">灰度化button>
????div>
????<canvas?id="canvas"?width="800"?height="600">canvas>
????<script>
??????function?loadImage()?{
????????var?img?=?new?Image();
????????img.crossOrigin?=?"";
????????img.onload?=?function?()?{
??????????draw(this);
????????};
????????//?這是阿寶哥的頭像喲
????????img.src?=?"https://avatars3.githubusercontent.com/u/4220799";
??????}
??????function?draw(img)?{
????????var?canvas?=?document.getElementById("canvas");
????????var?ctx?=?canvas.getContext("2d");
????????ctx.drawImage(img,?0,?0);
????????img.style.display?=?"none";
????????var?imageData?=?ctx.getImageData(0,?0,?canvas.width,?canvas.height);
????????var?data?=?imageData.data;
????????var?invert?=?function?()?{
??????????for?(var?i?=?0;?i?4)?{
????????????data[i]?=?255?-?data[i];?//?red
????????????data[i?+?1]?=?255?-?data[i?+?1];?//?green
????????????data[i?+?2]?=?255?-?data[i?+?2];?//?blue
??????????}
??????????ctx.putImageData(imageData,?0,?0);
????????};
????????var?grayscale?=?function?()?{
??????????for?(var?i?=?0;?i?4)?{
????????????var?avg?=?(data[i]?+?data[i?+?1]?+?data[i?+?2])?/?3;
????????????data[i]?=?avg;?//?red
????????????data[i?+?1]?=?avg;?//?green
????????????data[i?+?2]?=?avg;?//?blue
??????????}
??????????ctx.putImageData(imageData,?0,?0);
????????};
????????var?invertbtn?=?document.getElementById("invertbtn");
????????invertbtn.addEventListener("click",?invert);
????????var?grayscalebtn?=?document.getElementById("grayscalebtn");
????????grayscalebtn.addEventListener("click",?grayscale);
??????}
????script>
??body>
html>
需要注意的在調(diào)用?getImageData?方法獲取圖片像素數(shù)據(jù)時,你可能會遇到跨域問題,比如:
Uncaught?DOMException:?Failed?to?execute?'getImageData'?on?'CanvasRenderingContext2D':?The?canvas?has?been?tainted?by?cross-origin?data.
對于這個問題,你可以閱讀?「張鑫旭」?大神 “解決canvas圖片getImageData,toDataURL跨域問題” 這一篇文章。
3.6 如何實現(xiàn)圖片隱寫
「隱寫術(shù)是一門關(guān)于信息隱藏的技巧與科學(xué),所謂信息隱藏指的是不讓除預(yù)期的接收者之外的任何人知曉信息的傳遞事件或者信息的內(nèi)容?!?/strong>?隱寫術(shù)的英文叫做 Steganography,來源于特里特米烏斯的一本講述密碼學(xué)與隱寫術(shù)的著作 Steganographia,該書書名源于希臘語,意為 “隱秘書寫”。
下圖是阿寶哥采用在線的圖片隱寫工具,將?「“全棧修仙之路”」?這 6 個字隱藏到原始的圖片中,然后使用對應(yīng)的解密工具,解密出隱藏信息的結(jié)果:

(在線圖片隱寫體驗地址:https://c.p2hp.com/yinxietu/)
目前有多種方案可以實現(xiàn)圖片隱寫,以下是幾種常見的方案:
- 附加式的圖片隱寫;
- 基于文件結(jié)構(gòu)的圖片隱寫;
- 基于 LSB 原理的圖片隱寫;
- 基于 DCT 域的 JPG 圖片隱寫;
- 數(shù)字水印的隱寫;
- 圖片容差的隱寫。
篇幅有限,這里我們就不繼續(xù)展開,分別介紹每種方案,感興趣的小伙伴可以閱讀 “隱寫術(shù)之圖片隱寫(一)” 這篇文章。
四、參考資源
- Baike - 矢量圖
- Wiki - 可縮放矢量圖形
- 隱寫術(shù)之圖片隱寫(一)
- 不能說的秘密——前端也能玩的圖片隱寫術(shù)
- 又拍圖片管家億級圖像之搜圖系統(tǒng)的兩代演進及底層原理
- image-manipulation-libraries-for-javascript
支持
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)
關(guān)注我的官網(wǎng)?https://muyiy.cn,讓我們成為長期關(guān)系
關(guān)注公眾號「高級前端進階」,公眾號后臺回復(fù)「面試題」 送你高級前端面試題,回復(fù)「加群」加入面試互助交流群
