圖片處理不用愁,給你十個小幫手

本文阿寶哥會爲小夥伴們隆重介紹用於圖片處理的十個 「小幫手」,他們各個身懷絕技,擁有模糊、壓縮、裁剪、旋轉、合成、比對等技能。相信認識他們以後,你將可以輕鬆應對大多數的圖片處理場景。javascript

不過在介紹 「小幫手」 前,阿寶哥會先介紹一些圖片相關的基礎知識。此外,爲了讓小夥伴們可以學習更多圖片相關的知識,阿寶哥精心準備了 「阿寶哥有話說」 章節。該章節你將會學到如下知識:css

  • 如何區分圖片的類型(非文件後綴名);
  • 如何獲取圖片的尺寸(非右鍵查看圖片信息);
  • 如何預覽本地圖片(非圖片閱讀器);
  • 如何實現圖片壓縮(非圖片壓縮工具);
  • 如何操做位圖像素數據(非 PS 等圖片處理軟件);
  • 如何實現圖片隱寫(非肉眼可見)。

十個圖片處理 「小幫手」 已經已經火燒眉毛想與你見面,還在猶豫什麼?趕忙出發吧!html

1、基礎知識

1.1 位圖

位圖圖像(bitmap),亦稱爲點陣圖像或柵格圖像,是由稱做像素(圖片元素)的單個點組成的。 這些點能夠進行不一樣的排列和染色以構成圖樣。當放大位圖時,能夠看見賴以構成整個圖像的無數單個方塊。擴大位圖尺寸的效果是增大單個像素,從而使線條和形狀顯得良莠不齊。前端

用數碼相機拍攝的照片、掃描儀掃描的圖片以及計算機截屏圖等都屬於位圖。 位圖的特色是能夠表現色彩的變化和顏色的細微過渡,產生逼真的效果,缺點是在保存時須要記錄每個像素的位置和顏色值,佔用較大的存儲空間。經常使用的位圖處理軟件有 Photoshop、Painter 和 Windows 系統自帶的畫圖工具等。java

分辨率是位圖不可逾越的壁壘,在對位圖進行縮放、旋轉等操做時,沒法生產新的像素,所以會放大原有的像素填補空白,這樣會讓圖片顯得不清晰。node

(圖片來源:https://zh.wikipedia.org/wiki...ios

圖中的小方塊被稱爲像素,這些小方塊都有一個明確的位置和被分配的色彩數值,小方格顏色和位置就決定該圖像所呈現出來的樣子。git

能夠將像素視爲整個圖像中不可分割的單位或者是元素。不可分割的意思是它不可以再切割成更小單位抑或是元素,它是以一個單一顏色的小格存在。 每個點陣圖像包含了必定量的像素,這些像素決定圖像在屏幕上所呈現的大小。github

1.2 矢量圖

所謂矢量圖,就是使用直線和曲線來描述的圖形,構成這些圖形的元素是一些點、線、矩形、多邊形、圓和弧線等,它們都是經過數學公式計算得到的,具備編輯後不失真的特色。 例如一幅畫的矢量圖形其實是由線段造成外框輪廓,由外框的顏色以及外框所封閉的顏色決定畫顯示出的顏色。web

矢量圖以幾何圖形居多,圖形能夠無限放大,不變色、不模糊。 經常使用於圖案、標誌、VI、文字等設計。經常使用軟件有:CorelDraw、Illustrator、Freehand、XARA、CAD 等。

這裏咱們以 Web 開發者比較熟悉的 SVGScalable Vector Graphics —— 可縮放矢量圖形)爲例,來了解一下 SVG 的結構:

svg-abao-demo.jpg

可縮放矢量圖形(英語:Scalable Vector Graphics,SVG)是一種基於可擴展標記語言(XML),用於描述二維矢量圖形的圖形格式。SVG 由 W3C 制定,是一個開放標準。

SVG 主要支持如下幾種顯示對象:

  • 矢量顯示對象,基本矢量顯示對象包括矩形、圓、橢圓、多邊形、直線、任意曲線等;
  • 嵌入式外部圖像,包括 PNG、JPEG、SVG 等;
  • 文字對象。

瞭解完位圖與矢量圖的區別,下面咱們來介紹一下位圖的數學表示。

1.3 位圖的數學表示

位圖的像素都分配有特定的位置和顏色值。每一個像素的顏色信息由 RGB 組合或者灰度值表示。

