@storiny/obelisk
Version:
Build isometrics elements with canvas
325 lines (274 loc) • 9.07 kB
text/typescript
/* eslint-disable no-bitwise */
import { CanvasManager } from "../utils/CanvasManager";
export class BitmapData {
public imageData: ImageData | null;
public canvas: HTMLCanvasElement | null;
public context: CanvasRenderingContext2D | null;
constructor(w?: number, h?: number, useDefaultCanvas?: boolean) {
if (w === undefined || h === undefined) {
throw new Error("BitmapData width or height is missing");
}
if (useDefaultCanvas) {
this.canvas = CanvasManager.getDefaultCanvas();
} else {
this.canvas = CanvasManager.getNewCanvas();
}
this.imageData = null;
this.context = null;
if (this.canvas) {
this.canvas.setAttribute("width", w.toString());
this.canvas.setAttribute("height", h.toString());
this.context = this.canvas.getContext("2d") || null;
if (this.context) {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
(this.context as any).mozImageSmoothingEnabled = false;
(this.context as any).msImageSmoothingEnabled = false;
this.context.imageSmoothingEnabled = false;
this.imageData = this.context.createImageData(w, h);
}
}
}
public setPixel(posX: number, posY: number, color: number): void {
if (this.imageData) {
const index = (posY * this.imageData.width + posX) * 4;
this.setPixelByIndex(index, color);
}
}
public setPixelByIndex(index: number, color: number): void {
if (this.imageData) {
const pixels = this.imageData.data;
pixels[index] = (color >>> 16) & 0xff;
pixels[index + 1] = (color >>> 8) & 0xff;
pixels[index + 2] = (color >>> 0) & 0xff;
pixels[index + 3] = (color >>> 24) & 0xff;
}
}
public checkPixelAvailable(x: number, y: number): boolean {
if (this.imageData) {
const index = (y * this.imageData.width + x) * 4;
return this.imageData.data[index + 3] === 0;
}
return false;
}
public floodFill(posX: number, posY: number, color: number): void {
if (((color >>> 24) & 0xff) === 0x00 || !this.imageData) {
// Transparent flood fill
return;
}
let x = posX;
let y = posY;
const stack: number[] = [];
let nowCol: number[] = [];
let prevCol: number[] = [];
let col: number;
let row: number;
let matchFlag: boolean;
let newStart: number;
const w = this.imageData.width;
const h = this.imageData.height;
let i: number;
let j: number;
// Bound reach
if (x < 0 || y < 0 || x >= w || y >= h) {
return;
}
// First point check fail
if (!this.checkPixelAvailable(x, y)) {
throw new Error("Start point for flood fill is already filled");
}
// Left side flood fill
for (col = x; col >= 0; col -= 1) {
// Top side
for (row = y; row >= 0; row -= 1) {
if (this.checkPixelAvailable(col, row)) {
// Available pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
// First one is invalid pixel and not at col top
if (row === y && this.checkPixelAvailable(col + 1, row - 1)) {
// Next one is valid
if (this.checkPixelAvailable(col, row - 1)) {
newStart = row - 1;
} else if (this.checkPixelAvailable(col + 1, row - 2)) {
newStart = row - 2;
} else {
// Fail, assign max value to avoid loop below
newStart = -1;
}
for (row = newStart; row >= 0; row -= 1) {
if (this.checkPixelAvailable(col, row)) {
// Available pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
break;
}
}
}
break;
}
}
// Bottom side
for (row = y; row < h; row += 1) {
if (this.checkPixelAvailable(col, row)) {
// Available pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
// First one is invalid pixel and not at col bottom
if (row === y && this.checkPixelAvailable(col + 1, row + 1)) {
// Next one is valid
if (this.checkPixelAvailable(col, row + 1)) {
newStart = row + 1;
} else if (this.checkPixelAvailable(col + 1, row + 2)) {
newStart = row + 2;
} else {
// Fail, assign max value to avoid loop below
newStart = h;
}
for (row = newStart; row < h; row += 1) {
if (this.checkPixelAvailable(col, row)) {
// AAvailable pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
break;
}
}
}
break;
}
}
// Compare with previous column
// for first column,
// the given point should be inside the container
if (col === x) {
prevCol = nowCol.concat();
}
matchFlag = false;
for (i = 0; i < prevCol.length; i += 1) {
for (j = 0; j < prevCol.length; j += 1) {
if (nowCol[j] === prevCol[i]) {
matchFlag = true;
y = prevCol[i]!;
break;
}
}
if (matchFlag) {
break;
}
}
if (matchFlag) {
prevCol = nowCol.concat();
nowCol = [];
} else {
// Bound reach
break;
}
}
// Reset start point
x = posX;
y = posY;
prevCol = [];
nowCol = [];
// Right side flood fill
for (col = x; col < w; col += 1) {
// Top side
for (row = y; row >= 0; row -= 1) {
if (this.checkPixelAvailable(col, row)) {
// Available pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
// First one is invalid pixel and not at col top
if (row === y && this.checkPixelAvailable(col - 1, row - 1)) {
// Next one is valid
if (this.checkPixelAvailable(col, row - 1)) {
newStart = row - 1;
} else if (this.checkPixelAvailable(col - 1, row - 2)) {
newStart = row - 2;
} else {
// Fail, assign max value to avoid loop below
newStart = -1;
}
for (row = newStart; row >= 0; row -= 1) {
if (this.checkPixelAvailable(col, row)) {
// Available pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
break;
}
}
}
break;
}
}
// Bottom side
for (row = y; row < h; row += 1) {
if (this.checkPixelAvailable(col, row)) {
// Available pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
// First one is invalid pixel && not at col bottom
if (row === y && this.checkPixelAvailable(col - 1, row + 1)) {
// Next one is valid
if (this.checkPixelAvailable(col, row + 1)) {
newStart = row + 1;
} else if (this.checkPixelAvailable(col - 1, row + 2)) {
newStart = row + 2;
} else {
// Fail, assign max value to avoid loop below
newStart = h;
}
for (row = newStart; row < h; row += 1) {
if (this.checkPixelAvailable(col, row)) {
// Available pixel
stack.push((row * w + col) * 4);
nowCol.push(row);
} else {
break;
}
}
}
break;
}
}
// Compare with previous column
// for first column,
// the given point should be inside the container
if (col === x) {
prevCol = nowCol.concat();
}
matchFlag = false;
for (i = 0; i < prevCol.length; i += 1) {
for (j = 0; j < prevCol.length; j += 1) {
if (nowCol[j] === prevCol[i]) {
matchFlag = true;
y = prevCol[i]!;
break;
}
}
if (matchFlag) {
break;
}
}
if (matchFlag) {
prevCol = nowCol.concat();
nowCol = [];
} else {
// Bound reach
break;
}
}
// Fill image data
for (i = 0; i < stack.length; i += 1) {
this.setPixelByIndex(stack[i]!, color);
}
}
public static toString(): string {
return "[BitmapData]";
}
}