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下)
深度学习目标检测任务中常常需要大量的图片,这些图片一般来自网络爬虫或是自行批量下载,但下载下的图片常常在保存时被命名为长段英文数字混写,因此规律化命名下载的图片数据名称就显得尤为重要了,下面我演示在本 ...
随机推荐
- JAVA基础4---序列化和反序列化深入整理(JDK序列化)
一.什么是序列化和反序列化? 序列化:将对象状态信息转化成可以存储或传输的形式的过程(Java中就是将对象转化成字节序列的过程) 反序列化:从存储文件中恢复对象的过程(Java中就是通过字节序列转化成 ...
- MySQL InnoDB索引介绍以及在线添加索引实例分析
引言:MySQL之所以能成为经典,不是没有道理的,B+树足矣! 一.索引概念 InnoDB引擎支持三种常见的索引:B+树索引,全文索引和(自适应)哈希索引.B+树索引是传统意义上的索引,构造类似二叉树 ...
- Java switch case语句
switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支. switch case 语句语法格式如下: switch(expression){ case value : ...
- 从零开始搭建一个PaaS平台 - 我们要做什么
前言 从最开始的小公司做小网站,到现在进入现在的公司做项目,发现小公司里很多很多工作都是重复的劳动(增删改查),不过想想也是,业务软件最基础的东西不就是增删改查吗. 但是很多时候,这种业务逻辑其实没有 ...
- JPA EntityManager 在没有实体类的情况下返回Map
JPA entityManager.createNativeQuery()执行原生的SQL,当我们查询结果没有对应的实体类时,query.getResultList()返回的是一个List<Ob ...
- Golang源码学习:调度逻辑(一)初始化
本文所使用的Golang为1.14,dlv为1.4.0. 源代码 package main import "fmt" func main() { fmt.Println(" ...
- 容器技术之Docker网络
上一篇博客我们主要聊了下docker镜像相关的说明以及怎样基于现有镜像制作镜像.分发镜像到docker仓库中的相关测试:回顾请参考https://www.cnblogs.com/qiuhom-1874 ...
- JSP页面基础用法和方法查询
导包jar:jstl.jar standard.jar jsp嵌套java代码,使用jsp脚本:1)<%java代码%> ----- 内部的java代码翻译到service方法的内部 ...
- Java 基础系列知识梳理
- 第二章-数据绑定和第一个AnglarJS Web应用
Angularjs中的数据绑定 AngularJS创建实时模板来代替视图,而不是将数据合并进模板之后更新DOM.任何一个独立视图组件中的值都是动态替换的.这个功能可以说是AngularJS中最最重要的 ...