From 6aefd13af602bf8028df5fa659e273fcd5c354ae Mon Sep 17 00:00:00 2001 From: DD Liu Date: Tue, 10 Jul 2018 10:21:21 -0400 Subject: [PATCH 1/7] Break load costume into more functions, and update how bitmaps are loaded --- src/engine/runtime.js | 9 +++ src/import/load-costume.js | 162 ++++++++++++++++++++++--------------- src/virtual-machine.js | 8 ++ 3 files changed, 114 insertions(+), 65 deletions(-) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 530db3b40..9db91ad9d 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -995,6 +995,15 @@ class Runtime extends EventEmitter { this.v2SvgAdapter = svgAdapter; } + /** + * Set the bitmap adapter for the VM/runtime, which converts scratch 2 + * bitmaps to scratch 3 bitmaps. (Scratch 3 bitmaps are all bitmap resolution 2) + * @param {!function} bitmapAdapter The adapter to attach + */ + attachV2BitmapAdapter (bitmapAdapter) { + this.v2BitmapAdapter = bitmapAdapter; + } + /** * Attach the storage module * @param {!ScratchStorage} storage The storage module to attach diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 4cd04323c..9b9e6f86f 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -1,6 +1,99 @@ const StringUtil = require('../util/string-util'); const log = require('../util/log'); +const loadVector_ = function (costume, costumeAsset, runtime, rotationCenter, optVersion) { + let svgString = costumeAsset.decodeText(); + // SVG Renderer load fixes "quirks" associated with Scratch 2 projects + if (optVersion && optVersion === 2 && !runtime.v2SvgAdapter) { + log.error('No V2 SVG adapter present; SVGs may not render correctly.'); + } else if (optVersion && optVersion === 2 && runtime.v2SvgAdapter) { + runtime.v2SvgAdapter.loadString(svgString, true /* fromVersion2 */); + svgString = runtime.v2SvgAdapter.toString(); + // Put back into storage + const storage = runtime.storage; + costumeAsset.encodeTextData(svgString, storage.DataFormat.SVG); + costume.assetId = storage.builtinHelper.cache( + storage.AssetType.ImageVector, + storage.DataFormat.SVG, + costumeAsset.data + ); + costume.md5 = `${costume.assetId}.${costume.dataFormat}`; + } + // createSVGSkin does the right thing if rotationCenter isn't provided, so it's okay if it's + // undefined here + costume.skinId = runtime.renderer.createSVGSkin(svgString, rotationCenter); + costume.size = runtime.renderer.getSkinSize(costume.skinId); + // Now we should have a rotationCenter even if we didn't before + if (!rotationCenter) { + rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId); + costume.rotationCenterX = rotationCenter[0]; + costume.rotationCenterY = rotationCenter[1]; + } + + return costume; +}; + +const loadBitmap_ = function (costume, costumeAsset, runtime, rotationCenter) { + return new Promise((resolve, reject) => { + const imageElement = new Image(); + const onError = function () { + // eslint-disable-next-line no-use-before-define + removeEventListeners(); + reject(); + }; + const onLoad = function () { + // eslint-disable-next-line no-use-before-define + removeEventListeners(); + resolve(imageElement); + }; + const removeEventListeners = function () { + imageElement.removeEventListener('error', onError); + imageElement.removeEventListener('load', onLoad); + }; + imageElement.addEventListener('error', onError); + imageElement.addEventListener('load', onLoad); + let src = costumeAsset.encodeDataURI(); + if (costume.bitmapResolution === 1 && !runtime.v2BitmapAdapter) { + log.error('No V2 bitmap adapter present; bitmaps may not render correctly.'); + } else if (costume.bitmapResolution === 1) { + src = runtime.v2BitmapAdapter.convertResolution1Bitmap(src, dataURI => { + if (dataURI) { + // Put back into storage + const storage = runtime.storage; + costume.assetId = storage.builtinHelper.cache( + storage.AssetType.ImageBitmap, + storage.DataFormat.PNG, + runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI) + ); + costume.md5 = `${costume.assetId}.${costume.dataFormat}`; + if (rotationCenter) { + rotationCenter[0] = rotationCenter[0] * 2; + rotationCenter[1] = rotationCenter[1] * 2; + costume.rotationCenterX = rotationCenter[0]; + costume.rotationCenterY = rotationCenter[1]; + } + costume.bitmapResolution = 2; + } + imageElement.src = dataURI ? dataURI : src; // Use original src if conversion fails + }); + } else { + imageElement.src = src; + } + }).then(imageElement => { + // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined... + costume.skinId = runtime.renderer.createBitmapSkin(imageElement, costume.bitmapResolution, rotationCenter); + const renderSize = runtime.renderer.getSkinSize(costume.skinId); + costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2 + + if (!rotationCenter) { + rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId); + costume.rotationCenterX = rotationCenter[0] * 2; + costume.rotationCenterY = rotationCenter[1] * 2; + } + return costume; + }); +}; + /** * Initialize a costume from an asset asynchronously. * Do not call this unless there is a renderer attached. @@ -27,74 +120,13 @@ const loadCostumeFromAsset = function (costume, costumeAsset, runtime, optVersio // Use provided rotation center and resolution if they are defined. Bitmap resolution // should only ever be 1 or 2. if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) && - typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY) && - costume.bitmapResolution) { - rotationCenter = [ - costume.rotationCenterX / costume.bitmapResolution, - costume.rotationCenterY / costume.bitmapResolution - ]; + typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) { + rotationCenter = [costume.rotationCenterX, costume.rotationCenterY]; } if (costumeAsset.assetType === AssetType.ImageVector) { - let svgString = costumeAsset.decodeText(); - // SVG Renderer load fixes "quirks" associated with Scratch 2 projects - if (optVersion && optVersion === 2 && runtime.v2SvgAdapter) { - runtime.v2SvgAdapter.loadString(svgString, true /* fromVersion2 */); - svgString = runtime.v2SvgAdapter.toString(); - // Put back into storage - const storage = runtime.storage; - costumeAsset.encodeTextData(svgString, storage.DataFormat.SVG); - costume.assetId = storage.builtinHelper.cache( - storage.AssetType.ImageVector, - storage.DataFormat.SVG, - costumeAsset.data - ); - costume.md5 = `${costume.assetId}.${costume.dataFormat}`; - } - // createSVGSkin does the right thing if rotationCenter isn't provided, so it's okay if it's - // undefined here - costume.skinId = renderer.createSVGSkin(svgString, rotationCenter); - costume.size = renderer.getSkinSize(costume.skinId); - // Now we should have a rotationCenter even if we didn't before - if (!rotationCenter) { - rotationCenter = renderer.getSkinRotationCenter(costume.skinId); - costume.rotationCenterX = rotationCenter[0]; - costume.rotationCenterY = rotationCenter[1]; - } - - return costume; + return loadVector_(costume, costumeAsset, runtime, rotationCenter, optVersion); } - - return new Promise((resolve, reject) => { - const imageElement = new Image(); - const onError = function () { - // eslint-disable-next-line no-use-before-define - removeEventListeners(); - reject(); - }; - const onLoad = function () { - // eslint-disable-next-line no-use-before-define - removeEventListeners(); - resolve(imageElement); - }; - const removeEventListeners = function () { - imageElement.removeEventListener('error', onError); - imageElement.removeEventListener('load', onLoad); - }; - imageElement.addEventListener('error', onError); - imageElement.addEventListener('load', onLoad); - imageElement.src = costumeAsset.encodeDataURI(); - }).then(imageElement => { - // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined... - costume.skinId = renderer.createBitmapSkin(imageElement, costume.bitmapResolution, rotationCenter); - costume.size = renderer.getSkinSize(costume.skinId); - - if (!rotationCenter) { - rotationCenter = renderer.getSkinRotationCenter(costume.skinId); - costume.rotationCenterX = rotationCenter[0] * 2; - costume.rotationCenterY = rotationCenter[1] * 2; - } - return costume; - }); + return loadBitmap_(costume, costumeAsset, runtime, rotationCenter, optVersion); }; /** diff --git a/src/virtual-machine.js b/src/virtual-machine.js index e95258fb4..43ada32cb 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -895,6 +895,14 @@ class VirtualMachine extends EventEmitter { this.runtime.attachV2SVGAdapter(svgAdapter); } + /** + * Set the bitmap adapter for the VM/runtime, which converts scratch 2 svgs to scratch 3 bitmaps + * @param {!function} bitmapAdapter The adapter to attach + */ + attachV2BitmapAdapter (bitmapAdapter) { + this.runtime.attachV2BitmapAdapter(bitmapAdapter); + } + /** * Set the storage module for the VM/runtime * @param {!ScratchStorage} storage The storage module to attach From 704be2d83f616132e2514df51f0febcdfa5f18c7 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Tue, 10 Jul 2018 10:39:47 -0400 Subject: [PATCH 2/7] fix comment --- src/virtual-machine.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 43ada32cb..e428b7727 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -896,7 +896,8 @@ class VirtualMachine extends EventEmitter { } /** - * Set the bitmap adapter for the VM/runtime, which converts scratch 2 svgs to scratch 3 bitmaps + * Set the bitmap adapter for the VM/runtime, which converts scratch 2 + * bitmaps to scratch 3 bitmaps. (Scratch 3 bitmaps are all bitmap resolution 2) * @param {!function} bitmapAdapter The adapter to attach */ attachV2BitmapAdapter (bitmapAdapter) { From 747b4d1aed7d1ea78ede7371c4cea62bd761afb7 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Tue, 10 Jul 2018 11:47:11 -0400 Subject: [PATCH 3/7] Add bitmap resolution --- src/import/load-costume.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 9b9e6f86f..65a77f445 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -87,8 +87,10 @@ const loadBitmap_ = function (costume, costumeAsset, runtime, rotationCenter) { if (!rotationCenter) { rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId); + // Actual rotation center, since all bitmaps are resolution 2 costume.rotationCenterX = rotationCenter[0] * 2; costume.rotationCenterY = rotationCenter[1] * 2; + costume.bitmapResolution = 2; } return costume; }); From 37f66cc6c52caee686bb54053b80b6b81c75f0e3 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Tue, 10 Jul 2018 14:13:34 -0400 Subject: [PATCH 4/7] Test changing npm ignore --- .npmignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.npmignore b/.npmignore index 990cd607e..7e786bff4 100644 --- a/.npmignore +++ b/.npmignore @@ -6,8 +6,6 @@ /.github /.travis.yml /.tx -/test -/webpack.config.js # Build created files /playground From 0258a7390ff4e8e5af2d42fc6c51c0f3135ae1ca Mon Sep 17 00:00:00 2001 From: DD Liu Date: Thu, 12 Jul 2018 14:33:38 -0400 Subject: [PATCH 5/7] Convert to node style callback and convert to bitmap resolution 2 always --- .npmignore | 1 + src/import/load-costume.js | 28 +++++++++++++++++----------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.npmignore b/.npmignore index 7e786bff4..7b109bde4 100644 --- a/.npmignore +++ b/.npmignore @@ -6,6 +6,7 @@ /.github /.travis.yml /.tx +-/test # Build created files /playground diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 65a77f445..7780eb437 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -52,12 +52,14 @@ const loadBitmap_ = function (costume, costumeAsset, runtime, rotationCenter) { }; imageElement.addEventListener('error', onError); imageElement.addEventListener('load', onLoad); - let src = costumeAsset.encodeDataURI(); + const src = costumeAsset.encodeDataURI(); if (costume.bitmapResolution === 1 && !runtime.v2BitmapAdapter) { log.error('No V2 bitmap adapter present; bitmaps may not render correctly.'); } else if (costume.bitmapResolution === 1) { - src = runtime.v2BitmapAdapter.convertResolution1Bitmap(src, dataURI => { - if (dataURI) { + runtime.v2BitmapAdapter.convertResolution1Bitmap(src, (error, dataURI) => { + if (error) { + log.error(error); + } else if (dataURI) { // Put back into storage const storage = runtime.storage; costume.assetId = storage.builtinHelper.cache( @@ -66,15 +68,19 @@ const loadBitmap_ = function (costume, costumeAsset, runtime, rotationCenter) { runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI) ); costume.md5 = `${costume.assetId}.${costume.dataFormat}`; - if (rotationCenter) { - rotationCenter[0] = rotationCenter[0] * 2; - rotationCenter[1] = rotationCenter[1] * 2; - costume.rotationCenterX = rotationCenter[0]; - costume.rotationCenterY = rotationCenter[1]; - } - costume.bitmapResolution = 2; } - imageElement.src = dataURI ? dataURI : src; // Use original src if conversion fails + // Regardless of if conversion succeeds, convert it to bitmap resolution 2, + // since all code from here on will assume that. + if (rotationCenter) { + rotationCenter[0] = rotationCenter[0] * 2; + rotationCenter[1] = rotationCenter[1] * 2; + costume.rotationCenterX = rotationCenter[0]; + costume.rotationCenterY = rotationCenter[1]; + } + costume.bitmapResolution = 2; + // Use original src if conversion fails. + // The image will appear half-sized. + imageElement.src = dataURI ? dataURI : src; }); } else { imageElement.src = src; From 1c74c9028cf85db185a56f2a71edbe9e490ed65d Mon Sep 17 00:00:00 2001 From: DD Liu Date: Thu, 12 Jul 2018 17:18:38 -0400 Subject: [PATCH 6/7] Better error handling --- src/import/load-costume.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 7780eb437..932e1fc8a 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -39,7 +39,7 @@ const loadBitmap_ = function (costume, costumeAsset, runtime, rotationCenter) { const onError = function () { // eslint-disable-next-line no-use-before-define removeEventListeners(); - reject(); + reject('Image load failed'); }; const onLoad = function () { // eslint-disable-next-line no-use-before-define @@ -166,7 +166,10 @@ const loadCostume = function (md5ext, costume, runtime, optVersion) { return runtime.storage.load(assetType, md5, ext).then(costumeAsset => { costume.dataFormat = ext; return loadCostumeFromAsset(costume, costumeAsset, runtime, optVersion); - }); + }) + .catch(e => { + log.error(e); + }); }; module.exports = { From 46918f266e0a6ffa46c7e41b4bb9887772dfaf7e Mon Sep 17 00:00:00 2001 From: DD Liu Date: Thu, 12 Jul 2018 17:25:53 -0400 Subject: [PATCH 7/7] Add bitmap resolution so that required proptype warning stops --- src/import/load-costume.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 932e1fc8a..ee0b6474a 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -28,6 +28,7 @@ const loadVector_ = function (costume, costumeAsset, runtime, rotationCenter, op rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId); costume.rotationCenterX = rotationCenter[0]; costume.rotationCenterY = rotationCenter[1]; + costume.bitmapResolution = 1; } return costume;