node实现批量修改图片尺寸
前言
大家在工作中肯定有没有遇到过图片尺寸和我们要求的尺寸不一致的情况吧?通常我们会在网上找一下找在线的或者下载一个小工具,再或者通过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,而对象的键值对个数只能手动确认。
优化代码
知道了他两的区别后,我们就可以边写代码啦~~刚刚说到,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:如果不懂,请评论说出来,我看到会第一时间回复的。。。
拓展阅读: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
书写出让我们更好维护的代码了
最后
感谢各位观众老爷的观看 O(∩_∩)O 希望你能有所收获
node实现批量修改图片尺寸的更多相关文章
- 使用Adobe Photoshop CC 2015批量修改图片尺寸
最近在工作中遇到一个问题,当时客户给的图片尺寸与我要求的图片不符,由于图片非常的多,如果一张一张的修改,十分的麻烦,后来经过一位同事的指点,发现Adobe Photoshop CC 2015可以实现批 ...
- python:批量修改文件名批量修改图片尺寸
批量修改文件名 参考博客:https://www.cnblogs.com/zf-blog/p/7880126.html 功能:批量修改文件名 1 2 3 4 5 6 7 8 9 10 11 12 1 ...
- Shell脚本批量修改图片尺寸
#!/bin/sh function scandir(){ local cur_dir parent_dir workdir workdir=$ cd ${workdir} if [ ${workdi ...
- opencv批量修改图片尺寸
#include"opencv2/opencv.hpp" using namespace std; using namespace cv; #include<opencv2/ ...
- Python批量修改图片格式和尺寸
Python批量修改图片格式和尺寸 备注: 1.导入了PIL库,是处理图片用的,很强大; 2.导入了的win32库,是判断隐藏文件用的,我们的项目需要删除隐藏文件,不需要的可以直接找到删除. 3.导入 ...
- ps批量修改图片
批量更改图片尺寸的ps脚本 高端干货!PHOTOSHOP实用脚本大合集
- LINQPad_批量修改图片名称
用到这个工具是在后台批量修改图片名称的时候 下载并安装LINQPad. 这里要注意:在复制path路径的时候C:\xampp\htdocs\day01\angularjs_day01_am\angul ...
- 修改图片尺寸网站https://www.yasuotu.com/
修改图片尺寸网站https://www.yasuotu.com/
- 如何批量修改图片名称(win下)
深度学习目标检测任务中常常需要大量的图片,这些图片一般来自网络爬虫或是自行批量下载,但下载下的图片常常在保存时被命名为长段英文数字混写,因此规律化命名下载的图片数据名称就显得尤为重要了,下面我演示在本 ...
随机推荐
- vagrant与vrtualbox的使用
1.先要安装 vrtualbox和 vagrant (以centos 7下面的为例): cd /opt wget https://download.virtualbox.org/virtualbo ...
- jquery VS Dom(小实例单选-多选-反选)
一直以来大家对jquery评价莫过于六个字 “吃得少,干的多” ,应用实例让大家看看这款牛到爆的插件能帮我们做什么,话不多说,直接加码 <!DOCTYPE html> <html l ...
- 判断数组的方法/判断JS数据类型的四种方法
参考文: 以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣Object.prototype.toString.call() . instanceof 以及 Array.isArray() h ...
- jdk编译java文件时出现:编码GBK的不可映射字符
出现此问题的几种解决办法: 1.cmd下使用javac编译java文件 如: javac test.java 解决办法:编译时加上encoding选项 javac -encoding UTF-8 te ...
- 最优化之Robust PCA
最近加了一个QQ群,接触了点新的东西,包括稀疏近似,低秩近似和压缩感知等.Robust PCA中既包含了低秩,又包含了稀疏,于是以其为切入点,做了如下笔记.笔记中有的公式有比较详细的推导,希望对读者有 ...
- C语言关于数据类型转换
自动类型转换 自动类型转换就是编译器默默地.隐式地.偷偷地进行的数据类型转换,这种转换不需要程序员干预,会自动发生. 1) 将一种类型的数据赋值给另外一种类型的变量时就会发生自动类型转换,例如: ; ...
- 0506static【重点】
static[重点] [重点] 1.[没有对象] [没有对象] [没有对象] 2.static 修饰的是一个资源共享类型的变量 3.静态成员变量的基本使用规范 static修饰的成员变量只能通过静态方 ...
- [FlashDevelop] 001.FlashDevelop + LayaFlash环境搭建
产品简介: 唯一使用Flash直接开发或转换大型HTML5游戏的全套解决方案. 开发工具 FlashDevelop + JDK + flashplayer_18_sa_debug + LayaFlas ...
- Autofac依赖注入
简介 Autofac 是一款超赞的.NET IoC 容器 . 它管理类之间的依赖关系, 从而使 应用在规模及复杂性增长的情况下依然可以轻易地修改 .它的实现方式是将常规的.net类当做 组件 处理. ...
- Oracle数据库表被锁死的处理方法
(1)锁表查询的代码有以下的形式: select count(*) from v$locked_object; select * from v$locked_object; (2)查看哪个表被锁 se ...