node实现批量修改图片尺寸

更新日期: 2020-06-05阅读: 2.3k标签: node

前言

大家在工作中肯定有没有遇到过图片尺寸和我们要求的尺寸不一致的情况吧?通常我们会在网上找一下找在线的或者下载一个小工具,再或者通过 ps 的批处理解决。但是,作为 程序猿 ,当然还是通过代码来解决这种小问题啦。所以,闲话不多说啦,开始写我们的代码啦~~


简单的搭建一下

新建一个 canvas-image-resize 目录

初始化一个 node 项目工程

npm init -y

安装依赖,这里主要用到了三个依赖,分别是 处理图片 、 批量处理文件 、 压缩成zip文件

npm i canvas glob archiver -S

没错,这里我们又用到了 canvas 这个库,惊不惊喜,意不意外 :joy:


简单的使用一下

同样,有了前面我们使用 canvas 的经验,书写这个代码应该问题也不大,主要是对 api 的熟练问题

查看文档我们不难发现, drawImage 的第四和第五个参数就是设置图片的宽高,知道这个之后,我们书写代码就简单不少了

drawImage(image: Canvas|Image, dx: number, dy: number, dw: number, dh: number): void

所以,我们的代码大概如下,

// 创建写入流
const { createWriteStream } = require("fs");
// 获取文件名
const { basename } = require("path");
// 压缩文件
const archiver = require("archiver");
// 导入canvas库,用于裁剪图片
const { createCanvas, loadImage } = require("canvas");
// 批量获取路径
const glob = require("glob");
!(async () => {
  const paths = glob.sync("./images/*");
  // 压缩成zip
  const archive = archiver("zip", {
    zlib: { level: 9 }, // Sets the compression level.
  });
  // 输出到当前文件夹下的 image-resize.zip
  const output = createWriteStream(__dirname + "/image-resize.zip");
  archive.pipe(output);
  for (let i = 0; i < paths.length; i++) {
    const path = paths[i];
    const image = await loadImage(path);
    const { width, height } = image;
    const options = [width, height].map((item) => item / 2);
    const canvas = createCanvas(...options);
    const ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, ...options);
    archive.append(canvas.toBuffer(), { name: `${basename(path)}` });
  }
  archive.finalize();
})();

从上面代码可以看出,这里我只是对宽高进行了缩放一倍,没有做更多的配置,为了代码的健壮性,我们修改下我们的 options ,使得整个程序可以自定义宽高、可以根据宽度进行缩放、根据高度进行缩放

定义一下我们可配置的参数,基本配置是这样的:

module.exports = {
  // 自定义宽度,传一个根据宽度等比缩放
  width: "",
  // 自定义高度,传一个根据高度等比缩放
  height: "",
  // 根据宽度等比缩放,优先级更高
  isWidth: false,
  // 根据高度等比缩放
  isHeight: false,
  // 宽高整体缩放
  scale: 1,
};

ps:因为我们暂时没有图形界面,所以就定义一个 config.js 来模拟我们的插件啦

所以,在当前目录下,新建一个 config.js ,书写上我们那些配置,然后在 app.js 导入下,基本代码就变成了如下:

// ....
// 导入配置文件(用户传过来的配置)
const config = require("./config");
// 根据配置获取宽高
function getOptions(options, config) {
  // 书写配置相关的代码,默认缩放两倍
  return options.map((item) => item / 2);
}
!(async () => {
  //  ....
  for (let i = 0; i < paths.length; i++) {
    const path = paths[i];
    const image = await loadImage(path);
    const { width, height } = image;
    const options = getOptions({ width, height }, config);
    const canvas = createCanvas(...options);
    const ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, ...options);
    archive.append(canvas.toBuffer(), { name: `${basename(path)}` });
  }
  //  ....
})();

然后根据我们的配置文件来写逻辑的话,大概会出现如下逻辑:

// 根据配置获取宽高
function getOptions(options, config) {
  const [sourceWidth, sourceHeight] = options;
  const { width, height, isWidth, isHeight, scale } = config;
  if (width === 0 || height === 0) return [0, 0];
  if (width && height) {
    if (isWidth) {
      return [width, (sourceHeight * width * scale) / sourceWidth];
    }
    if (isHeight) {
      return [(sourceWidth * height * scale) / sourceHeight, height];
    }
    return [width / scale, height / scale];
  }
  if (width && !height) {
    return [width, (sourceHeight * width * scale) / sourceWidth];
  }
  if (height && !width) {
    return [(sourceWidth * height * scale) / sourceHeight, height];
  }
  return options.map((item) => item / scale);
}

