Skip to content

Commit

Permalink
Merge pull request #208 from NicDoesCode/release_1-1-1
Browse files Browse the repository at this point in the history
Release 1.1.1
  • Loading branch information
NicDoesCode authored Sep 10, 2023
2 parents f7ca258 + e442952 commit 6d9f7e7
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 206 deletions.
112 changes: 73 additions & 39 deletions wwwroot/modules/components/canvasManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import TileGridProvider from "../models/tileGridProvider.js";
import Tile from "../models/tile.js";
import TileMap from "../models/tileMap.js";
import TileMapFactory from "../factory/tileMapFactory.js";
import PaintUtil from "../util/paintUtil.js";
import TileImageManager from "./tileImageManager.js";


const highlightModes = {
pixel: 'pixel',
Expand All @@ -22,6 +25,11 @@ const highlightModes = {
columnBlockIndex: 'columnBlockIndex'
}


/**
* The CanvasManager class renders a tile grid onto a HTMLCanvasElement, as well as other features
* such as the tile / pixel grid, the cursor, etc.
*/
export default class CanvasManager {


Expand Down Expand Up @@ -176,48 +184,69 @@ export default class CanvasManager {
this.#transparencyIndex = value;
}

/**
* Gets or sets the X offset of the tile grid image.
*/
get offsetX() {
return this.#offsetX;
}
set offsetX(value) {
this.#offsetX = value;
}

/**
* Gets or sets the Y offset of the tile grid image.
*/
get offsetY() {
return this.#offsetY;
}
set offsetY(value) {
this.#offsetY = value;
}

/**
* Gets or sets the background colour of the viewport, set to null for transparency.
*/
get backgroundColour() {
return this.#backgroundColour;
}
set backgroundColour(value) {
this.#backgroundColour = value;
}

/**
* Gets or sets the colour of the pixel grid.
*/
get pixelGridColour() {
return this.#pixelGridColour;
}
set pixelGridColour(value) {
this.#pixelGridColour = value;
}

/**
* Gets or sets the opacity of the pixel grid.
*/
get pixelGridOpacity() {
return this.#pixelGridOpacity;
}
set pixelGridOpacity(value) {
this.#pixelGridOpacity = value;
}

/**
* Gets or sets the colour of the tile grid.
*/
get tileGridColour() {
return this.#tileGridColour;
}
set tileGridColour(value) {
this.#tileGridColour = value;
}

/**
* Gets or sets the opacity of the tile grid.
*/
get tileGridOpacity() {
return this.#tileGridOpacity;
}
Expand All @@ -238,6 +267,8 @@ export default class CanvasManager {
#tilePreviewCanvas = null;
/** @type {HTMLCanvasElement} */
#gridPatternCanvas = null;
/** @type {TileImageManager} */
#tileImageManager = {};
/** @type {TileSet} */
#tileSet = null;
/** @type {PaletteList} */
Expand Down Expand Up @@ -269,7 +300,7 @@ export default class CanvasManager {


/**
* Creates a new instance of the tile canvas.
* Constructor for the class.
* @param {TileGridProvider} [tileGrid] - Object that contains the tile grid to render.
* @param {TileSet} [tileSet] - Tile set that is the source of tiles.
* @param {PaletteList} [paletteList] - Colour palette list to use.
Expand All @@ -279,13 +310,15 @@ export default class CanvasManager {
if (tileGrid) this.#tileGrid = tileGrid;
if (tileSet) this.#tileSet = tileSet;
if (paletteList) this.#paletteList = paletteList;
this.#tileImageManager = new TileImageManager();
}


/**
* Invalidates the tile set image and forces a redraw.
*/
invalidateImage() {
// Set redraw variables
this.#needToDrawTileImage = true;
this.#tilePreviewCanvas = null;
}
Expand All @@ -295,6 +328,7 @@ export default class CanvasManager {
* @param {number} index - Index of the tile to invalidate.
*/
invalidateTile(index) {
// Set redraw variables
this.#redrawTiles.push(index);
this.#tilePreviewCanvas = null;
}
Expand All @@ -305,8 +339,9 @@ export default class CanvasManager {
*/
invalidateTileId(tileId) {
this.tileGrid.getTileIdIndexes(tileId).forEach((index) => {
this.invalidateTile(index);
this.#redrawTiles.push(index);
});
this.#tilePreviewCanvas = null;
}


Expand All @@ -320,6 +355,19 @@ export default class CanvasManager {
}


/**
* Sets or clears the tile image manager.
* @param {TileImageManager?} tileImageManager - Tile image manager to set.
*/
setTileImageManager(tileImageManager) {
if (tileImageManager && tileImageManager instanceof TileImageManager) {
this.#tileImageManager = tileImageManager;
} else {
this.#tileImageManager = new TileImageManager();
}
}


/**
* Sets the tile preview image, this will be drawn over the tile index where the mouse is.
* @param {string|Tile|TileMap} tileOrTileIdOrTileMap
Expand Down Expand Up @@ -622,48 +670,34 @@ export default class CanvasManager {
const tileGridCol = tileindex % tileWidth;
const tileGridRow = (tileindex - tileGridCol) / tileWidth;
const palette = this.paletteList.getPalette(tileInfo.paletteIndex);
const numColours = palette.getColours().length;

context.imageSmoothingEnabled = false;

if (tile) {
// Tile exists
let pixelPaletteIndex = 0;
for (let tilePx = 0; tilePx < 64; tilePx++) {

const tileCol = tilePx % 8;
const tileRow = (tilePx - tileCol) / 8;

const x = ((tileGridCol * 8) + tileCol) * pxSize;
const y = ((tileGridRow * 8) + tileRow) * pxSize;

if (tileInfo.horizontalFlip && tileInfo.verticalFlip) {
pixelPaletteIndex = tile.readAt(63 - tilePx);
} else if (tileInfo.horizontalFlip) {
const readPx = (tileRow * 8) + (7 - tileCol);
pixelPaletteIndex = tile.readAt(readPx);
} else if (tileInfo.verticalFlip) {
const readPx = ((7 - tileRow) * 8) + tileCol;
pixelPaletteIndex = tile.readAt(readPx);
} else {
pixelPaletteIndex = tile.readAt(tilePx);
}

// Set colour of the pixel
if (pixelPaletteIndex >= 0 && pixelPaletteIndex < numColours) {
const colour = palette.getColour(pixelPaletteIndex);
const hex = ColourUtil.toHex(colour.r, colour.g, colour.b);
context.fillStyle = hex;
}
const tileCanvas = this.#tileImageManager.getTileImage(tile, palette, transparencyColour);

if (pixelPaletteIndex !== transparencyColour) {
// Pixel colour is different to transparency, so draw it.
context.fillRect(x, y, pxSize, pxSize);
} else {
// If this pixel colour is the colour of transparency, then it shouldn't be
// drawn, so clear it instead of drawing it.
context.clearRect(x, y, pxSize, pxSize);
}
// Tile exists
const x = tileGridCol * 8 * pxSize;
const y = tileGridRow * 8 * pxSize;
const sizeXY = pxSize * 8;
if (tileInfo.horizontalFlip && tileInfo.verticalFlip) {
context.scale(-1, -1);
context.drawImage(tileCanvas, -x, -y, -sizeXY, -sizeXY);
context.setTransform(1, 0, 0, 1, 0, 0);
} else if (tileInfo.horizontalFlip) {
context.scale(-1, 1);
context.drawImage(tileCanvas, -x, y, -sizeXY, sizeXY);
context.setTransform(1, 0, 0, 1, 0, 0);
} else if (tileInfo.verticalFlip) {
context.scale(1, -1);
context.drawImage(tileCanvas, x, -y, sizeXY, -sizeXY);
context.setTransform(1, 0, 0, 1, 0, 0);
} else {
context.drawImage(tileCanvas, x, y, sizeXY, sizeXY);
}
} else {
this.#tileImageManager.clearByTile(tileInfo.tileId);
// Draw in pixel mesh when the tile doesn't exist
const originX = (tileGridCol * 8) * pxSize;
const originY = (tileGridRow * 8) * pxSize;
Expand Down Expand Up @@ -1281,4 +1315,4 @@ function createGridPatternCanvas(scale) {
}
}
return gridCanvas;
}
}
99 changes: 99 additions & 0 deletions wwwroot/modules/components/tileImageManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import Tile from "./../models/tile.js";
import Palette from "./../models/palette.js";
import PaintUtil from "./../util/paintUtil.js";


/**
* Acts as a shared tile image cache.
*/
export default class TileImageManager {


/** @type {Object.<string, Object<string, Object<string, HTMLCanvasElement>>>} */
#tileCanvases = {};


/**
* Constructor for the class.
*/
constructor() {
}


/**
* Gets the image for the tile.
* @param {Tile} tile - Tile that we get the image for.
* @param {Palette} palette - Colour palette to use.
* @param {number?} transparencyColour
* @returns {HTMLCanvasElement}
*/
getTileImage(tile, palette, transparencyColour) {
if (typeof transparencyColour !== 'number' || transparencyColour < 0 || transparencyColour >= 16) {
transparencyColour = -1;
}

let tileRec = this.#tileCanvases[tile.tileId];
if (!tileRec) {
tileRec = {};
this.#tileCanvases[tile.tileId] = tileRec;
}

let paletteRec = tileRec[palette.paletteId];
if (!paletteRec) {
paletteRec = {};
this.#tileCanvases[tile.tileId][palette.paletteId] = paletteRec;
}

let transId = `${transparencyColour}`;
let transRec = paletteRec[transId]
if (!transRec) {
transRec = PaintUtil.createTileCanvas(tile, palette, transparencyColour);
this.#tileCanvases[tile.tileId][palette.paletteId][transId] = transRec;
}

return transRec;
}


/**
* Clears all cached tile images.
*/
clear() {
const keys = Object.keys(this.#tileCanvases);
keys.forEach((tileId) => this.clearByTile(tileId));
}

/**
* Clears cached tile images for a particular tile.
* @param {string|string[]} value - Array or single unique ID of the tile whose cached images are to be discarded.
*/
clearByTile(value) {
if (!value) return;
if (Array.isArray(value)) {
value.forEach((tileId) => this.clearByTile(tileId));
} else if (value && typeof value === 'string' && value.length > 0) {
if (this.#tileCanvases[value]) {
delete this.#tileCanvases[value];
}
}
}

/**
* Clears cached tile images for a particular palette.
* @param {string|string[]} value - Array or single unique ID of the palette whose cached images are to be discarded.
*/
clearByPalette(value) {
if (!value) return;
if (Array.isArray(value)) {
value.forEach((paletteId) => this.clearByPalette(paletteId));
} else if (value && typeof value === 'string' && value.length > 0) {
Object.keys(this.#tileCanvases).forEach((tileId) => {
if (this.#tileCanvases[tileId][value]) {
delete this.#tileCanvases[tileId][value];
}
});
}
}


}
Loading

0 comments on commit 6d9f7e7

Please sign in to comment.