From c482f6da9a30b5f5f7cc0d851937810f47585bf6 Mon Sep 17 00:00:00 2001 From: Jordan Irwin Date: Wed, 31 Jan 2024 17:29:41 -0800 Subject: [PATCH] Dedicated class for manipulating images before they are displayed --- src/js/stendhal/SingletonRepo.ts | 5 + src/js/stendhal/data/DrawingStage.ts | 139 +++++++++++++++++++++++++++ src/js/stendhal/data/SpriteStore.ts | 19 +--- 3 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 src/js/stendhal/data/DrawingStage.ts diff --git a/src/js/stendhal/SingletonRepo.ts b/src/js/stendhal/SingletonRepo.ts index 92cec32a073..28310c61c85 100644 --- a/src/js/stendhal/SingletonRepo.ts +++ b/src/js/stendhal/SingletonRepo.ts @@ -24,6 +24,7 @@ import { WeatherRenderer } from "./util/WeatherRenderer"; import { CStatus } from "./data/CStatus"; import { CacheManager } from "./data/CacheManager"; +import { DrawingStage } from "./data/DrawingStage"; import { EmojiStore } from "./data/EmojiStore"; import { GroupManager } from "./data/GroupManager"; import { Map } from "./data/Map"; @@ -74,6 +75,10 @@ export class SingletonRepo { return DownloadUtil; } + static getDrawingStage(): DrawingStage { + return DrawingStage.get(); + } + static getEmojiStore(): EmojiStore { return EmojiStore.get(); } diff --git a/src/js/stendhal/data/DrawingStage.ts b/src/js/stendhal/data/DrawingStage.ts new file mode 100644 index 00000000000..1dad14a8283 --- /dev/null +++ b/src/js/stendhal/data/DrawingStage.ts @@ -0,0 +1,139 @@ +/*************************************************************************** + * Copyright © 2024 - Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + + +/** + * Hidden canvas for manipulating images before they are displayed. + */ +export class DrawingStage { + + private canvas: HTMLCanvasElement; + + /** Singleton instance. */ + private static instance: DrawingStage; + + + /** + * Retrieves the singleton instance. + */ + public static get(): DrawingStage { + if (!DrawingStage.instance) { + DrawingStage.instance = new DrawingStage(); + } + return DrawingStage.instance; + } + + /** + * Hidden singleton constructor. + */ + private constructor() { + this.canvas = document.getElementById("drawing-stage")! as HTMLCanvasElement; + // initialize with smallest possible dimensions + this.clear(); + } + + /** + * Retreives the 2D drawing context of the canves. + */ + private getContext(): CanvasRenderingContext2D { + return this.canvas.getContext("2d")! as CanvasRenderingContext2D; + } + + /** + * Clears image data from canvas and sets size to minimum possible dimensions. + */ + public clear() { + this.getContext().clearRect(0, 0, this.canvas.width, this.canvas.height); + this.canvas.width = 0; + this.canvas.height = 0; + } + + /** + * Sets canvas dimensions. + */ + private setSize(width: number, height: number) { + this.canvas.width = width; + this.canvas.height = height; + } + + /** + * Compares canvas dimensions against image and adjusts if necessary. + */ + private checkSize(image: HTMLImageElement) { + if (this.canvas.width < image.width || this.canvas.height < image.height) { + this.setSize(image.width, image.height); + } + } + + /** + * Draws an image on the canvas. + * + * @param image + * Image to be drawn. + * @param clear + * If `true` erases current image data before drawing. + */ + public drawImage(image: HTMLImageElement, clear=true) { + if (clear) { + this.clear(); + } + this.checkSize(image); + this.getContext().drawImage(image, 0, 0); + } + + /** + * Draws a rotated image on the canvas. + * + * NOTE: currently only supports accurate rotation on square image in 90 degree increments + * + * @param image + * Image to be drawn. + * @param angle + * Desired angle of image rotation. + * @param clear + * If `true` erases current image data before drawing. + */ + public drawImageRotated(image: HTMLImageElement, angle: number, clear=true) { + if (clear) { + this.clear(); + } + this.checkSize(image); + const ctx = this.getContext(); + ctx.translate(this.canvas.width / 2, this.canvas.height / 2); + ctx.rotate(angle * Math.PI / 180); + ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2); + // NOTE: do we need to set canvas size again in case of non-square image? + ctx.drawImage(image, 0, 0); + } + + /** + * Converts canvas data to data URL. + * + * @return + * String representation of image data. + */ + public toDataURL(): string { + return this.canvas.toDataURL("image/png"); + } + + /** + * Converts canvas to PNG image. + * + * @return + * New image. + */ + public toImage(): HTMLImageElement { + const image = new Image(); + image.src = this.canvas.toDataURL("image/png"); + return image; + } +} diff --git a/src/js/stendhal/data/SpriteStore.ts b/src/js/stendhal/data/SpriteStore.ts index 0bc953556c9..ad5bbd3ece4 100644 --- a/src/js/stendhal/data/SpriteStore.ts +++ b/src/js/stendhal/data/SpriteStore.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2024 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -11,6 +11,7 @@ declare var stendhal: any; +import { DrawingStage } from "./DrawingStage"; import { Paths } from "./Paths"; @@ -135,19 +136,9 @@ export class SpriteStore { * Angle of rotation. */ private rotate(img: HTMLImageElement, angle: number) { - const canvas = document.getElementById("drawing-stage")!; - const ctx = canvas.getContext("2d")!; - // make sure working with blank canvas - ctx.clearRect(0, 0, canvas.width, canvas.height); - canvas.width = img.width; - canvas.height = img.height; - - ctx.translate(canvas.width / 2, canvas.height / 2); - ctx.rotate(angle * Math.PI / 180); - ctx.translate(-canvas.width / 2, -canvas.height / 2); - ctx.drawImage(img, 0, 0); - - img.src = canvas.toDataURL("image/png"); + const stage = DrawingStage.get(); + stage.drawImageRotated(img, angle); + img.src = stage.toDataURL(); } /**