前言

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

简单的搭建一下

  • 新建一个 canvas-image-resize 目录

  • 初始化一个node项目工程

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

    npm i canvas glob archiver -S

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

简单的使用一下

同样,有了前面我们使用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,而对象的键值对个数只能手动确认。

优化代码

知道了他两的区别后,我们就可以边写代码啦~~刚刚说到,Mapkey可是是任意值,所以我们就可以使用正则类型(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:如果不懂,请评论说出来,我看到会第一时间回复的。。。

拓展阅读:object 属性的输出顺序是无序的问题了解

拓展阅读:5 分钟彻底理解 Object.keys

正式编写优化后的代码

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

// 获取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 地址,github 地址

最后

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

node实现批量修改图片尺寸的更多相关文章

  1. 使用Adobe Photoshop CC 2015批量修改图片尺寸

    最近在工作中遇到一个问题,当时客户给的图片尺寸与我要求的图片不符,由于图片非常的多,如果一张一张的修改,十分的麻烦,后来经过一位同事的指点,发现Adobe Photoshop CC 2015可以实现批 ...

  2. python:批量修改文件名批量修改图片尺寸

    批量修改文件名  参考博客:https://www.cnblogs.com/zf-blog/p/7880126.html 功能:批量修改文件名 1 2 3 4 5 6 7 8 9 10 11 12 1 ...

  3. Shell脚本批量修改图片尺寸

    #!/bin/sh function scandir(){ local cur_dir parent_dir workdir workdir=$ cd ${workdir} if [ ${workdi ...

  4. opencv批量修改图片尺寸

    #include"opencv2/opencv.hpp" using namespace std; using namespace cv; #include<opencv2/ ...

  5. Python批量修改图片格式和尺寸

    Python批量修改图片格式和尺寸 备注: 1.导入了PIL库,是处理图片用的,很强大; 2.导入了的win32库,是判断隐藏文件用的,我们的项目需要删除隐藏文件,不需要的可以直接找到删除. 3.导入 ...

  6. ps批量修改图片

    批量更改图片尺寸的ps脚本 高端干货!PHOTOSHOP实用脚本大合集

  7. LINQPad_批量修改图片名称

    用到这个工具是在后台批量修改图片名称的时候 下载并安装LINQPad. 这里要注意:在复制path路径的时候C:\xampp\htdocs\day01\angularjs_day01_am\angul ...

  8. 修改图片尺寸网站https://www.yasuotu.com/

    修改图片尺寸网站https://www.yasuotu.com/

  9. 如何批量修改图片名称(win下)

    深度学习目标检测任务中常常需要大量的图片,这些图片一般来自网络爬虫或是自行批量下载,但下载下的图片常常在保存时被命名为长段英文数字混写,因此规律化命名下载的图片数据名称就显得尤为重要了,下面我演示在本 ...

随机推荐

  1. mysql小白系列_11 MHA

    一.MHA是什么?能干什么的 (1)以Perl语言写的一套Mysql故障切换方案,一个脚本管理工具 (2)保障数据库的高可用性 (3)修复多个slave之间的差异日志,最终使所有的slave保持数据一 ...

  2. html5做webAPP界面适配总结

    一.px em rem px像素(Pixel).相对长度单位.像素px是相对于显示器屏幕分辨率而言的. em是相对长度单位.相对于当前对象内文本的字体尺寸.如当前对行内文本的字体尺寸未被人为设置,则相 ...

  3. Java——反射三种方式的效率对比

    转载自:https://blog.csdn.net/aitcax/article/details/52694423 1 使用field(效率最高)             long start = S ...

  4. Magicodes.SwaggerUI 已支持.NET Core 3.1

    Magicodes.SwaggerUI 通过配置文件简单配置即可快速完成SwaggerUI的配置,包括: SwaggerUI的文档信息 API分组 API隐藏 API JSON生成(枚举.API架构I ...

  5. 【JVM】垃圾回收的四大算法

    GC垃圾回收 JVM大部分时候回收的都是新生代(伊甸区+幸存0区+幸存1区).按照回收的区域可以分成两种类型:Minor GC和Full GC(MajorGC). Minor GC:只针对新生代区域的 ...

  6. 实验三 UML 建模工具的安装与使用

    UML 建模工具的安装与使用一. 实验目的1) 学习使用 EA(Enterprise Architect) 开发环境创建模型的一般方法: 2) 理解 EA 界面布局和元素操作的一般技巧: 3) 熟悉 ...

  7. 【HBase】知识小结+HMaster选举、故障恢复、读写流程

    1:什么是HBase HBase是一个高可靠性,高性能,面向列,可伸缩的分布式数据库,提供海量数据存储功能,一个结构化的分布式存储系统,不同于一般的关系型数据库,它适合半结构化和非结构化数据存储. 2 ...

  8. Java实现 蓝桥杯 算法训练 Beaver's Calculator

    试题 算法训练 Beaver's Calculator 问题描述 从万能词典来的聪明的海狸已经使我们惊讶了一次.他开发了一种新的计算器,他将此命名为"Beaver's Calculator ...

  9. Java实现 LeetCode 566 重塑矩阵(遍历矩阵)

    566. 重塑矩阵 在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据. 给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表 ...

  10. Java实现蓝桥杯VIP 算法训练 sign函数

    问题描述 给定实数x,输出sign(x)的值. sign(x)是符号函数,如果x>0,则返回1:如果x=0,则返回0:如果x<0,则返回-1. 输入格式 一行一个实数x. 输出格式 一行一 ...