因为瓦片地图的颜色问题,需要对大量地图图片进行颜色处理。前端处理可以利用canvas,然后转为Base64,调用接口保存,但是需要前后端配合,递归读取文件,处理后进行保存,由于chrome对同一server的http请求数量最多只有6个,限制执行效率,所以采取纯服务的方式。
图片处理依赖node-canvas库,最开始采用异步IO的方式,导致程序启动后读取所有文件(7w张图片),程序崩溃,后面改为同步方式,并采用多进程单线程负载均衡,处理7w张图片耗时500s左右。
工程包括 config.js配置文件,master.js主进程文件,worker.js工作进程文件。
config.js:
module.exports = {
SRC_DIR: 'D:\\TIANDITU2\\TIANDITU2', // 源文件目录
TARGER_DIR: 'resultImages' // 输出目录
}
master.js:
const fs = require('fs');
const cpuNum = require('os').cpus().length;
const childProcess = require('child_process');
const CONFIG = require('./config');
let index = 0; // 负载均衡
let workers = []; // 子进程
let pids = []; // 子进程pid
let count = 0; // 已处理文件数量
let timeStamp = +new Date(); // 启动时间戳
// 创建子进程
createWorkers();
// 读取文件,由子进程处理
getFiles(CONFIG.SRC_DIR)
function getFiles(dir) {
fs.readdir(dir, function(err, files){
if(err) {
console.warn(err)
}else {
files.forEach(fileName => {
workers[index].send({
dir, fileName})
index = Number.parseInt((index + 1) % cpuNum)
})
}
})
}
function createWorkers() {
console.log('-------------------- 主进程启动 --------------------')
for (let i = 0; i < cpuNum; ++i) {
let worker_instance = childProcess.fork('worker.js');
worker_instance.on('message', ({
type, data, pid}) => {
switch (type) {
case 'dir':
getFiles(data);
break;
case 'count':
console.log('%c已转换' + ++count + '个, 耗时:' + ((+new Date() - timeStamp) / 1000) + 's, 进程' + (pids.indexOf(pid) + 1) + '处理', 'color: green');
break;
}
})
workers.push(worker_instance);
pids.push(worker_instance.pid)
}
console.log('-------------------- 子进程启动 --------------------')
}
worker.js:
const path = require("path");
const fs = require('fs');
const {
createCanvas, loadImage } = require('canvas')
const CONFIG = require('./config')
let canvas = null,
ctx = null,
canvasWidth = 0,
canvasHeight = 0;
process.on('message', ({
dir, fileName}) => {
var filedir = path.join(dir, fileName);
let stat = fs.statSync(filedir)
if(!stat){
console.warn('stat');
}else {
var isFile = stat.isFile();
// 图片 => 转换
if(isFile) {
let dirList = path.dirname(filedir).split(path.sep);
dirList.shift()
handleImage(filedir, dirList.join(path.sep), fileName)
}else {
// 文件夹 => 递归获取
process.send({
type: 'dir', data: filedir})
}
}
})
function handleImage(filePath, middleDir, fileName) {
loadImage(filePath).then((image) => {
// const canvas = createCanvas(image.width, image.height);
// const ctx = canvas.getContext('2d');
if(image.width != canvasWidth || image.height != canvasHeight) {
canvas = createCanvas(image.width, image.height);
ctx = canvas.getContext('2d');
canvasWidth = image.width;
canvasHeight = image.height;
}
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// ctx.filter = "hue-rotate(185deg) invert(90%) sepia(30%) saturate(160%) brightness(80%) contrast(90%) grayscale(5%)";
ctx.drawImage(image, 0, 0)
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var px = imgData.data;
for(var i = 0; i < canvas.width * canvas.height; i++){
px[i * 4 + 0] ^= 0xff;
px[i * 4 + 1] ^= 0xff;
px[i * 4 + 2] ^= 0xff;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imgData, 0, 0);
let dir = path.join(CONFIG.TARGER_DIR, middleDir, fileName);
dirExists(dir)
fs.writeFileSync(dir, canvas.toBuffer())
process.send({
type: 'count', pid: process.pid})
})
}
// 递归判断目录是否存在, 不存在则创建目录
function dirExists(_path) {
// 递归判断目录是否存在
let _dir = path.parse(_path).dir;
if(fs.existsSync(_dir)) {
return true
}else {
// 当前目录不存在,递归校验上层目录
if( dirExists(_dir) ) {
fs.mkdirSync(_dir);
return true;
}
}
}