UNPKG

@storiny/obelisk

Version:

Build isometrics elements with canvas

325 lines (274 loc) 9.07 kB
/* 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]"; } }