前言

大家在工作中肯定有没有遇到过图片尺寸和我们要求的尺寸不一致的情况吧?通常我们会在网上找一下找在线的或者下载一个小工具,再或者通过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. sourcetree 拉取 一直让输入密码

    以下方法都没用 在控制台中 git gc git prune git config --global credential.helper store git pull 输入账号密码 git pull ...

  2. 模板:分页JSP(结合Servlet)

    DAO类(后续无需改变) package dao; import java.sql.Connection; import java.sql.PreparedStatement; import java ...

  3. Java中异常分类和Throw和Throws的区别

    Java中异常分类和Throw和Throws的区别 1.异常分类 Throwable是Java中所有错误和异常的超类.它的下一级是Error和Exception 1.1 Error(错误) Error ...

  4. 特效 css3 持续动作的渐变背景

    html,body{ margin:; padding:; height: 100%; width: 100%; background: linear-gradient(125deg,#2c3e50, ...

  5. 【解构云原生】初识Kubernetes Service

    编者按:云原生是网易杭州研究院(网易杭研)奉行的核心技术方向之一,开源容器平台Kubernetes作为云原生产业技术标准.云原生生态基石,在设计上不可避免有其复杂性,Kubernetes系列文章基于网 ...

  6. iOS开发添加新手引导

    往往项目中经常出现此类需求 用户通过点击引导按钮可响应页面附带按钮的点击事件. // // gzhGuideView.h // GuideView // // Created by 郭志贺 on 20 ...

  7. html5学习之路_007

    CSS概述 CSS指层叠样式表 CSS样式表极大地提高了工作效率 CSS基础语法 selector { property:value } 例:hi {color:red; font-size:14px ...

  8. pandas DataFrame 的横向纵向拼接组合

    concat 与其说是连接,更准确的说是拼接.就是把两个表直接合在一起.于是有一个突出的问题,是横向拼接还是纵向拼接,所以concat 函数的关键参数是axis . 函数的具体参数是: concat( ...

  9. 【Java8新特性】关于并行流与串行流,你必须掌握这些!!

    写在前面 提到Java8,我们不得不说的就是Lambda表达式和Stream API.而在Java8中,对于并行流和串行流同样做了大量的优化.对于并行流和串行流的知识,也是在面试过程中,经常被问到的知 ...

  10. DQN(Deep Q-learning)入门教程(五)之DQN介绍

    简介 DQN--Deep Q-learning.在上一篇博客DQN(Deep Q-learning)入门教程(四)之Q-learning Play Flappy Bird 中,我们使用Q-Table来 ...