使用 Node.js 實現(xiàn)圖片的動態(tài)裁切及算法實例代碼詳解
背景&概覽
目前常見的圖床服務都會有圖片動態(tài)裁切的功能,主要的應用場景用以為各種終端和業(yè)務形態(tài)輸出合適尺寸的圖片。
一張動輒以 MB 為計量單位的原始大圖,通常不會只設置一下顯示尺寸就直接輸出到終端中,因為體積太大加載體驗會很差,除了影響加載速度還會增加終端設備的內(nèi)存占用。所以要想在各種終端下都能保證圖片質(zhì)量的同時又確保輸出合適的尺寸,那么此時就需要根據(jù)圖片 URL 來對原始圖片進行裁切,然后動態(tài)生成并輸出一張新的圖片。
URL 的設計
圖片 URL 需要包含圖片 id、尺寸、質(zhì)量等信息。有兩種類型的圖片 URL,分別是原圖 URL 和帶動態(tài)裁切信息的 URL。
// 原圖 URL http://example.com/$imgId // 帶裁切信息的圖片 URL http://example.com/$cropType/$width_$height_$quality/$imgId
來分析一下上面 URL 中的變量:
- $imgId
- $cropType
- $width
- $height
- $quality
那么一張圖片 id 為 4b2d4edcc1f82452 的原圖 URL 應該是:
http://example.com/4b2d4edcc1f82452.jpg
如果想要一張該圖 800×600 的版本,裁切的 URL 大致是下面這樣的:
http://example.com/es/800_600_/4b2d4edcc1f82452.jpg
裁切算法
該來說說以上 URL 背后的算法了。在 Node.js 中可以使用著名的圖片裁切庫 GM ,該庫是基于 imagemagick 和 graphicsmagick 底層庫的封裝。
最常見的裁切算法是等比例裁切,等比裁切的算法需要至少給出裁切目標圖片的寬度和高度的其中一個,如果圖片限寬就給出寬度,限高就給出高度,如果兩個參數(shù)都有,就需要確保裁切的目標寬高相對于原始的寬高是按比例計算的,否則裁切的結果就會出現(xiàn)拉伸。
var gm = require('gm'); // 裁切的最小尺寸 var minSize = 48; var defaultQuality = 90; /** * 等比例縮放 equal scaling * @param { String } 原文件路徑 * @param { String } 新文件路徑 * @param { String } 縮放規(guī)則 * @return { promise } */ var es = function(src, dest, rules) { return new Promise(function(resolve, reject) { // 900_600_90 => 寬度900/高度600/品質(zhì)90 rules = rules.split('_'); if (rules.length !== 3) { return reject(new Error('Resize rules invalid')); } // 解析裁切的目標寬高 let resizeWidth = parseInt(rules[0]); let resizeHeight = parseInt(rules[1]); let quality = parseInt(rules[2]) || defaultQuality; const readStream = fs.createReadStream(src); const writeStream = fs.createWriteStream(dest); gm(readStream) .size({ bufferStream: true }, function(err, size) { if (err) { return reject(err); } const origWidth = size.width; const origHeight = size.height; let resizeResult; // 縮放的寬度和高度做最大最小值限制 if (resizeWidth) { if (resizeWidth > origWidth * 1.5) { resizeWidth = Math.floor(origWidth * 1.5); } else if (resizeWidth < minSize) { resizeWidth = minSize; } } if (resizeHeight) { if (resizeHeight > origHeight * 1.5) { resizeHeight = Math.floor(origHeight * 1.5); } else if (resizeHeight < minSize) { resizeHeight = minSize; } } resizeResult = this.resize(resizeWidth, resizeHeight); resizeResult .quality(quality) .interlace('line') // 使用逐行掃描方式 .unsharp(2, 0.5, 0.5, 0) .stream() .on('end', resolve) .pipe(writeStream); }); }); };
說說幾個重要的 API:
quality 設置圖片的質(zhì)量,GM 圖片質(zhì)量范圍是 0-100,默認的質(zhì)量是 75。
interlace 用于設置圖片在顯示器上加載時的顯示方式,當然顯示方式本身還要受圖片本身的影響。
unsharp 用來設置圖片的銳度,將一張大圖縮放成一張小圖時,會損失很多像素,需要適當?shù)脑黾訄D片銳度來保證圖片的質(zhì)量。關于 unsharp 的使用,詳見 Using ImageMagick to make sharp web-sized photographs 。
等比例裁切嚴格來說實際上還只是對圖片進行縮放,并未動用圖片裁切的 API。
還有一種比較常見的裁切方式,會先將圖片等比例縮放后再從中心裁切,裁切出來的圖片是一個正方形,這樣能盡可能保證圖片的內(nèi)容。
/* * 等比例縮放后從中心裁切 equal scaling crop center(正方形裁切) * @param { String } 原文件路徑 * @param { String } 新文件路徑 * @param { String } 縮放規(guī)則 * @return { promise } */ var escc = function(src, dest, rules) { return new Promise(function(resolve, reject) { // 600_90 => 寬度600/高度600/品質(zhì)90 rules = rules.split('_'); if (rules.length !== 2) { return reject(new Error('Resize rules invalid')); } let cropSize = parseInt(rules[0]); let quality = parseInt(rules[1]) || defaultQuality; const readStream = fs.createReadStream(src); const writeStream = fs.createWriteStream(dest); if (!cropSize) { reject(new Error('Crop params invalid')); return; } gm(readStream) .size({ bufferStream: true }, function(err, size) { if (err) { reject(err); return; } const origWidth = size.width; const origHeight = size.height; let cropX = 0; let cropY = 0; let resizeWidth; let resizeHeight; let resizeResult; // 裁切的寬度和高度做最大最小值限制 if (cropSize > origWidth) { cropSize = origWidth; } else if (cropSize > origHeight) { cropSize = origHeight; } else if (cropSize < minSize) { cropSize = minSize; } // 先計算出等比縮放的尺寸,然后再根據(jù)此尺寸計算出裁切位置 if (origWidth > origHeight) { resizeWidth = cropSize / origHeight * origWidth; resizeHeight = cropSize; cropX = Math.floor((resizeWidth - cropSize) / 2); cropY = 0; } else { resizeHeight = cropSize / origWidth * origHeight; resizeWidth = cropSize; cropX = 0; cropY = Math.floor((resizeHeight - cropSize) / 2); } resizeResult = this.resize(resizeWidth, resizeHeight); resizeResult .quality(quality) .interlace('line') // 使用逐行掃描方式 .crop(cropSize, cropSize, cropX, cropY) .unsharp(2, 0.5, 0.5, 0) .stream() .on('end', resolve) .pipe(writeStream); }); }); };
上面的 crop 就是對圖片進行裁切。當然除了中心裁切,還能延伸出頂部裁切,底部裁切等,相對來說使用場景要少很多。
結語
在服務的實際應用中,還會做一些優(yōu)化,比如對服務的接口做一些安全限制,確保該接口不會被刷,裁切本身是比較消耗資源的操作。由于裁切操作比較耗資源,那么相同的尺寸應該保證只有一次裁切操作,這樣只有第一次請求裁切圖片才會真正有裁切操作,后續(xù)的訪問就直接讀取原來就裁切好的實體文件即可。
以上所述是小編給大家介紹的使用 Node.js 實現(xiàn)圖片的動態(tài)裁切及算法實例代碼詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關文章
使用NODE.JS創(chuàng)建一個WEBSERVER(服務器)的步驟
在 node.js 中創(chuàng)建一個服務器非常簡單,只需要使用 node.js 為我們提供的 http 模塊及相關 API 即可創(chuàng)建一個麻雀雖小但五臟俱全的web 服務器,相比 Java/Python/Ruby 搭建web服務器的過程簡單的很。本文簡單的講解下實現(xiàn)步驟2021-06-06AngularJS + Node.js + MongoDB開發(fā)的基于高德地圖位置的通訊錄
這篇文章主要介紹了AngularJS + Node.js + MongoDB開發(fā)的基于高德地圖位置的通訊錄,需要的朋友可以參考下2015-01-01React和Node.js快速上傳進度條功能實現(xiàn)
這篇文章主要為大家介紹了React和Node.js快速上傳進度條功能實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03nodejs連接mongodb數(shù)據(jù)庫實現(xiàn)增刪改查
本篇文章主要結合了nodejs操作mongodb數(shù)據(jù)庫實現(xiàn)增刪改查,包括對數(shù)據(jù)庫的增加,刪除,查找和更新,有興趣的可以了解一下。2016-12-12NodeJS模塊與ES6模塊系統(tǒng)語法及注意點詳解
這篇文章主要給大家介紹了關于NodeJS模塊與ES6模塊系統(tǒng)語法及注意點的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-01-01