使用 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 相关文章推荐
DWR Ext 加载数据
Mar 22 Javascript
读JavaScript DOM编程艺术笔记
Nov 15 Javascript
js获取键盘按键响应事件(兼容各浏览器)
May 16 Javascript
js类型转换与引用类型详解(Boolean_Number_String)
Mar 07 Javascript
JavaScript控制各种浏览器全屏模式的方法、属性和事件介绍
Apr 03 Javascript
Jquery+ajax+JAVA(servlet)实现下拉菜单异步取值
Mar 23 Javascript
jQuery的Each比JS原生for循环性能慢很多的原因
Jul 05 Javascript
webpack入门必知必会
Jan 16 Javascript
React Native实现地址挑选器功能
Oct 24 Javascript
解决vue 路由变化页面数据不刷新的问题
Mar 13 Javascript
Vue中通过属性绑定为元素绑定style行内样式的实例代码
Apr 30 Javascript
Vue全家桶入门基础教程
May 14 Vue.js
使用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
牡丹941资料
2021/03/01 无线电
PHP Pear 安装及使用
2009/03/19 PHP
php下过滤html代码的函数 提高程序安全性
2010/03/02 PHP
PHP查询网站的PR值
2013/10/30 PHP
php-redis中的sort排序函数总结
2015/07/08 PHP
用js判断浏览器是否是IE的比较好的办法
2007/05/08 Javascript
Jquery下判断Id是否存在的代码
2011/01/06 Javascript
nodeType属性返回被选节点的节点类型介绍
2013/11/22 Javascript
jQuery多个input求和的实现方法
2015/02/12 Javascript
JQuery实现的图文自动轮播效果插件
2015/06/19 Javascript
js自定义回调函数
2015/12/13 Javascript
[原创]JQuery 在表单提交之前修改 提交的值
2016/04/14 Javascript
js判断复选框是否选中及选中个数的实现代码
2016/05/30 Javascript
JS中input表单隐藏域及其使用方法
2017/02/13 Javascript
JS实现匀加速与匀减速运动的方法示例
2017/09/04 Javascript
JS实现分页浏览横向图片(类轮播)实例代码
2017/11/06 Javascript
微信小程序如何获取用户手机号
2018/01/26 Javascript
Javascript的console['']常用输入方法汇总
2018/04/26 Javascript
vue实现点击关注后及时更新列表功能
2018/06/26 Javascript
AngularJS实现动态切换样式的方法分析
2018/06/26 Javascript
在Python的Django框架中更新数据库数据的方法
2015/07/17 Python
python使用itchat实现手机控制电脑
2018/02/22 Python
Python实现输入二叉树的先序和中序遍历,再输出后序遍历操作示例
2018/07/27 Python
python 调用有道api接口的方法
2019/01/03 Python
PyTorch: 梯度下降及反向传播的实例详解
2019/08/20 Python
Python单元测试与测试用例简析
2019/11/09 Python
python实现自动化报表功能(Oracle/plsql/Excel/多线程)
2019/12/02 Python
pandas实现DataFrame显示最大行列,不省略显示实例
2019/12/26 Python
python中count函数简单的实例讲解
2020/02/06 Python
让IE支持HTML5的方法
2012/12/11 HTML / CSS
For Art’s Sake官网:手工制作的奢华眼镜
2018/12/15 全球购物
瑞士设计师家具和家居饰品网上商店:Bruno Wickart
2019/03/18 全球购物
JAVA和C++区别都有哪些
2015/03/30 面试题
公司总经理工作职责管理办法
2014/02/28 职场文书
幼儿教师师德演讲稿
2014/05/06 职场文书
高中教师考核方案
2014/05/18 职场文书