发现了吗?是不是感觉很乱?就算我们把一些公有部分提取出来改写如下:

// 根据配置获取宽高
function getOptions(options, config) {
  const [sourceWidth, sourceHeight] = options;
  const { width, height, isWidth, isHeight, scale } = config;
  if (width === 0 || height === 0) return [0, 0];
  const widthOfOptions = [
    width * scale,
    (sourceHeight * width * scale) / sourceWidth,
  ];
  const heightOfOptions = [
    (sourceWidth * height * scale) / sourceHeight,
    height * scale,
  ];
  if (width && height) {
    if (isWidth) {
      return widthOfOptions;
    }
    if (isHeight) {
      return heightOfOptions;
    }
    return [width / scale, height / scale];
  }
  if (width && !height) {
    return widthOfOptions;
  }
  if (height && !width) {
    return heightOfOptions;
  }
  return options.map((item) => item / scale);
}

其实就算经过我们这么优化,其实看起来也不是特别优雅,不知道大家是否还记得我之前的一篇文章 从零搭建 Window 前端开发环境 ,这里说过,我们可以使用 使用 Map 代替 if/else ,让我们的代码变得更优雅,可读性也更好。所以,接下来我们就通过 Map 来改写我们的代码吧

ps: 如果判断简单,其实用 {} 对象也可以,这里只是用 Map 做个延申


思考一下,为什么用 Map 更好呢?

说到这个,就不得不说 Map 对象和 Object 的区别了,他两有不少语法上的区别,比如 Map 获取值需要 get(key) ,设置值需要 set(key,value) ,但是这些区别不在我们讨论的范围内,我们说说他两最主要也是最重要的区别:

一个对象的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。

Map 自身有 size 属性,可以自己维护自己的 size,而对象的键值对个数只能手动确认。


优化代码

知道了他两的区别后,我们就可以边写代码啦~~刚刚说到, Map 的 key 可是是任意值,所以我们就可以使用正则类型( RegExp )来作为我们的 key 了,而正是因为有了正则,那么我们的判断就有了无限可能,可以适应各种情况。

思考一下怎么通过正则来实现我们的代码呢?

首先我们可以先观察下我们之前 if/else 这个版本的代码,最先判断的是不是有没有宽高,即宽高是否为 0,所以我们就可以通过这个条件把我们的判断改为布尔值,因为 js 是弱类型的,所以我们就可以用 0 或者 1 来表示了,又因为这里存在不传则根据传的值缩放的情况,所以我们需要额外判断当他为 空字符串 时取 01 之外的数字,这里我取的是 2 。

这里可能有点绕口,我举两个例子大家可能就就懂了,假如我们传入的数据为默认数据:

module.exports = {
  // 自定义宽度,传一个根据宽度等比缩放
  width: "",
  // 自定义高度,传一个根据高度等比缩放
  height: "",
  // 根据宽度等比缩放,优先级更高
  isWidth: false,
  // 根据高度等比缩放
  isHeight: false,
  // 宽高整体缩放
  scale: 1,
};

那么得出的字符串就是 22001 ,假设我们传入了宽度,即数据:

module.exports = {
  // 自定义宽度,传一个根据宽度等比缩放
  width: 1920,
  // 自定义高度,传一个根据高度等比缩放
  height: "",
  // 根据宽度等比缩放,优先级更高
  isWidth: false,
  // 根据高度等比缩放
  isHeight: false,
  // 宽高整体缩放
  scale: 1,
};

那么得出的字符串就是 12001 ,看到这里大家应该懂了吧?所以我们只需要判断 config 这个配置的 value 值来生成我们的字符串即可,即得出如下代码

// 获取config字符串,即传入了就是true,即1,没传就是0,为空字符串就是2
function getConfigStr(config) {
  return Object.values(config).map((el) => (el === "" ? "2" : Number(!!el)));
}

ps:如果不懂,请评论说出来,我看到会第一时间回复的。。。

正式编写优化后的代码

通过上面的思考,我们基本分析出了我们的代码需要怎么写,如何写,我想大家应该很容易就能书写出来了,这里还是贴一下我的(仅供参考):