根據位深度,可將位圖分爲一、四、八、1六、24 及 32 位圖像等。每一個像素使用的信息位數越多,可用的顏色就越多,顏色表現就越逼真,相應的數據量越大。

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 位數據/像素)。一般將使用 24 位 RGB 組合數據位表示的的位圖稱爲真彩色位圖。

RGB 彩色圖像可由三種矩陣表示:一種表明像素中紅色的強度,一種表明綠色,另外一種表明藍色。

rgb-elgendy-demo.png

(圖片來源:https://freecontent.manning.c...

圖像處理的本質實際上就是對這些像素矩陣進行計算。 其實位圖中的圖像類型,除了二值圖像和 RGB 圖像以外,還有灰度圖像、索引圖像和 YUV 圖像。這裏咱們不作過多介紹,感興趣的小夥伴,能夠自行查閱相關資料。

2、圖片處理庫

2.1 AlloyImage

基於 HTML 5 的專業級圖像處理開源引擎。

https://github.com/AlloyTeam/...

AlloyImage 基於 HTML5 技術的專業圖像處理庫,來自騰訊 AlloyTeam 團隊。它擁有如下功能特性:

  • 基於多圖層操做 —— 一個圖層的處理不影響其餘圖層;
  • 與 PS 對應的 17 種圖層混合模式 —— 便於 PS 處理教程的無縫遷移;
  • 多種基本濾鏡處理效果 —— 基本濾鏡不斷豐富、可擴展;
  • 基本的圖像調節功能 —— 色相、飽和度、對比度、亮度、曲線等;
  • 簡單快捷的 API —— 鏈式處理、API 簡潔易用、傳參靈活;
  • 多種組合效果封裝 —— 一句代碼輕鬆實現一種風格;
  • 接口一致的單、多線程支持 —— 單、多線程切換無需更改一行代碼,多線程保持快捷 API 特性。

對於該庫 AlloyTeam 團隊建議的使用場景以下:

  • 桌面軟件客戶端內嵌網頁運行方式 >>> 打包 Webkit 內核:用戶較大頭像上傳風格處理、用戶相冊風格處理(處理時間平均 < 1s);
  • Win8 Metro 應用 >>> 用戶上傳頭像,比較小的圖片風格處理後上傳(Win8 下 IE 10 支持多線程);
  • Mobile APP >>> Andriod 平臺、iOS 平臺小圖風格 Web 處理的需求,如 PhoneGap 應用,在線頭像上傳時的風格處理、Mobile Web 端分享圖片時風格處理等。

使用示例

// $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/Al...

alloy-image-soften-face.jpg

(圖片來源:http://alloyteam.github.io/Al...

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.js 是一個用於圖片模糊,很小的 JavaScript 庫(約 2 kb),並支持從 CSS 模式到 Canvas 模式的優雅降級。該插件支持三種模式:

  • css 模式:使用 filter 屬性,默認模式;
  • canvas 模式:使用 canvas 導出 base64;
  • auto 模式:優先使用 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/b...

blurify-demo.jpg

(圖片來源:https://justclear.github.io/b...

看到這裏是否是有些小夥伴以爲只是模糊處理而已,以爲不過癮,能不能來點更酷的。嘿嘿,有求必應!阿寶哥立馬來個 「酷炫叼」 的庫 —— midori,該庫用於爲背景圖建立動畫,使用 three.js 編寫並使用 WebGL。原本是想給個演示動圖,無奈單個 Gif 太大,只能放個體驗地址,感興趣的小夥伴自行體驗一下。

midori 示例地址: https://aeroheim.github.io/mi...

2.3 cropperjs

JavaScript image cropper.

https://github.com/fengyuanch...

Cropper.js 是一款很是強大卻又簡單的圖片裁剪工具,它能夠進行很是靈活的配置,支持手機端使用,支持包括 IE9 以上的現代瀏覽器。它能夠用於知足諸如裁剪頭像上傳、商品圖片編輯之類的需求。

Cropper.js 支持如下特性:

  • 支持 39 個配置選項;
  • 支持 27 個方法;
  • 支持 6 種事件;
  • 支持 touch(移動端);
  • 支持縮放、旋轉和翻轉;
  • 支持在畫布上裁剪;
  • 支持在瀏覽器端經過畫布裁剪圖像;
  • 支持處理 Exif 方向信息;
  • 跨瀏覽器支持。
可交換圖像文件格式(英語:Exchangeable image file format,官方簡稱 Exif),是專門爲數碼相機的照片設定的文件格式,能夠記錄數碼照片的屬性信息和拍攝數據。Exif 能夠附加於 JPEG、TIFF、RIFF 等文件之中,爲其增長有關數碼相機拍攝信息的內容和索引圖或圖像處理軟件的版本信息。

Exif 信息以 0xFFE1 做爲開頭標記,後兩個字節表示 Exif 信息的長度。因此 Exif 信息最大爲 64 kB,而內部採用 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.i...

cropper-demo.jpg

2.4 compressorjs

JavaScript image compressor.

https://github.com/fengyuanch...

compressorjs 是 JavaScript 圖像壓縮器。使用瀏覽器原生的 canvas.toBlob API 進行壓縮工做,這意味着它是有損壓縮。一般的使用場景是,在瀏覽器端圖片上傳以前對其進行預壓縮。

在瀏覽器端要實現圖片壓縮,除了使用 canvas.toBlob API 以外,還可使用 Canvas 提供的另外一個 API,即 toDataURL API,它接收 typeencoderOptions 兩個可選參數。

其中 type 表示圖片格式,默認爲 image/png。而 encoderOptions 用於表示圖片的質量,在指定圖片格式爲 image/jpegimage/webp 的狀況下,能夠從 0 到 1 的區間內選擇圖片的質量。若是超出取值範圍,將會使用默認值 0.92,其餘參數會被忽略。

相比 canvas.toDataURL API 來講,canvas.toBlob API 是異步的,所以多了個 callback 參數,這個 callback 回調方法默認的第一個參數就是轉換好的 blob 文件信息。canvas.toBlob 的簽名以下:

canvas.toBlob(callback, mimeType, qualityArgument)

使用示例

import axios from 'axios';
import Compressor from 'compressorjs';

// <input type="file" id="file" accept="image/*">
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.i...

compressorjs-demo.jpg

2.5 fabric.js

Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser.

https://github.com/fabricjs/f...

Fabric.js 是一個框架,可以讓你輕鬆使用 HTML5 Canvas 元素。它是一個位於 Canvas 元素之上的交互式對象模型,同時也是一個 SVG-to-canvas 的解析器。

使用 Fabric.js,你能夠在畫布上建立和填充對象。所謂的對象,能夠是簡單的幾何形狀,好比矩形,圓形,橢圓形,多邊形,或更復雜的形狀,包含數百或數千個簡單路徑。而後,你可使用鼠標縮放,移動和旋轉這些對象。並修改它們的屬性 —— 顏色,透明度,z-index 等。此外你還能夠一塊兒操縱這些對象,即經過簡單的鼠標選擇將它們分組。

Fabric.js 支持全部主流的瀏覽器,具體的兼容狀況以下:

  • Firefox 2+
  • Safari 3+
  • Opera 9.64+
  • Chrome(全部版本)
  • IE10,IE11,Edge

使用示例

<!DOCTYPE html>
<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

fabric-demo.png

(圖片來源:https://github.com/fabricjs/f...

2.6 Resemble.js

Image analysis and comparison

https://github.com/rsmbl/Rese...

Resemble.js 使用 HTML Canvas 和 JavaScript 來實現圖片的分析和比較。兼容大於 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/Resemb...

resemble-demo.jpg

2.7 Pica

Resize image in browser with high quality and high speed

https://github.com/nodeca/pica

Pica 可用於在瀏覽器中調整圖像大小,沒有像素化而且至關快。它會自動選擇最佳的可用技術:webworkers,webassembly,createImageBitmap,純 JS。

藉助 Pica,你能夠實現如下功能:

  • 減少大圖像的上傳大小,節省上傳時間;
  • 在圖像處理上節省服務器資源;
  • 在瀏覽器中生成縮略圖。

使用示例

const pica = require('pica')();

// 調整畫布/圖片的大小
pica.resize(from, to, {
  unsharpAmount: 80,
  unsharpRadius: 0.6,
  unsharpThreshold: 2
})
.then(result => console.log('resize done!'));

// 調整大小並轉換爲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/...

pica-beauty-demo.jpg

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.im...

tui.image-editor 是使用 HTML5 Canvas 的全功能圖像編輯器。它易於使用,並提供強大的過濾器。同時它支持對圖像進行裁剪、翻轉、旋轉、繪圖、形狀、文本、遮罩和圖片過濾等操做。

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-imag...

tui-image-editor-demo.jpg

2.9 gif.js

JavaScript GIF encoding library

https://github.com/jnordberg/...

gif.js 是運行在瀏覽器端的 JavaScript GIF 編碼器。它使用類型化數組和 Web Worker 在後臺渲染每一幀,速度真的很快。該庫可工做在支持:Web Workers,File 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/gi...

gifjs-static-demo.jpg

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 的典型應用場景是將常見格式的大圖像轉換爲尺寸較小,對網絡友好的 JPEG,PNG 和 WebP 格式的圖像。因爲其內部使用 libvips ,使得調整圖像大小一般比使用 ImageMagick 和 GraphicsMagick 設置快 4-5 倍 。除了支持調整圖像大小以外,Sharp 還支持旋轉、提取、合成和伽馬校訂等功能。

Sharp 支持讀取 JPEG,PNG,WebP,TIFF,GIF 和 SVG 圖像。輸出圖像能夠是 JPEG,PNG,WebP 和 TIFF 格式,也能夠是未壓縮的原始像素數據。

使用示例

// 改變圖像尺寸
sharp(inputBuffer)
  .resize(320, 240)
  .toFile('output.webp', (err, info) => { ... });
       
// 旋轉輸入圖像並改變圖片尺寸                                         
sharp('input.jpg')
  .rotate()
  .resize(200)
  .toBuffer()
  .then( data => { ... })
  .catch( err => { ... });

在線示例

https://segmentfault.com/a/11...

該示例是來自阿寶哥 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"));

// 建立圓形SVG,用於實現頭像裁剪
const roundedCorners = new Buffer(
  '<svg><circle r="90" cx="90" cy="90"/></svg>'
);

// 設置SVG文本元素相關參數
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);
  // 用戶數據
  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];
  // 圖層疊加參數列表
  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、阿寶哥有話說

3.1 如何區分圖片的類型

計算機並非經過圖片的後綴名來區分不一樣的圖片類型,而是經過 「魔數」(Magic Number)來區分。 對於某一些類型的文件,起始的幾個字節內容都是固定的,跟據這幾個字節的內容就能夠判斷文件的類型。

常見圖片類型對應的魔數以下表所示:

文件類型 文件後綴 魔數
JPEG jpg/jpeg 0xFFD8FF
PNG png 0x89504E47
GIF gif 0x47494638(GIF8)
BMP bmp 0x424D

這裏咱們以阿寶哥的頭像(abao.png)爲例,驗證一下該圖片的類型是否正確:

abao-png-desc.jpg

在平常開發過程當中,若是遇到檢測圖片類型的場景,咱們能夠直接利用一些現成的第三方庫。好比,你想要判斷一張圖片是否爲 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 如何獲取圖片的尺寸

圖片的尺寸、位深度、色彩類型和壓縮算法都會存儲在文件的二進制數據中,咱們繼續以阿寶哥的頭像(abao.png)爲例,來了解一下實際的狀況:

528(十進制) => 0x0210

560(十進制)=> 0x0230

所以若是想要獲取圖片的尺寸,咱們就須要依據不一樣的圖片格式對圖片二進制數據進行解析。幸運的是,咱們不須要本身作這件事,image-size 這個 Node.js 庫已經幫咱們實現了獲取主流圖片類型文件尺寸的功能:

同步方式

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 如何預覽本地圖片

利用 HTML FileReader API,咱們也能夠方便的實現圖片本地預覽功能,具體代碼以下:

<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>

在完成本地圖片預覽以後,能夠直接把圖片對應的 Data URLs 數據提交到服務器。針對這種情形,服務端須要作一些相關處理,才能正常保存上傳的圖片,這裏以 Express 爲例,具體處理代碼以下:

const app = require('express')();

app.post('/upload', function(req, res){
    let imgData = req.body.imgData; // 獲取POST請求中的base64圖片數據
    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 如何實現圖片壓縮

在一些場合中,咱們但願在上傳本地圖片時,先對圖片進行必定的壓縮,而後再提交到服務器,從而減小傳輸的數據量。在前端要實現圖片壓縮,咱們能夠利用 Canvas 對象提供的 toDataURL() 方法,該方法接收 typeencoderOptions 兩個可選參數。

其中 type 表示圖片格式,默認爲 image/png。而 encoderOptions 用於表示圖片的質量,在指定圖片格式爲 image/jpegimage/webp 的狀況下,能夠從 0 到 1 的區間內選擇圖片的質量。若是超出取值範圍,將會使用默認值 0.92,其餘參數會被忽略。

下面咱們來看一下具體如何實現圖片壓縮:

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 格式的圖片數據,爲了進一步減小傳輸的數據量,咱們能夠把它轉換爲 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 < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeType });
}

在轉換完成後,咱們就能夠壓縮後的圖片對應的 Blob 對象封裝在 FormData 對象中,而後再經過 AJAX 提交到服務器上:

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 如何操做位圖像素數據

若是想要操做圖片像素數據,咱們能夠利用 CanvasRenderingContext2D 提供的 getImageData 來獲取圖片像素數據,其中 getImageData() 返回一個 ImageData 對象,用來描述 canvas 區域隱含的像素數據,這個區域經過矩形表示,起始點爲(sx, sy)、寬爲 sw、高爲 sh。其中 getImageData 方法的語法以下:

ctx.getImageData(sx, sy, sw, sh);

相應的參數說明以下:

  • sx:將要被提取的圖像數據矩形區域的左上角 x 座標。
  • sy:將要被提取的圖像數據矩形區域的左上角 y 座標。
  • sw:將要被提取的圖像數據矩形區域的寬度。
  • sh:將要被提取的圖像數據矩形區域的高度。

在獲取到圖片的像素數據以後,咱們就能夠對獲取的像素數據進行處理,好比進行灰度化或反色處理。當完成處理後,若要在頁面上顯示處理效果,則咱們須要利用 CanvasRenderingContext2D 提供的另外一個 API —— putImageData

該 API 是 Canvas 2D API 將數據從已有的 ImageData 對象繪製到位圖的方法。 若是提供了一個繪製過的矩形,則只繪製該矩形的像素。此方法不受畫布轉換矩陣的影響。putImageData 方法的語法以下:

void ctx.putImageData(imagedata, dx, dy);
void ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);

