使用 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 相关文章推荐
一个很酷的拖动层的js类,兼容IE及Firefox
Jun 23 Javascript
jQuery 行级解析读取XML文件(附源码)
Oct 12 Javascript
关于document.cookie的使用javascript
Oct 29 Javascript
Jquery截取中文字符串的实现代码
Dec 22 Javascript
同域jQuery(跨)iframe操作DOM(示例代码)
Dec 13 Javascript
jquery对dom节点的操作【推荐】
Apr 15 Javascript
JS简单实现点击复制链接的方法
Aug 03 Javascript
javascript实现将数字转成千分位的方法小结【5种方式】
Dec 11 Javascript
jQuery插件HighCharts绘制简单2D柱状图效果示例【附demo源码】
Mar 21 jQuery
微信小程序 功能函数小结(手机号验证*、密码验证*、获取验证码*)
Dec 08 Javascript
vue2 v-model/v-text 中使用过滤器的方法示例
May 09 Javascript
详解jenkins自动化部署vue
May 14 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
PHPExcel读取Excel文件的实现代码
2011/12/06 PHP
php+mysqli实现将数据库中一张表信息打印到表格里的方法
2015/01/28 PHP
PHP实现的随机IP函数【国内IP段】
2016/07/20 PHP
php给数组赋值的实例方法
2019/09/26 PHP
ajaxControlToolkit AutoCompleteExtender的用法
2008/10/30 Javascript
有关JavaScript的10个怪癖和秘密分享
2011/08/28 Javascript
JavaScript代码复用模式实例分析
2012/12/02 Javascript
document.forms用法示例介绍
2014/06/26 Javascript
深入分析escape()、encodeURI()、encodeURIComponent()的区别及示例
2014/08/04 Javascript
BootStrap 附加导航组件
2016/07/22 Javascript
mui上拉加载更多下拉刷新数据的封装过程
2017/11/03 Javascript
JS实现求5的阶乘示例
2019/01/21 Javascript
JQuery常见节点操作实例分析
2019/05/15 jQuery
解决Vue.js应用回退或刷新界面时提示用户保存修改问题
2019/11/24 Javascript
微信小程序复选框实现多选一功能过程解析
2020/02/14 Javascript
TypeScript的安装、使用、自动编译的实现
2020/04/10 Javascript
js判断鼠标移入移出方向的方法
2020/06/24 Javascript
sqlalchemy对象转dict的示例
2014/04/22 Python
Python多线程编程(三):threading.Thread类的重要函数和方法
2015/04/05 Python
利用Python的Django框架中的ORM建立查询API
2015/04/20 Python
Python 专题五 列表基础知识(二维list排序、获取下标和处理txt文本实例)
2017/03/20 Python
python WindowsError的错误代码详解
2017/07/23 Python
Python实现列表删除重复元素的三种常用方法分析
2017/11/24 Python
python3.x 将byte转成字符串的方法
2018/07/17 Python
Django model序列化为json的方法示例
2018/10/16 Python
python 文件查找及内容匹配方法
2018/10/25 Python
python实现文本界面网络聊天室
2018/12/12 Python
Python hashlib模块实例使用详解
2019/12/24 Python
Pytorch如何切换 cpu和gpu的使用详解
2021/03/01 Python
html5 worker 实例(二) 图片变换效果
2013/06/24 HTML / CSS
总经理文秘岗位职责
2014/02/03 职场文书
2014年大学生预备党员思想汇报1000字
2014/09/13 职场文书
2015年乡镇人大工作总结
2015/04/22 职场文书
党员转正介绍人意见
2015/06/03 职场文书
2016新春团拜会致辞
2015/08/01 职场文书
MySQL运行报错:“Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggre”解决方法
2022/06/14 MySQL