使用 Node.js 實(shí)現(xiàn)圖片的動(dòng)態(tài)裁切及算法實(shí)例代碼詳解
背景&概覽
目前常見(jiàn)的圖床服務(wù)都會(huì)有圖片動(dòng)態(tài)裁切的功能,主要的應(yīng)用場(chǎng)景用以為各種終端和業(yè)務(wù)形態(tài)輸出合適尺寸的圖片。
一張動(dòng)輒以 MB 為計(jì)量單位的原始大圖,通常不會(huì)只設(shè)置一下顯示尺寸就直接輸出到終端中,因?yàn)轶w積太大加載體驗(yàn)會(huì)很差,除了影響加載速度還會(huì)增加終端設(shè)備的內(nèi)存占用。所以要想在各種終端下都能保證圖片質(zhì)量的同時(shí)又確保輸出合適的尺寸,那么此時(shí)就需要根據(jù)圖片 URL 來(lái)對(duì)原始圖片進(jìn)行裁切,然后動(dòng)態(tài)生成并輸出一張新的圖片。
URL 的設(shè)計(jì)
圖片 URL 需要包含圖片 id、尺寸、質(zhì)量等信息。有兩種類型的圖片 URL,分別是原圖 URL 和帶動(dòng)態(tài)裁切信息的 URL。
// 原圖 URL http://example.com/$imgId // 帶裁切信息的圖片 URL http://example.com/$cropType/$width_$height_$quality/$imgId
來(lái)分析一下上面 URL 中的變量:
- $imgId
- $cropType
- $width
- $height
- $quality
那么一張圖片 id 為 4b2d4edcc1f82452 的原圖 URL 應(yīng)該是:
http://example.com/4b2d4edcc1f82452.jpg
如果想要一張?jiān)搱D 800×600 的版本,裁切的 URL 大致是下面這樣的:
http://example.com/es/800_600_/4b2d4edcc1f82452.jpg
裁切算法
該來(lái)說(shuō)說(shuō)以上 URL 背后的算法了。在 Node.js 中可以使用著名的圖片裁切庫(kù) GM ,該庫(kù)是基于 imagemagick 和 graphicsmagick 底層庫(kù)的封裝。
最常見(jiàn)的裁切算法是等比例裁切,等比裁切的算法需要至少給出裁切目標(biāo)圖片的寬度和高度的其中一個(gè),如果圖片限寬就給出寬度,限高就給出高度,如果兩個(gè)參數(shù)都有,就需要確保裁切的目標(biāo)寬高相對(duì)于原始的寬高是按比例計(jì)算的,否則裁切的結(jié)果就會(huì)出現(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')); } // 解析裁切的目標(biāo)寬高 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); }); }); };
說(shuō)說(shuō)幾個(gè)重要的 API:
quality 設(shè)置圖片的質(zhì)量,GM 圖片質(zhì)量范圍是 0-100,默認(rèn)的質(zhì)量是 75。
interlace 用于設(shè)置圖片在顯示器上加載時(shí)的顯示方式,當(dāng)然顯示方式本身還要受圖片本身的影響。
unsharp 用來(lái)設(shè)置圖片的銳度,將一張大圖縮放成一張小圖時(shí),會(huì)損失很多像素,需要適當(dāng)?shù)脑黾訄D片銳度來(lái)保證圖片的質(zhì)量。關(guān)于 unsharp 的使用,詳見(jiàn) Using ImageMagick to make sharp web-sized photographs 。
等比例裁切嚴(yán)格來(lái)說(shuō)實(shí)際上還只是對(duì)圖片進(jìn)行縮放,并未動(dòng)用圖片裁切的 API。
還有一種比較常見(jiàn)的裁切方式,會(huì)先將圖片等比例縮放后再?gòu)闹行牟们?,裁切出?lái)的圖片是一個(gè)正方形,這樣能盡可能保證圖片的內(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ì)算出等比縮放的尺寸,然后再根據(jù)此尺寸計(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 就是對(duì)圖片進(jìn)行裁切。當(dāng)然除了中心裁切,還能延伸出頂部裁切,底部裁切等,相對(duì)來(lái)說(shuō)使用場(chǎng)景要少很多。
結(jié)語(yǔ)
在服務(wù)的實(shí)際應(yīng)用中,還會(huì)做一些優(yōu)化,比如對(duì)服務(wù)的接口做一些安全限制,確保該接口不會(huì)被刷,裁切本身是比較消耗資源的操作。由于裁切操作比較耗資源,那么相同的尺寸應(yīng)該保證只有一次裁切操作,這樣只有第一次請(qǐng)求裁切圖片才會(huì)真正有裁切操作,后續(xù)的訪問(wèn)就直接讀取原來(lái)就裁切好的實(shí)體文件即可。
以上所述是小編給大家介紹的使用 Node.js 實(shí)現(xiàn)圖片的動(dòng)態(tài)裁切及算法實(shí)例代碼詳解,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
NodeJS 實(shí)現(xiàn)多語(yǔ)言的示例代碼
這篇文章主要介紹了NodeJS 實(shí)現(xiàn)多語(yǔ)言的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09使用NODE.JS創(chuàng)建一個(gè)WEBSERVER(服務(wù)器)的步驟
在 node.js 中創(chuàng)建一個(gè)服務(wù)器非常簡(jiǎn)單,只需要使用 node.js 為我們提供的 http 模塊及相關(guān) API 即可創(chuàng)建一個(gè)麻雀雖小但五臟俱全的web 服務(wù)器,相比 Java/Python/Ruby 搭建web服務(wù)器的過(guò)程簡(jiǎn)單的很。本文簡(jiǎn)單的講解下實(shí)現(xiàn)步驟2021-06-06AngularJS + Node.js + MongoDB開(kāi)發(fā)的基于高德地圖位置的通訊錄
這篇文章主要介紹了AngularJS + Node.js + MongoDB開(kāi)發(fā)的基于高德地圖位置的通訊錄,需要的朋友可以參考下2015-01-01基于node.js制作簡(jiǎn)單爬蟲(chóng)教程
這篇文章主要為大家詳細(xì)介紹了基于node.js制作簡(jiǎn)單爬蟲(chóng)的教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06React和Node.js快速上傳進(jìn)度條功能實(shí)現(xiàn)
這篇文章主要為大家介紹了React和Node.js快速上傳進(jìn)度條功能實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03node操作mysql數(shù)據(jù)庫(kù)實(shí)例詳解
這篇文章主要介紹了node操作mysql數(shù)據(jù)庫(kù),結(jié)合實(shí)例形式較為詳細(xì)的分析了node操作數(shù)據(jù)庫(kù)的連接、增刪改查、事務(wù)處理及錯(cuò)誤處理相關(guān)操作技巧,需要的朋友可以參考下2017-03-03nodejs連接mongodb數(shù)據(jù)庫(kù)實(shí)現(xiàn)增刪改查
本篇文章主要結(jié)合了nodejs操作mongodb數(shù)據(jù)庫(kù)實(shí)現(xiàn)增刪改查,包括對(duì)數(shù)據(jù)庫(kù)的增加,刪除,查找和更新,有興趣的可以了解一下。2016-12-12NodeJS模塊與ES6模塊系統(tǒng)語(yǔ)法及注意點(diǎn)詳解
這篇文章主要給大家介紹了關(guān)于NodeJS模塊與ES6模塊系統(tǒng)語(yǔ)法及注意點(diǎn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01