相應的參數說明以下:

  • imageData: ImageData ,包含像素值的數組對象。
  • dx:源圖像數據在目標畫布中的位置偏移量(x 軸方向的偏移量)。
  • dy:源圖像數據在目標畫布中的位置偏移量(y 軸方向的偏移量)。
  • dirtyX(可選):在源圖像數據中,矩形區域左上角的位置。默認是整個圖像數據的左上角(x 座標)。
  • dirtyY(可選):在源圖像數據中,矩形區域左上角的位置。默認是整個圖像數據的左上角(y 座標)。
  • dirtyWidth(可選):在源圖像數據中,矩形區域的寬度。默認是圖像數據的寬度。
  • dirtyHeight(可選):在源圖像數據中,矩形區域的高度。默認是圖像數據的高度。

介紹完相關的 API,下面咱們來舉一個實際例子:

<!DOCTYPE html>
<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 < data.length; 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 < data.length; 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>

須要注意的在調用 getImageData 方法獲取圖片像素數據時,你可能會遇到跨域問題,好比:

Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.

對於這個問題,你能夠閱讀 張鑫旭 大神 「解決canvas圖片getImageData,toDataURL跨域問題」 這一篇文章。

3.6 如何實現圖片隱寫

隱寫術是一門關於信息隱藏的技巧與科學,所謂信息隱藏指的是不讓除預期的接收者以外的任何人知曉信息的傳遞事件或者信息的內容。 隱寫術的英文叫作 Steganography,來源於特里特米烏斯的一本講述密碼學與隱寫術的著做 Steganographia,該書書名源於希臘語,意爲 「隱祕書寫」。

下圖是阿寶哥採用在線的圖片隱寫工具,將 「全棧修仙之路」 這 6 個字隱藏到原始的圖片中,而後使用對應的解密工具,解密出隱藏信息的結果:

steganography-demo.jpg

(在線圖片隱寫體驗地址:https://c.p2hp.com/yinxietu/

目前有多種方案能夠實現圖片隱寫,如下是幾種常見的方案:

  • 附加式的圖片隱寫;
  • 基於文件結構的圖片隱寫;
  • 基於 LSB 原理的圖片隱寫;
  • 基於 DCT 域的 JPG 圖片隱寫;
  • 數字水印的隱寫;
  • 圖片容差的隱寫。

篇幅有限,這裏咱們就不繼續展開,分別介紹每種方案,感興趣的小夥伴能夠閱讀 「隱寫術之圖片隱寫(一)」 這篇文章。

4、參考資源

5、推薦閱讀

相關文章
相關標籤/搜索