Files
scratch-render/src/BitmapSkin.js
Michael "Z" Goddard e31934f6a9 update Skin textures with ImageData
When possible pass ImageData to texture creation and updating to help
remove chance of references that keep canvas and underlying data from
being garbage collected.
2019-03-19 17:52:21 -04:00

140 lines
4.7 KiB
JavaScript

const twgl = require('twgl.js');
const Skin = require('./Skin');
class BitmapSkin extends Skin {
/**
* Create a new Bitmap Skin.
* @extends Skin
* @param {!int} id - The ID for this Skin.
* @param {!RenderWebGL} renderer - The renderer which will use this skin.
*/
constructor (id, renderer) {
super(id);
/** @type {!int} */
this._costumeResolution = 1;
/** @type {!RenderWebGL} */
this._renderer = renderer;
/** @type {WebGLTexture} */
this._texture = null;
/** @type {Array<int>} */
this._textureSize = [0, 0];
}
/**
* Dispose of this object. Do not use it after calling this method.
*/
dispose () {
if (this._texture) {
this._renderer.gl.deleteTexture(this._texture);
this._texture = null;
}
super.dispose();
}
/**
* @returns {boolean} true for a raster-style skin (like a BitmapSkin), false for vector-style (like SVGSkin).
*/
get isRaster () {
return true;
}
/**
* @return {Array<number>} the "native" size, in texels, of this skin.
*/
get size () {
return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution];
}
/**
* @param {Array<number>} scale - The scaling factors to be used.
* @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale.
*/
// eslint-disable-next-line no-unused-vars
getTexture (scale) {
return this._texture;
}
/**
* Get the bounds of the drawable for determining its fenced position.
* @param {Array<number>} drawable - The Drawable instance this skin is using.
* @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB for bitmaps.
*/
getFenceBounds (drawable) {
return drawable.getAABB();
}
/**
* Set the contents of this skin to a snapshot of the provided bitmap data.
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin.
* @param {int} [costumeResolution=1] - The resolution to use for this bitmap.
* @param {Array<number>} [rotationCenter] - Optional rotation center for the bitmap. If not supplied, it will be
* calculated from the bounding box
* @fires Skin.event:WasAltered
*/
setBitmap (bitmapData, costumeResolution, rotationCenter) {
const gl = this._renderer.gl;
// Preferably bitmapData is ImageData. ImageData speeds up updating
// Silhouette and is better handled by more browsers in regards to
// memory.
let textureData = bitmapData;
if (bitmapData instanceof HTMLCanvasElement) {
// Given a HTMLCanvasElement get the image data to pass to webgl and
// Silhouette.
const context = bitmapData.getContext('2d');
textureData = context.getImageData(0, 0, bitmapData.width, bitmapData.height);
}
if (this._texture) {
gl.bindTexture(gl.TEXTURE_2D, this._texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureData);
this._silhouette.update(textureData);
} else {
// TODO: mipmaps?
const textureOptions = {
auto: true,
wrap: gl.CLAMP_TO_EDGE,
src: textureData
};
this._texture = twgl.createTexture(gl, textureOptions);
this._silhouette.update(textureData);
}
// Do these last in case any of the above throws an exception
this._costumeResolution = costumeResolution || 2;
this._textureSize = BitmapSkin._getBitmapSize(bitmapData);
if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter();
this.setRotationCenter.apply(this, rotationCenter);
this.emit(Skin.Events.WasAltered);
}
/**
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - bitmap data to inspect.
* @returns {Array<int>} the width and height of the bitmap data, in pixels.
* @private
*/
static _getBitmapSize (bitmapData) {
if (bitmapData instanceof HTMLImageElement) {
return [bitmapData.naturalWidth || bitmapData.width, bitmapData.naturalHeight || bitmapData.height];
}
if (bitmapData instanceof HTMLVideoElement) {
return [bitmapData.videoWidth || bitmapData.width, bitmapData.videoHeight || bitmapData.height];
}
// ImageData or HTMLCanvasElement
return [bitmapData.width, bitmapData.height];
}
}
module.exports = BitmapSkin;