const xmlEscape = require('../../util/xml-escape'); const uid = require('../../util/uid'); const StageLayering = require('../../engine/stage-layering'); class CanvasVar { static customId = 'canvasData' /** * initiats the variable * @param {Runtime} runtime the runtime this canvas exists inside * @param {string} id this canvas's id * @param {string} name the name of this canvas * @param {[number,number]|string|Image} [img=[1, 1]] optionally the image to be loaded into this canvas */ constructor (runtime, id, name, img = [1, 1]) { this.id = id ?? uid(); this.name = name; this.type = 'canvas'; this.customId = CanvasVar.customId; this.runtime = runtime; this.renderer = runtime.renderer; this.canvas = document.createElement('canvas'); this._costumeDrawer = this.renderer.createDrawable(StageLayering.SPRITE_LAYER); this._skinId = this.renderer.createBitmapSkin(this.canvas, 1); this._monitorUpToDate = false; this._cachedMonContent = [null, 0]; this._cameraStuff = { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1 }; // img is just a size to be given to the canvas if (Array.isArray(img)) { this.size = img; return; } if (img) this.loadImage(img); } serialize(canvas) { const instance = canvas ?? this; return [instance.id, instance.name, instance.canvas.toDataURL()]; } getSnapshot() { const snap = new Image(); snap.src = this.canvas.toDataURL(); return snap; } toReporterContent() { return this.canvas; } toMonitorContent() { if (!this._monitorUpToDate) { this._cachedMonContent = this.getSnapshot(); this._monitorUpToDate = true; } return this._cachedMonContent; } toListEditor() { return this.toString(); } fromListEditor(edit) { if (this.toString() !== edit) { this.loadImage(edit); } return this; } toString() { return this.canvas.toDataURL(); } toXML(isLocal) { return `${xmlEscape(this.name)}`; } toToolboxDefault(fieldName) { return `${xmlEscape(this.name)}`; } get size() { return [this.canvas.width, this.canvas.height]; } set size(size) { this.canvas.width = size[0]; this.canvas.height = size[1]; } /** * load an image onto the 2d canvas * @param {Image} img the image to load onto the 2d canvas */ async loadImage(img) { // we where not given something we can use imediatly :( if (img instanceof Image && !img.complete) { await new Promise(resolve => { img.onload = resolve; img.onerror = resolve; }); } if (typeof img === 'string') { await new Promise(resolve => { const src = img; img = new Image(); img.onload = resolve; img.onerror = resolve; img.src = src; }); } this.canvas.width = img.width; this.canvas.height = img.height; const ctx = this.canvas.getContext('2d'); ctx.drawImage(img, 0, 0); // do this cause we just added new content this.updateCanvasContentRenders(); } stampDrawable(id, x, y) { // drawable doesnt exist, we will get an error if we try to access this drawable if (!this.renderer._allDrawables[id]) return; const drawable = this.renderer.extractDrawableScreenSpace(id); // never got any data, ignore request if (!drawable) return; const ctx = this.canvas.getContext('2d'); ctx.putImageData(drawable.imageData, x, y); } stampCostume(target, costumeName, x, y) { const skin = costumeName !== '__current__' ? (() => { const costumeIdx = target.getCostumeIndexByName(costumeName); const costumeList = target.getCostumes(); const costume = costumeList[costumeIdx]; return this.renderer._allSkins[costume.skinId]; })() : this.renderer._allDrawables[target.drawableID].skin; const ctx = this.canvas.getContext('2d'); // draw svg skins loaded image element if (skin._svgImage) { ctx.drawImage(skin._svgImage, x, y); return; } // draw the generated content of TextCostumeSkin or TextBubbleSkin directly if (skin._canvas) { ctx.drawImage(skin._canvas, x, y); return; } // shit, alright we cant just goofy ahh our way through this // we need to somehow request some form of image that we can just draw to the canvas // from either the webgl texture that the skin gives us or the sprite /** * TODO: please if someone could make this not shitty ass and make it just draw a * fucking webgl texture directly that would be amazing */ this.renderer.updateDrawableSkinId(this._costumeDrawer, skin.id); this.stampDrawable(this._costumeDrawer); } updateCanvasContentRenders() { this._monitorUpToDate = false; // if width or height are smaller then one, replace them with one const width = Math.max(this.canvas.width, 1); const height = Math.max(this.canvas.height, 1); const ctx = this.canvas.getContext('2d'); const printSkin = this.renderer._allSkins[this._skinId]; const imageData = ctx.getImageData(0, 0, width, height); printSkin._setTexture(imageData); } applyCanvasToTarget(target) { this.renderer.updateDrawableSkinId(target.drawableID, this._skinId); this.runtime.requestRedraw(); } } module.exports = CanvasVar;