pixelplanet/src/core/Image.js
2020-05-24 23:23:19 +02:00

241 lines
7.3 KiB
JavaScript

/* @flow
*
* functions to deal with images
*
*/
// Tile creation is allowed to be slow
/* eslint-disable no-await-in-loop */
import RedisCanvas from '../data/models/RedisCanvas';
import logger from './logger';
import { getChunkOfPixel } from './utils';
import { TILE_SIZE } from './constants';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json';
import Palette from './Palette';
/*
* Load iamge from ABGR buffer onto canvas
* (be aware that tis function does no validation of arguments)
* @param canvasId numerical ID of canvas
* @param x X coordinate on canvas
* @param y Y coordinate on canvas
* @param data buffer of image in ABGR format
* @param width Width of image
* @param height height of image
*/
export async function imageABGR2Canvas(
canvasId: number,
x: number,
y: number,
data: Buffer,
width: number,
height: number,
wipe?: boolean,
protect?: boolean,
) {
logger.info(
`Loading image with dim ${width}/${height} to ${x}/${y}/${canvasId}`,
);
const canvas = canvases[canvasId];
const { colors, cli, size } = canvas;
const palette = new Palette(colors);
const canvasMinXY = -(size / 2);
const imageData = new Uint32Array(data.buffer);
const [ucx, ucy] = getChunkOfPixel(size, x, y);
const [lcx, lcy] = getChunkOfPixel(size, x + width, y + height);
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
let chunk;
for (let cx = ucx; cx <= lcx; cx += 1) {
for (let cy = ucy; cy <= lcy; cy += 1) {
chunk = await RedisCanvas.getChunk(canvasId, cx, cy);
chunk = (chunk)
? new Uint8Array(chunk)
: new Uint8Array(TILE_SIZE * TILE_SIZE);
// offset of chunk in image
const cOffX = cx * TILE_SIZE + canvasMinXY - x;
const cOffY = cy * TILE_SIZE + canvasMinXY - y;
let cOff = 0;
let pxlCnt = 0;
for (let py = 0; py < TILE_SIZE; py += 1) {
for (let px = 0; px < TILE_SIZE; px += 1) {
const clrX = cOffX + px;
const clrY = cOffY + py;
if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) {
const clr = imageData[clrX + clrY * width];
const clrIndex = (wipe || protect)
? palette.abgr.indexOf(clr)
: palette.abgr.indexOf(clr, cli);
if (clrIndex !== -1) {
const pixel = (protect) ? (clrIndex | 0x80) : clrIndex;
chunk[cOff] = pixel;
pxlCnt += 1;
}
}
cOff += 1;
}
}
if (pxlCnt) {
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
if (ret) {
logger.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`);
}
}
chunk = null;
}
}
logger.info('Image loading done.');
}
/*
* Load iamgemask from ABGR buffer and execute function for each black pixel
* (be aware that tis function does no validation of arguments)
* @param canvasId numerical ID of canvas
* @param x X coordinate on canvas
* @param y Y coordinate on canvas
* @param data buffer of image in ABGR format
* @param width Width of image
* @param height height of image
* @param filter function that defines what happens to the pixel that matches,
* it will be called with the pixelcolor as argument, its return value gets set
*/
export async function imagemask2Canvas(
canvasId: number,
x: number,
y: number,
data: Buffer,
width: number,
height: number,
filter,
) {
logger.info(
`Loading mask with size ${width} / ${height} to ${x} / ${y} to the canvas`,
);
const canvas = canvases[canvasId];
const palette = new Palette(canvas.colors);
const canvasMinXY = -(canvas.size / 2);
const imageData = new Uint8Array(data.buffer);
const [ucx, ucy] = getChunkOfPixel(canvas.size, x, y);
const [lcx, lcy] = getChunkOfPixel(canvas.size, x + width, y + height);
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
let chunk;
for (let cx = ucx; cx <= lcx; cx += 1) {
for (let cy = ucy; cy <= lcy; cy += 1) {
chunk = await RedisCanvas.getChunk(canvasId, cx, cy);
chunk = (chunk)
? new Uint8Array(chunk)
: new Uint8Array(TILE_SIZE * TILE_SIZE);
// offset of chunk in image
const cOffX = cx * TILE_SIZE + canvasMinXY - x;
const cOffY = cy * TILE_SIZE + canvasMinXY - y;
let cOff = 0;
let pxlCnt = 0;
for (let py = 0; py < TILE_SIZE; py += 1) {
for (let px = 0; px < TILE_SIZE; px += 1) {
const clrX = cOffX + px;
const clrY = cOffY + py;
if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) {
let offset = (clrX + clrY * width) * 3;
if (!imageData[offset++]
&& !imageData[offset++]
&& !imageData[offset]
) {
chunk[cOff] = filter(palette.abgr[chunk[cOff]]);
pxlCnt += 1;
}
}
cOff += 1;
}
}
if (pxlCnt) {
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
if (ret) {
logger.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`);
}
}
chunk = null;
}
}
logger.info('Imagemask loading done.');
}
/*
* Set an area of the canvas to protected
* @param canvasId numerical ID of canvas
* @param x X coordinate on canvas
* @param y Y coordinate on canvas
* @param width Width of image
* @param height height of image
*/
export async function protectCanvasArea(
canvasId: number,
x: number,
y: number,
width: number,
height: number,
protect: boolean = true,
) {
logger.info(
// eslint-disable-next-line max-len
`Setting protection ${protect} with size ${width} / ${height} to ${x} / ${y}`,
);
const canvas = canvases[canvasId];
const canvasMinXY = -(canvas.size / 2);
const [ucx, ucy] = getChunkOfPixel(canvas.size, x, y);
const [lcx, lcy] = getChunkOfPixel(
canvas.size, x + width - 1,
y + height - 1,
);
let chunk;
for (let cx = ucx; cx <= lcx; cx += 1) {
for (let cy = ucy; cy <= lcy; cy += 1) {
chunk = await RedisCanvas.getChunk(canvasId, cx, cy);
if (!chunk) {
continue;
}
chunk = new Uint8Array(chunk);
// offset of area in chunk
const cOffX = x - cx * TILE_SIZE - canvasMinXY;
const cOffY = y - cy * TILE_SIZE - canvasMinXY;
const cOffXE = cOffX + width;
const cOffYE = cOffY + height;
const startX = (cOffX > 0) ? cOffX : 0;
const startY = (cOffY > 0) ? cOffY : 0;
const endX = (cOffXE >= TILE_SIZE) ? TILE_SIZE : cOffXE;
const endY = (cOffYE >= TILE_SIZE) ? TILE_SIZE : cOffYE;
let pxlCnt = 0;
for (let py = startY; py < endY; py += 1) {
for (let px = startX; px < endX; px += 1) {
const offset = px + py * TILE_SIZE;
if (protect) {
chunk[offset] |= 0x80;
} else {
chunk[offset] &= 0x7F;
}
pxlCnt += 1;
}
}
if (pxlCnt) {
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
if (ret) {
// eslint-disable-next-line max-len
logger.info(`Set protection for ${pxlCnt} pixels in chunk ${cx}, ${cy}.`);
}
}
chunk = null;
}
}
logger.info('Setting protection for area done.');
}