Merge pull request #232 from cwillisf/rasterize-scaled-svg
Re-rasterize SVGs when scaled up
This commit is contained in:
@@ -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,12 +65,29 @@ class SVGSkin extends Skin {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<number>} scale - The scaling factors to be used.
|
||||
* @param {Array<number>} 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) {
|
||||
/** @todo re-render a scaled version if the requested scale is significantly larger than the current render */
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this._texture;
|
||||
}
|
||||
|
||||
@@ -74,8 +99,9 @@ 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;
|
||||
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 +117,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -38,6 +38,7 @@ class SvgRenderer {
|
||||
this._canvas = canvas || document.createElement('canvas');
|
||||
this._context = this._canvas.getContext('2d');
|
||||
this._measurements = {x: 0, y: 0, width: 0, height: 0};
|
||||
this._cachedImage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,14 +53,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,6 +90,9 @@ class SvgRenderer {
|
||||
* @param {string} svgString String of SVG data to draw in quirks-mode.
|
||||
*/
|
||||
_loadString (svgString) {
|
||||
// New svg string invalidates the cached image
|
||||
this._cachedImage = null;
|
||||
|
||||
// Parse string into SVG XML.
|
||||
const parser = new DOMParser();
|
||||
this._svgDom = parser.parseFromString(svgString, 'text/xml');
|
||||
@@ -298,33 +300,49 @@ 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();
|
||||
const bbox = this._measurements;
|
||||
|
||||
_draw (scale, onFinish) {
|
||||
// Convert the SVG text to an Image, and then draw it to the canvas.
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
// Set up the canvas for drawing.
|
||||
this._canvas.width = bbox.width * ratio;
|
||||
this._canvas.height = bbox.height * ratio;
|
||||
this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
this._context.scale(ratio, ratio);
|
||||
this._context.drawImage(img, 0, 0);
|
||||
// Reset the canvas transform after drawing.
|
||||
this._context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
// Set the CSS style of the canvas to the actual measurements.
|
||||
this._canvas.style.width = bbox.width;
|
||||
this._canvas.style.height = bbox.height;
|
||||
// All finished - call the callback if provided.
|
||||
if (this._onFinish) {
|
||||
this._onFinish();
|
||||
}
|
||||
};
|
||||
const svgText = this._toString();
|
||||
img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
|
||||
if (this._cachedImage) {
|
||||
this._drawFromImage(scale, onFinish);
|
||||
} else {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
this._cachedImage = img;
|
||||
this._drawFromImage(scale, onFinish);
|
||||
};
|
||||
const svgText = this._toString();
|
||||
img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw to the canvas from a loaded image element.
|
||||
* @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.
|
||||
**/
|
||||
_drawFromImage (scale, onFinish) {
|
||||
if (!this._cachedImage) return;
|
||||
|
||||
const ratio = this.getDrawRatio() * (Number.isFinite(scale) ? scale : 1);
|
||||
const bbox = this._measurements;
|
||||
this._canvas.width = bbox.width * ratio;
|
||||
this._canvas.height = bbox.height * ratio;
|
||||
this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
this._context.scale(ratio, ratio);
|
||||
this._context.drawImage(this._cachedImage, 0, 0);
|
||||
// Reset the canvas transform after drawing.
|
||||
this._context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
// Set the CSS style of the canvas to the actual measurements.
|
||||
this._canvas.style.width = bbox.width;
|
||||
this._canvas.style.height = bbox.height;
|
||||
// All finished - call the callback if provided.
|
||||
if (onFinish) {
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user