// 获取config字符串
function getConfigStr(config) {
  return Object.values(config).map((el) => (el === "" ? "2" : Number(!!el)));
}
// 根据配置获取宽高
function getOptions(options, config) {
  const [sourceWidth, sourceHeight] = options;
  const { width, height, scale } = config;
  const widthOfOptions = [
    width * scale,
    (sourceHeight * width * scale) / sourceWidth,
  ];
  const heightOfOptions = [
    (sourceWidth * height * scale) / sourceHeight,
    height * scale,
  ];
  const configStr = getConfigStr(config);
  const map = new Map([
    [/^0|^\d0/, [0, 0]],
    [/^1\d1|^1[0|2]0/, widthOfOptions],
    [/^\d101|^210/, heightOfOptions],
    [/^1100/, [width / scale, height / scale]],
    [/^2{2}\d{2}1/, options.map((item) => item / scale)],
  ]);
  return [...map].find(([key]) => key.test(configStr.join("")))[1] || options;
}

ps: 这里使用了正则,如果有对正则不太了解的,建议可以去看下正则,因为正则对字符串的处理有着极大的意义,以极大程度上方便了我们的开发

也许你看到这里,你就会像,你这里写的不是比以前更复杂了吗?还用了这些看不太懂的 正则 ,可读性就更差了。。。

但是其实我这里只是想引申出 使用 Map 代替 if/else 这个思想(思路),通过这个例子,我想以后我们写的代码也可以使用 Map 书写出让我们更好维护的代码了

gitee 地址 


最后

感谢各位观众老爷的观看 O(∩_∩)O 希望你能有所收获 :grin:

原文 http://www.cnblogs.com/gating/p/13114661.html


链接: https://www.fly63.com/article/detial/9298

关于 Node.js 里 ES6 Modules 的一次更新说明

关于 Node.js 里 ES6 Modules 的一次更新说明,总结来说:CommonJS 与 ES6 Modules 之间的关键不同在于代码什么时候知道一个模块的结构和使用它。

用node.js开发一个可交互的命令行应用

在这个教程中,我们会开发一个命令行应用,它可以接收一个 CSV 格式的用户信息文件,教程的内容大纲:“Hello,World”,处理命令行参数,运行时的用户输入,异步网络会话,美化控制台的输出,封装成 shell 命令,JavaScript 之外

Node启动https服务器

首先你需要生成https证书,可以去付费的网站购买或者找一些免费的网站,可能会是key或者crt或者pem结尾的。不同格式之间可以通过OpenSSL转换

nodejs 异步转同步

nodej项目在微信环境开发,nodejs的异步特效,会导致请求没有完成就执行下面的代码,出现错误。经过多方查找,可以使用async模块来异步转同步,只有前一个function执行callback,下一个才会执行。

基于node服务器的大文件(G级)上传

3G的大文件分1500个2M二进度文件,通post方法发送给node服务,服务器全部接收到文件后,进组装生成你上文件。

为什么要把 JavaScript 放到服务器端上运行?

JavaScript比C的开发门槛要低,尽管服务器端JavaScript存在已经很多年了,但是后端部分一直没有市场,JavaScript在浏览器中有广泛的事件驱动方面的应用,考虑到高性能、符合事件驱动、没有历史包袱这3个主要原因,JavaScript成为了Node的实现语言。

了解node.js事件循环

node.js的第一个基本论点是I / O的性能消耗是很昂贵。因此,使用当前编程技术的最大浪费来自于等待I / O完成。有几种方法可以处理性能影响

Node.js 应用:Koa2 使用 JWT 进行鉴权

在前后端分离的开发中,通过 Restful API 进行数据交互时,如果没有对 API 进行保护,那么别人就可以很容易地获取并调用这些 API 进行操作。那么服务器端要如何进行鉴权呢?

Node.js 前端开发指南

我们经常跟Node.js打交道,即使你是一名前端开发人员 -- npm脚本,webpack配置,gulp任务,程序打包 或 运行测试等。即使你真的不需要深入理解这些任务,但有时候你会感到困惑,会因为缺少Node.js的一些核心概念而以非常奇怪的方式来编码。

happypack提升项目构建速度

运行在 Node.js 之上的 Webpack 是单线程模型的,也就是说 Webpack 需要处理的任务需要一件件挨着做,不能多个事情一起做。happypack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!