438 lines
11 KiB
JavaScript
438 lines
11 KiB
JavaScript
/*
|
|
* runs a filter over a larger canvas area over time
|
|
*
|
|
*/
|
|
|
|
import {
|
|
setData,
|
|
getData,
|
|
setStatus,
|
|
getStatus,
|
|
} from '../data/models/CanvasCleaner';
|
|
import RedisCanvas from '../data/models/RedisCanvas';
|
|
import {
|
|
getChunkOfPixel,
|
|
getCornerOfChunk,
|
|
} from './utils';
|
|
import { setPixelByOffset } from './setPixel';
|
|
import {
|
|
TILE_SIZE,
|
|
} from './constants';
|
|
import logger from './logger';
|
|
// eslint-disable-next-line import/no-unresolved
|
|
import canvases from './canvases.json';
|
|
|
|
const METHODS = {
|
|
/*
|
|
* @param xc, yc chunk coordinates of pixel relative to center chunk
|
|
* of chunk area
|
|
*/
|
|
spare: (xc, yc, clrIgnore, canvasCleaner) => {
|
|
let pxl = canvasCleaner.getPixelInChunkArea(xc, yc);
|
|
if (pxl === null || pxl < clrIgnore) {
|
|
return null;
|
|
}
|
|
let rplPxl = null;
|
|
for (let u = -1; u <= 1; u += 1) {
|
|
for (let v = -1; v <= 1; v += 1) {
|
|
pxl = canvasCleaner.getPixelInChunkArea(xc + u, yc + v);
|
|
if (pxl === null
|
|
|| (u === 0 && v === 0)
|
|
) {
|
|
continue;
|
|
}
|
|
if (pxl >= clrIgnore) {
|
|
return null;
|
|
}
|
|
if (rplPxl === null) {
|
|
rplPxl = pxl;
|
|
}
|
|
}
|
|
}
|
|
return rplPxl;
|
|
},
|
|
|
|
spareext: (xc, yc, clrIgnore, canvasCleaner) => {
|
|
let pxl = canvasCleaner.getPixelInChunkArea(xc, yc);
|
|
if (pxl === null || pxl < clrIgnore) {
|
|
return null;
|
|
}
|
|
let cntSet = 1;
|
|
let rplPxl = null;
|
|
for (let u = -1; u <= 1; u += 1) {
|
|
for (let v = -1; v <= 1; v += 1) {
|
|
pxl = canvasCleaner.getPixelInChunkArea(xc + u, yc + v);
|
|
if (pxl === null
|
|
|| (u === 0 && v === 0)
|
|
) {
|
|
continue;
|
|
}
|
|
if (pxl >= clrIgnore) {
|
|
if (cntSet >= 2) {
|
|
return null;
|
|
}
|
|
cntSet += 1;
|
|
}
|
|
if (rplPxl === null) {
|
|
rplPxl = pxl;
|
|
}
|
|
}
|
|
}
|
|
return rplPxl;
|
|
},
|
|
|
|
spareextu: (xc, yc, clrIgnore, canvasCleaner) => {
|
|
let pxl = canvasCleaner.getPixelInChunkArea(xc, yc);
|
|
if (pxl === null || pxl < clrIgnore) {
|
|
return null;
|
|
}
|
|
let rplPxl = null;
|
|
let fixPxl = null;
|
|
const origPxl = pxl;
|
|
for (let u = -1; u <= 1; u += 1) {
|
|
for (let v = -1; v <= 1; v += 1) {
|
|
pxl = canvasCleaner.getPixelInChunkArea(xc + u, yc + v);
|
|
if (pxl === null
|
|
|| (u === 0 && v === 0)
|
|
) {
|
|
continue;
|
|
}
|
|
if (pxl < clrIgnore) {
|
|
if (rplPxl === null) {
|
|
rplPxl = pxl;
|
|
}
|
|
continue;
|
|
}
|
|
if (fixPxl === null) {
|
|
fixPxl = pxl;
|
|
continue;
|
|
}
|
|
if (pxl !== fixPxl) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
const finPxl = (rplPxl !== null) ? rplPxl : fixPxl;
|
|
if (finPxl === origPxl) {
|
|
return null;
|
|
}
|
|
return finPxl;
|
|
},
|
|
};
|
|
|
|
class CanvasCleaner {
|
|
// canvas id: integer
|
|
canvasId;
|
|
// coords of top left and bottom right corner of area: integer
|
|
x;
|
|
y;
|
|
u;
|
|
v;
|
|
// name of filter method, string
|
|
methodName;
|
|
// 3x3 canvas area
|
|
// [
|
|
// [AA, AB, AC],
|
|
// [BA, BB, BC],
|
|
// [CA, CB, CC],
|
|
// ]
|
|
chunks;
|
|
// chunk coordinates of center BB of chunks
|
|
centerChunk;
|
|
// iterator over chunks
|
|
cIter;
|
|
// info about chunks of total affected area
|
|
// cx, cy: top right chunk coords
|
|
// cw, ch: height and width in chunks
|
|
// amountChunks: cw * ch
|
|
cx; cy;
|
|
cw; ch;
|
|
amountChunks;
|
|
// current setTimeout index
|
|
tick;
|
|
// if running: boolean
|
|
running;
|
|
// stats
|
|
pxlProcessed;
|
|
pxlCleaned;
|
|
|
|
constructor() {
|
|
this.logger = (text) => {
|
|
logger.warn(`[CanvasCleaner] ${text}`);
|
|
};
|
|
this.cleanChunk = this.cleanChunk.bind(this);
|
|
this.clearValues();
|
|
this.loadArgs();
|
|
}
|
|
|
|
clearValues() {
|
|
this.running = false;
|
|
this.chunks = [
|
|
[null, null, null],
|
|
[null, null, null],
|
|
[null, null, null],
|
|
];
|
|
this.centerChunk = [null, null];
|
|
this.cIter = 0;
|
|
this.cx = 0;
|
|
this.cy = 0;
|
|
this.cw = 0;
|
|
this.ch = 0;
|
|
this.amountChunks = 0;
|
|
this.pxlProcessed = 0;
|
|
this.pxlCleaned = 0;
|
|
this.tick = null;
|
|
}
|
|
|
|
async loadArgs() {
|
|
const [cIter, running] = await getStatus();
|
|
if (running) {
|
|
const [canvasId, x, y, u, v, methodName] = await getData();
|
|
this.set(canvasId, x, y, u, v, methodName, cIter);
|
|
}
|
|
}
|
|
|
|
stop() {
|
|
this.running = false;
|
|
const str = 'Stopped CanvasCleaner';
|
|
this.logger(str);
|
|
return str;
|
|
}
|
|
|
|
async cleanChunk() {
|
|
this.tick = null;
|
|
const {
|
|
canvasId, cIter, cw, cx, cy,
|
|
} = this;
|
|
const method = METHODS[this.methodName];
|
|
if (cIter >= this.amountChunks || !this.running) {
|
|
// finished
|
|
// eslint-disable-next-line max-len
|
|
this.logger(`Finished Cleaning on ${this.x},${this.y}, cleaned ${this.pxlCleaned} / ${this.pxlProcessed} pixels`);
|
|
this.clearValues();
|
|
this.saveStatus();
|
|
return;
|
|
}
|
|
const canvas = canvases[canvasId];
|
|
let i = (cIter % cw);
|
|
const j = ((cIter - i) / cw) + cy;
|
|
i += cx;
|
|
const clrIgnore = canvas.cli || 0;
|
|
|
|
await this.loadChunkArea(i, j);
|
|
if (this.checkIfChunkInArea(i, j)) {
|
|
const [xCor, yCor] = getCornerOfChunk(canvas.size, i, j);
|
|
const xLow = (xCor > this.x) ? 0 : (this.x - xCor);
|
|
const yLow = (yCor > this.y) ? 0 : (this.y - yCor);
|
|
const xHigh = (xCor + TILE_SIZE <= this.u) ? TILE_SIZE
|
|
: (this.u - xCor + 1);
|
|
const yHigh = (yCor + TILE_SIZE <= this.v) ? TILE_SIZE
|
|
: (this.v - yCor + 1);
|
|
for (let xc = xLow; xc < xHigh; xc += 1) {
|
|
for (let yc = yLow; yc < yHigh; yc += 1) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const rplPxl = await method(xc, yc, clrIgnore, this);
|
|
this.pxlProcessed += 1;
|
|
if (rplPxl !== null) {
|
|
this.pxlCleaned += 1;
|
|
setPixelByOffset(
|
|
canvasId,
|
|
rplPxl,
|
|
i, j,
|
|
yc * TILE_SIZE + xc,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.saveStatus();
|
|
this.cIter += 1;
|
|
|
|
this.tick = setTimeout(this.cleanChunk, 500);
|
|
}
|
|
|
|
set(canvasId, x, y, u, v, methodName, cIter = 0) {
|
|
if (!METHODS[methodName]) {
|
|
const str = `Method ${methodName} not available`;
|
|
this.logger(str);
|
|
return str;
|
|
}
|
|
const canvas = canvases[canvasId];
|
|
if (!canvas) {
|
|
const str = `Canvas ${canvasId} invalid`;
|
|
this.logger(str);
|
|
return str;
|
|
}
|
|
if (canvas.v) {
|
|
const str = 'Can not clean 3D canvas';
|
|
this.logger(str);
|
|
return str;
|
|
}
|
|
if (x > u || y > v) {
|
|
const str = 'Invalid area';
|
|
this.logger(str);
|
|
return str;
|
|
}
|
|
const canvasSize = canvas.size;
|
|
const canvasMaxXY = canvasSize / 2;
|
|
const canvasMinXY = -canvasMaxXY;
|
|
if (x < canvasMinXY || y < canvasMinXY
|
|
|| x >= canvasMaxXY || y >= canvasMaxXY
|
|
|| u < canvasMinXY || v < canvasMinXY
|
|
|| u >= canvasMaxXY || v >= canvasMaxXY) {
|
|
const str = 'Coordinates out of bounds';
|
|
this.logger(str);
|
|
return str;
|
|
}
|
|
|
|
if (this.tick) {
|
|
this.running = false;
|
|
clearTimeout(this.tick);
|
|
}
|
|
this.canvasId = canvasId;
|
|
this.x = x;
|
|
this.y = y;
|
|
this.u = u;
|
|
this.v = v;
|
|
this.cIter = cIter;
|
|
this.methodName = methodName;
|
|
const [cx, cy] = getChunkOfPixel(canvas.size, this.x, this.y);
|
|
this.cx = cx;
|
|
this.cy = cy;
|
|
const [cu, cv] = getChunkOfPixel(canvas.size, this.u, this.v);
|
|
this.cw = cu - cx + 1;
|
|
this.ch = cv - cy + 1;
|
|
this.amountChunks = this.cw * this.ch;
|
|
|
|
this.running = true;
|
|
this.tick = setTimeout(this.cleanChunk, 500);
|
|
// eslint-disable-next-line max-len
|
|
this.logger(`Start Cleaning on #${canvas.ident},${this.x},${this.y} till #${canvas.ident},${this.u},${this.v} with method ${methodName}`);
|
|
this.saveData();
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* get pixel out of 3x3 chunk area
|
|
* @param x, y coordinates relative to center chunk
|
|
* @return integer color index or null if chunk is empty
|
|
*/
|
|
getPixelInChunkArea(x, y) {
|
|
const { chunks } = this;
|
|
let col;
|
|
let xc = x;
|
|
if (x >= 0 && x < TILE_SIZE) {
|
|
col = 1;
|
|
} else if (x < 0) {
|
|
col = 0;
|
|
xc += TILE_SIZE;
|
|
} else {
|
|
col = 2;
|
|
xc -= TILE_SIZE;
|
|
}
|
|
let row;
|
|
let yc = y;
|
|
if (y >= 0 && y < TILE_SIZE) {
|
|
row = 1;
|
|
} else if (y < 0) {
|
|
row = 0;
|
|
yc += TILE_SIZE;
|
|
} else {
|
|
row = 2;
|
|
yc -= TILE_SIZE;
|
|
}
|
|
const chunk = chunks[row][col];
|
|
if (!chunk) return null;
|
|
// get rid of protection
|
|
return chunk[yc * TILE_SIZE + xc] & 0x3F;
|
|
}
|
|
|
|
/*
|
|
* load 3x3 chunk area
|
|
* @param i, j chunk coordinates of center chunk
|
|
*/
|
|
async loadChunkArea(i, j) {
|
|
const { chunks, centerChunk, canvasId } = this;
|
|
const [io, jo] = centerChunk;
|
|
const newChunks = [
|
|
[null, null, null],
|
|
[null, null, null],
|
|
[null, null, null],
|
|
];
|
|
for (let iRel = -1; iRel <= 1; iRel += 1) {
|
|
for (let jRel = -1; jRel <= 1; jRel += 1) {
|
|
let chunk = null;
|
|
const iAbs = iRel + i;
|
|
const jAbs = jRel + j;
|
|
if (
|
|
io && jo
|
|
&& iAbs >= io - 1
|
|
&& iAbs <= io + 1
|
|
&& jAbs >= jo - 1
|
|
&& jAbs <= jo + 1
|
|
) {
|
|
chunk = chunks[jAbs - jo + 1][iAbs - io + 1];
|
|
} else {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
chunk = await RedisCanvas.getChunk(canvasId, iAbs, jAbs);
|
|
if (!chunk || chunk.length !== TILE_SIZE * TILE_SIZE) {
|
|
chunk = null;
|
|
if (chunk) {
|
|
// eslint-disable-next-line max-len
|
|
this.logger(`Chunk ch:${canvasId}:${iAbs}:${jAbs} has invalid size ${chunk.length}.`);
|
|
}
|
|
}
|
|
}
|
|
newChunks[jRel + 1][iRel + 1] = chunk;
|
|
}
|
|
}
|
|
this.chunks = newChunks;
|
|
this.centerChunk = [i, j];
|
|
}
|
|
|
|
/*
|
|
* check if chunk exists in area and is not empty
|
|
* @param i, j chunk to check
|
|
*/
|
|
checkIfChunkInArea(i, j) {
|
|
const { chunks, centerChunk } = this;
|
|
const [io, jo] = centerChunk;
|
|
if (
|
|
io && jo
|
|
&& i >= io - 1
|
|
&& i <= io + 1
|
|
&& j >= jo - 1
|
|
&& j <= jo + 1
|
|
) {
|
|
const col = i - io + 1;
|
|
const row = j - jo + 1;
|
|
if (chunks[row][col] !== null) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
reportStatus() {
|
|
return {
|
|
running: this.running,
|
|
canvasId: this.canvasId,
|
|
percent: `${this.cIter} / ${this.amountChunks}`,
|
|
tl: `${this.x}_${this.y}`,
|
|
br: `${this.u}_${this.v}`,
|
|
method: this.methodName,
|
|
};
|
|
}
|
|
|
|
saveData() {
|
|
setData(this.canvasId, this.x, this.y, this.u, this.v, this.methodName);
|
|
}
|
|
|
|
saveStatus() {
|
|
setStatus(this.cIter, this.running);
|
|
}
|
|
}
|
|
|
|
export default new CanvasCleaner();
|