mirror of
https://github.com/ShinkoNet/Wplace-Overlay-Pro.git
synced 2026-03-11 21:35:32 +00:00
143 lines
No EOL
5.5 KiB
TypeScript
143 lines
No EOL
5.5 KiB
TypeScript
//import * as PImage from 'pureimage';
|
|
const PImage = require('pureimage');
|
|
import fs from 'fs';
|
|
import { workerData, parentPort } from 'worker_threads';
|
|
import { toNearestColor } from '../util/canvasUtil';
|
|
import { ISwatch } from '../models/swatch';
|
|
|
|
console.log('blueprint worker: starting');
|
|
|
|
const imageDecodeFunc = (workerData.stencilUrl as string).endsWith('.png') ? PImage.decodePNGFromStream : PImage.decodeJPEGFromStream;
|
|
//Not needed as we always save in PNG (tho JPG extensions is left, TODO: fix in Stencil Functionality update)
|
|
//const imageEncodeFunc = (workerData.stencilUrl as string).endsWith('.png') ? PImage.encodePNGToStream : PImage.encodeJPEGToStream;
|
|
|
|
// still not sure of the typing:
|
|
Promise.all([
|
|
imageDecodeFunc(fs.createReadStream('./store/stencils/' + workerData.stencilUrl)),
|
|
workerData.fullBpMarksUrl && PImage.decodePNGFromStream(fs.createReadStream(workerData.fullBpMarksUrl))
|
|
]).then(res => {
|
|
const [img, symImg]: HTMLImageElement[] = res;
|
|
|
|
const SYM_SIZE_FALLBACK_NO_BORDER = 5;
|
|
const SYM_SIZE_FALLBACK_WITH_BORDER = SYM_SIZE_FALLBACK_NO_BORDER + 2;
|
|
|
|
let SYM_SIZE = symImg?.height ?? SYM_SIZE_FALLBACK_WITH_BORDER;
|
|
const [w, h] = [Math.floor(workerData.metadata.width), Math.floor(workerData.metadata.height || (workerData.metadata.width / img.width * img.height))]
|
|
const canvas = PImage.make(w * SYM_SIZE, h * SYM_SIZE);
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.clearRect(0, 0, w * SYM_SIZE, h * SYM_SIZE);
|
|
|
|
// "glow"
|
|
/*
|
|
ctx.globalAlpha = 0.3;
|
|
ctx.drawImage(img, 0, 0, w * SYM_SIZE, h * SYM_SIZE);
|
|
*/
|
|
|
|
const palette = workerData.palette as ISwatch[];
|
|
|
|
const [symW, symH] = [symImg?.width ?? 256 * SYM_SIZE_FALLBACK_WITH_BORDER, symImg?.height ?? SYM_SIZE_FALLBACK_WITH_BORDER]
|
|
const symCanvas = PImage.make(symW, symH);
|
|
const symCtx = symCanvas.getContext('2d');
|
|
symCtx.clearRect(0, 0, symW, symH);
|
|
// generating symbols marks from swatch glyph data
|
|
if (!symImg && workerData.bpType === 'symbols') {
|
|
palette.forEach((s, index) => {
|
|
// new ImageData not implemented in pureImage, getting from symCanvas
|
|
const data = symCtx.getImageData(palette.length * SYM_SIZE_FALLBACK_WITH_BORDER + 1, 1, SYM_SIZE_FALLBACK_NO_BORDER, SYM_SIZE_FALLBACK_NO_BORDER)
|
|
for (let i = 0; i < SYM_SIZE_FALLBACK_NO_BORDER * SYM_SIZE_FALLBACK_NO_BORDER; i++) {
|
|
if ((s.glyph >> i) & 0x1) {
|
|
for (let s = 0; s < 4; s++) {
|
|
data.data[i * 4 + s] = 255;
|
|
}
|
|
}
|
|
}
|
|
symCtx.putImageData(data, index * SYM_SIZE_FALLBACK_WITH_BORDER + 1, 1)
|
|
});
|
|
} else {
|
|
symCtx.drawImage(symImg, -1, -1, symImg.width + 1, symImg.height + 1);
|
|
}
|
|
|
|
const swatchCanvas = PImage.make(256 * SYM_SIZE, SYM_SIZE);
|
|
const sctx = swatchCanvas.getContext('2d');
|
|
sctx.clearRect(0, 0, 256 * SYM_SIZE, SYM_SIZE);
|
|
palette.forEach((s, i) => {
|
|
let symIndex: number = 0;
|
|
switch (workerData.bpType) {
|
|
case 'letters':
|
|
symIndex = s.name.toUpperCase().charCodeAt(0) - 0x41;
|
|
break;
|
|
case 'symbols':
|
|
symIndex = i;
|
|
break;
|
|
case 'numbers':
|
|
symIndex = (i + 1) % 10;
|
|
break;
|
|
}
|
|
sctx.drawImage(symCanvas,
|
|
symIndex * SYM_SIZE,
|
|
0,
|
|
SYM_SIZE,
|
|
SYM_SIZE,
|
|
s.index * SYM_SIZE - ((workerData.bpType === 'numbers' && i < 9) ? 2 : 0), // centering numbers
|
|
0,
|
|
SYM_SIZE,
|
|
SYM_SIZE);
|
|
if (workerData.bpType === 'numbers' && i >= 9) {
|
|
sctx.drawImage(symCanvas, Math.floor((i + 1) / 10) * SYM_SIZE + 4, 0, 4, SYM_SIZE, s.index * SYM_SIZE, 0, 4, SYM_SIZE);
|
|
}
|
|
});
|
|
|
|
// Colorize the blueprint marks:
|
|
/*
|
|
sctx.globalCompositeOperation = 'source-atop';
|
|
(workerData.palette as ISwatch[]).forEach(s => {
|
|
sctx.fillStyle = `rgba(${s.rgba[0]},${s.rgba[1]},${s.rgba[2]},${s.rgba[3] / 255})`;
|
|
sctx.fillRect(s.index * SYM_SIZE, 0, SYM_SIZE, SYM_SIZE);
|
|
});
|
|
*/
|
|
|
|
const sData = sctx.getImageData(0, 0, 256 * SYM_SIZE, SYM_SIZE);
|
|
for (let i = 0; i < sData.data.length; i += 4) {
|
|
const px = (i / 4) % (256 * SYM_SIZE);
|
|
const paletteIndex = Math.floor(px / SYM_SIZE);
|
|
const swatch = (workerData.palette as ISwatch[]).find(sw => sw.index === paletteIndex);
|
|
if (swatch) {
|
|
sData.data[i + 0] = swatch.rgba[0];
|
|
sData.data[i + 1] = swatch.rgba[1];
|
|
sData.data[i + 2] = swatch.rgba[2];
|
|
}
|
|
}
|
|
sctx.putImageData(sData, 0, 0, 256 * SYM_SIZE, SYM_SIZE);
|
|
|
|
ctx.globalAlpha = 1;
|
|
const miniCanvas = PImage.make(w, h);
|
|
const mctx = miniCanvas.getContext('2d');
|
|
mctx.clearRect(0, 0, w, h);
|
|
|
|
// the PureImage lib uses nearest neighbor scaling for shrinking, but always uses left-top pixel from source rectangle
|
|
mctx.drawImage(img, 0, 0, img.width, img.height, -1, -1, w + 1, h + 1);
|
|
const { data } = mctx.getImageData(0, 0, w, h);
|
|
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const color = toNearestColor([data[i], data[i + 1], data[i + 2], data[i + 3]], workerData.palette);
|
|
if (!color || data[i + 3] < 128) continue;
|
|
const x = (i / 4) % w;
|
|
const y = Math.floor(i / 4 / w);
|
|
|
|
ctx.drawImage(
|
|
swatchCanvas,
|
|
color.index * SYM_SIZE,
|
|
0,
|
|
SYM_SIZE,
|
|
SYM_SIZE,
|
|
x * SYM_SIZE,
|
|
y * SYM_SIZE,
|
|
SYM_SIZE,
|
|
SYM_SIZE);
|
|
}
|
|
|
|
PImage.encodePNGToStream(canvas, fs.createWriteStream(workerData.dirPath + '/' + workerData.stencilUrl)).then(() => {
|
|
console.log('blueprint worker: finishing');
|
|
parentPort?.postMessage(workerData.fileName);
|
|
})
|
|
}) |