溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么使用Node.js實(shí)現(xiàn)圖片的動(dòng)態(tài)裁切及算法

發(fā)布時(shí)間:2021-05-21 10:28:03 來(lái)源:億速云 閱讀:196 作者:小新 欄目:web開(kāi)發(fā)

小編給大家分享一下怎么使用Node.js實(shí)現(xiàn)圖片的動(dòng)態(tài)裁切及算法,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

背景&概覽

目前常見(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)景要少很多。

以上是“怎么使用Node.js實(shí)現(xiàn)圖片的動(dòng)態(tài)裁切及算法”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI