使用 Node.js 实现图片的动态裁切及算法实例代码详解


Posted in Javascript onSeptember 29, 2018

背景&概览

目前常见的图床服务都会有图片动态裁切的功能,主要的应用场景用以为各种终端和业务形态输出合适尺寸的图片。

一张动辄以 MB 为计量单位的原始大图,通常不会只设置一下显示尺寸就直接输出到终端中,因为体积太大加载体验会很差,除了影响加载速度还会增加终端设备的内存占用。所以要想在各种终端下都能保证图片质量的同时又确保输出合适的尺寸,那么此时就需要根据图片 URL 来对原始图片进行裁切,然后动态生成并输出一张新的图片。

URL 的设计

图片 URL 需要包含图片 id、尺寸、质量等信息。有两种类型的图片 URL,分别是原图 URL 和带动态裁切信息的 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 底层库的封装。

最常见的裁切算法是等比例裁切,等比裁切的算法需要至少给出裁切目标图片的宽度和高度的其中一个,如果图片限宽就给出宽度,限高就给出高度,如果两个参数都有,就需要确保裁切的目标宽高相对于原始的宽高是按比例计算的,否则裁切的结果就会出现拉伸。

var gm = require('gm');
// 裁切的最小尺寸
var minSize = 48;
var defaultQuality = 90;
/**
 * 等比例缩放 equal scaling
 * @param { String } 原文件路径
 * @param { String } 新文件路径
 * @param { String } 缩放规则
 * @return { promise }
 */
var es = function(src, dest, rules) {
  return new Promise(function(resolve, reject) {
    // 900_600_90 => 宽度900/高度600/品质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 设置图片的质量,GM 图片质量范围是 0-100,默认的质量是 75。
interlace 用于设置图片在显示器上加载时的显示方式,当然显示方式本身还要受图片本身的影响。
unsharp 用来设置图片的锐度,将一张大图缩放成一张小图时,会损失很多像素,需要适当的增加图片锐度来保证图片的质量。关于 unsharp 的使用,详见 Using ImageMagick to make sharp web-sized photographs 。
等比例裁切严格来说实际上还只是对图片进行缩放,并未动用图片裁切的 API。

还有一种比较常见的裁切方式,会先将图片等比例缩放后再从中心裁切,裁切出来的图片是一个正方形,这样能尽可能保证图片的内容。

/*
 * 等比例缩放后从中心裁切 equal scaling crop center(正方形裁切)
 * @param { String } 原文件路径
 * @param { String } 新文件路径
 * @param { String } 缩放规则
 * @return { promise }
 */
var escc = function(src, dest, rules) {
  return new Promise(function(resolve, reject) {
  // 600_90 => 宽度600/高度600/品质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;
        }
        // 先计算出等比缩放的尺寸,然后再根据此尺寸计算出裁切位置
        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 就是对图片进行裁切。当然除了中心裁切,还能延伸出顶部裁切,底部裁切等,相对来说使用场景要少很多。

结语

在服务的实际应用中,还会做一些优化,比如对服务的接口做一些安全限制,确保该接口不会被刷,裁切本身是比较消耗资源的操作。由于裁切操作比较耗资源,那么相同的尺寸应该保证只有一次裁切操作,这样只有第一次请求裁切图片才会真正有裁切操作,后续的访问就直接读取原来就裁切好的实体文件即可。

以上所述是小编给大家介绍的使用 Node.js 实现图片的动态裁切及算法实例代码详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
理解Javascript_12_执行模型浅析
Oct 18 Javascript
jQuery.extend 函数详解
Feb 03 Javascript
javascript模块化是什么及其优缺点介绍
Sep 02 Javascript
jQuery动态显示和隐藏datagrid中的某一列的方法
Dec 11 Javascript
window.returnValue使用方法示例介绍
Jul 03 Javascript
使用不同的方法结合/合并两个JS数组
Sep 18 Javascript
JavaScript中的依赖注入详解
Mar 18 Javascript
AngularJS中的按需加载ocLazyLoad示例
Jan 11 Javascript
Vue学习笔记之表单输入控件绑定
Sep 05 Javascript
vue 注册组件的使用详解
May 05 Javascript
微信小程序在地图选择地址并返回经纬度简单示例
Dec 03 Javascript
React组件对子组件children进行加强的方法
Jun 23 Javascript
使用electron将vue-cli项目打包成exe的方法
Sep 29 #Javascript
脚手架vue-cli工程webpack的作用和特点
Sep 29 #Javascript
基于vue和react的spa进行按需加载的实现方法
Sep 29 #Javascript
使用Vuex解决Vue中的身份验证问题
Sep 28 #Javascript
js限制输入框只能输入数字(onkeyup触发)
Sep 28 #Javascript
js限制input只能输入有效的数字(第一个不能是小数点)
Sep 28 #Javascript
js实现点击展开隐藏效果(实例代码)
Sep 28 #Javascript
You might like
mysql 中InnoDB和MyISAM的区别分析小结
2008/04/15 PHP
php中fgetcsv()函数用法实例
2014/11/28 PHP
PHP中调用C/C++制作的动态链接库的教程
2016/03/10 PHP
Yii2使用dropdownlist实现地区三级联动功能的方法
2016/07/18 PHP
Yii2简单实现多语言配置的方法
2016/07/23 PHP
php封装的验证码类分享
2017/02/26 PHP
javascript获得CheckBoxList选中的数量
2009/10/27 Javascript
可恶的ie8提示缺少id未定义
2014/03/20 Javascript
Javascript中For In语句用法实例
2015/05/14 Javascript
javascript字符串循环匹配实例分析
2015/07/17 Javascript
jQuery实现页面顶部显示的进度条效果完整实例
2015/12/09 Javascript
BootStrap的弹出框(Popover)支持鼠标移到弹出层上弹窗层不隐藏的原因及解决办法
2016/04/03 Javascript
JQuery 的跨域方法推荐_可跨任何网站
2016/05/18 Javascript
Bootstrap框架安装使用详解
2017/01/21 Javascript
使用JavaScriptCore实现OC和JS交互详解
2017/03/28 Javascript
整理关于Bootstrap警示框的慕课笔记
2017/03/29 Javascript
Vue实现简单分页器
2018/12/29 Javascript
javascript网页随机点名实现过程解析
2019/10/15 Javascript
[42:25]2018DOTA2亚洲邀请赛 4.5 淘汰赛 LGD vs Liquid 第三场
2018/04/06 DOTA
Python使用迭代器打印螺旋矩阵的思路及代码示例
2016/07/02 Python
pandas中apply和transform方法的性能比较及区别介绍
2018/10/30 Python
利用Python查看微信共同好友功能的实现代码
2019/04/24 Python
python文件转为exe文件的方法及用法详解
2019/07/08 Python
详解Windows下PyCharm安装Numpy包及无法安装问题解决方案
2020/06/18 Python
浅析Python __name__ 是什么
2020/07/07 Python
使用HTML5做个画图板的方法介绍
2013/05/03 HTML / CSS
全球独特生活方式产品和礼品购物网站:AHAlife
2018/09/18 全球购物
党员培训思想汇报
2014/01/07 职场文书
采购类个人求职的自我评价
2014/02/18 职场文书
餐厅感恩节活动策划方案
2014/10/11 职场文书
2014年超市员工工作总结
2014/11/18 职场文书
2014年体检中心工作总结
2014/12/23 职场文书
个性发展自我评价2015
2015/03/09 职场文书
HTML+CSS+JS实现图片的瀑布流布局的示例代码
2021/04/22 HTML / CSS
浅谈Redis变慢的原因及排查方法
2022/06/21 Redis
MySQL数据库配置信息查看与修改方法详解
2022/06/25 MySQL