tileblaster
Version:
a quick and versatile map tile caching proxy
119 lines (97 loc) • 3.78 kB
JavaScript
const worker = require("node:worker_threads");
const stream = require("node:stream");
if (worker.isMainThread) {
const cache = {};
// optimize
module.exports = function modernize({ opts, data }, next){
const debug = this.lib.debug;
const load = this.lib.load;
if (!cache.optipng) cache.optipng = load.exists("optipng-js");
if (!cache.mozjpeg) cache.mozjpeg = load.exists("js-mozjpeg");
if (!cache.hasOwnProperty(data.map)) cache[data.map] = {
png: (!opts.png) ? false : (opts.png === true) ? {} : opts.png,
jpeg: (!opts.jpeg) ? false : (opts.jpeg === true) ? {} : opts.jpeg,
};
opts = cache[data.map];
const jobs = [];
// find all pngs
if (opts.png && cache.optipng) /*[ data.tile, ...data.tiles ]*/data.tiles.filter(function(tile){
return (tile.type === "png" && !tile.compression && tile.buffer.length > 0) // uncompressed png only
}).forEach(function(tile){
jobs.push(new Promise(function(resolve,reject){
// create png worker
const w = new worker.Worker(__filename, {
workerData: { type: "png", opts: opts.png },
stdin: true, stdout: true,
});
// get result from worker
let result = [];
w.stdout.on("data", function(chunk){
result.push(chunk);
}).on("end", function(){
result = Buffer.concat(result);
if (result && result.length > 0 && result.length < tile.buffer.length) {
debug.info("Optimized PNG '%s': -%db", tile.path.magenta, tile.buffer.length-result.length);
tile.buffer = result;
}
resolve();
});
// stream buffer to worker
stream.Readable.from(tile.buffer).pipe(w.stdin);
}));
});
// find all jpegs
if (opts.jpeg && cache.mozjpeg) [ data.tile, ...data.tiles ].filter(function(tile){
return ((tile.type === "jpeg" || tile.type === "jpg") && !tile.compression && tile.buffer.length > 0) // uncompressed jpeg only
}).forEach(function(tile){
jobs.push(new Promise(function(resolve,reject){
// create jpeg worker
const w = new worker.Worker(__filename, {
workerData: { type: "jpeg", opts: opts.jpeg },
stdin: true, stdout: true,
});
// get result from worker
let result = [];
w.stdout.on("data", function(chunk){
result.push(chunk);
}).on("end", function(){
result = Buffer.concat(result);
if (result && result.length > 0 && result.length < tile.buffer.length) {
debug.info("Optimized JPEG '%s': -%db", tile.path.magenta, tile.buffer.length-result.length);
tile.buffer = result;
}
resolve();
});
// stream buffer to worker
stream.Readable.from(tile.buffer).pipe(w.stdin);
}));
});
// check if there are jobs
if (jobs.length === 0) return debug.warn("Nothing to optimize for '%s': -%db", (data.tile.path||"").magenta), next();
// execute
Promise.allSettled(jobs).then(function(){
next();
});
};
} else {
let buf = [];
process.stdin.on("data", function(chunk){
buf.push(chunk);
}).on("end", function() {
buf = Buffer.concat(buf);
if (buf.length > 0) switch (worker.workerData.type) {
case "png":
const optipng = require("optipng-js");
const outputpng = optipng(buf, { o: "3", i: "0", fix: true, quiet: true, ...worker.workerData.opts });
if (outputpng.data && outputpng.data.length > 0 && outputpng.data.length < buf.length) buf = Buffer.from(outputpng.data);
break;
case "jpg":
case "jpeg":
const jpegtran = require("js-mozjpeg").jpegtran;
const outputjpeg = jpegtran(buf, { optimize: true, maxmemory: "4m", copy: "none", ...(worker.workerData.opts||{}) });
if (outputjpeg.data && outputjpeg.data.length > 0 && outputjpeg.data.length < buf.length) buf = Buffer.from(outputjpeg.data);
break;
};
stream.Readable.from(buf).pipe(process.stdout);
});
};