diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 186eb16..fbfda2a 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -5,6 +5,8 @@ const SvgRenderer = require('./svg-quirks-mode/svg-renderer'); const Silhouette = require('./Silhouette'); +const MAX_TEXTURE_DIMENSION = 2048; + class SVGSkin extends Skin { /** * Create a new SVG skin. @@ -25,6 +27,12 @@ class SVGSkin extends Skin { /** @type {WebGLTexture} */ this._texture = null; + /** @type {number} */ + this._textureScale = 1; + + /** @type {Number} */ + this._maxTextureScale = 0; + this._silhouette = new Silhouette(); } @@ -57,11 +65,29 @@ class SVGSkin extends Skin { } /** - * @param {Array} scale - The scaling factors to be used. + * @param {Array} scale - The scaling factors to be used, each in the [0,100] range. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. */ // eslint-disable-next-line no-unused-vars getTexture (scale) { + // The texture only ever gets uniform scale. Take the larger of the two axes. + const scaleMax = scale ? Math.max(Math.abs(scale[0]), Math.abs(scale[1])) : 100; + const requestedScale = Math.min(scaleMax / 100, this._maxTextureScale); + let newScale = this._textureScale; + while ((newScale < this._maxTextureScale) && (requestedScale >= 1.5 * newScale)) { + newScale *= 2; + } + if (this._textureScale !== newScale) { + this._textureScale = newScale; + this._svgRenderer._draw(this._textureScale, () => { + if (this._textureScale === newScale) { + const gl = this._renderer.gl; + gl.bindTexture(gl.TEXTURE_2D, this._texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._svgRenderer.canvas); + } + }); + } + /** @todo re-render a scaled version if the requested scale is significantly larger than the current render */ return this._texture; } @@ -74,8 +100,10 @@ class SVGSkin extends Skin { * @fires Skin.event:WasAltered */ setSVG (svgData, rotationCenter) { - this._svgRenderer.fromString(svgData, () => { + this._svgRenderer.fromString(svgData, 1, () => { const gl = this._renderer.gl; + this._textureScale = this._maxTextureScale = 1; + this._svgRenderer.extraScale = 1; if (this._texture) { gl.bindTexture(gl.TEXTURE_2D, this._texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._svgRenderer.canvas); @@ -91,6 +119,13 @@ class SVGSkin extends Skin { this._texture = twgl.createTexture(gl, textureOptions); this._silhouette.update(this._svgRenderer.canvas); } + + const maxDimension = Math.max(this._svgRenderer.canvas.width, this._svgRenderer.canvas.height); + let testScale = 2; + for (testScale; maxDimension * testScale <= MAX_TEXTURE_DIMENSION; testScale *= 2) { + this._maxTextureScale = testScale; + } + if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.setRotationCenter.apply(this, rotationCenter); this.emit(Skin.Events.WasAltered); diff --git a/src/playground/index.html b/src/playground/index.html index e64d503..0d041c3 100644 --- a/src/playground/index.html +++ b/src/playground/index.html @@ -43,17 +43,35 @@ scale: [100, 100], direction: 90 }); + var drawableID2 = renderer.createDrawable(); + var wantBitmapSkin = false; + + // Bitmap (squirrel) var image = new Image(); image.onload = function () { - var skinId = renderer.createBitmapSkin(image); - renderer.updateDrawableProperties(drawableID2, { - skinId: skinId - }); + var bitmapSkinId = renderer.createBitmapSkin(image); + if (wantBitmapSkin) { + renderer.updateDrawableProperties(drawableID2, { + skinId: bitmapSkinId + }); + } }; image.crossOrigin = 'anonymous'; - image.src = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + - '09dc888b0b7df19f70d81588ae73420e.svg/get/'; + image.src = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/7e24c99c1b853e52f8e7f9004416fa34.png/get/'; + + // SVG (cat 1-a) + var xhr = new XMLHttpRequest(); + xhr.addEventListener("load", function () { + var skinId = renderer.createSVGSkin(xhr.responseText); + if (!wantBitmapSkin) { + renderer.updateDrawableProperties(drawableID2, { + skinId: skinId + }); + } + }); + xhr.open('GET', 'https://cdn.assets.scratch.mit.edu/internalapi/asset/f88bf1935daea28f8ca098462a31dbb0.svg/get/'); + xhr.send(); var fudgeSelect = document.getElementById('fudgeproperty'); var posX = 0; diff --git a/src/svg-quirks-mode/svg-renderer.js b/src/svg-quirks-mode/svg-renderer.js index 5b6147c..e679ab6 100644 --- a/src/svg-quirks-mode/svg-renderer.js +++ b/src/svg-quirks-mode/svg-renderer.js @@ -52,14 +52,12 @@ class SvgRenderer { * This will be parsed and transformed, and finally drawn. * When drawing is finished, the `onFinish` callback is called. * @param {string} svgString String of SVG data to draw in quirks-mode. + * @param {number} [scale] - Optionally, also scale the image by this factor (multiplied by `getDrawRatio()`). * @param {Function} [onFinish] Optional callback for when drawing finished. */ - fromString (svgString, onFinish) { - // Store the callback for later. - this._onFinish = onFinish; + fromString (svgString, scale, onFinish) { this._loadString(svgString); - // Draw to a canvas. - this._draw(); + this._draw(scale, onFinish); } /** @@ -298,10 +296,12 @@ class SvgRenderer { } /** - * Draw the SVG to a canvas. + * Draw the SVG to a canvas. The canvas will automatically be scaled by the value returned by `getDrawRatio`. + * @param {number} [scale] - Optionally, also scale the image by this factor (multiplied by `getDrawRatio()`). + * @param {Function} [onFinish] - An optional callback to call when the draw operation is complete. */ - _draw () { - const ratio = this.getDrawRatio(); + _draw (scale, onFinish) { + const ratio = this.getDrawRatio() * (Number.isFinite(scale) ? scale : 1); const bbox = this._measurements; // Convert the SVG text to an Image, and then draw it to the canvas. @@ -319,8 +319,8 @@ class SvgRenderer { this._canvas.style.width = bbox.width; this._canvas.style.height = bbox.height; // All finished - call the callback if provided. - if (this._onFinish) { - this._onFinish(); + if (onFinish) { + onFinish(); } }; const svgText = this._toString();