From f4488d1b6059727e89059b6a26f8103c328d5fa8 Mon Sep 17 00:00:00 2001 From: Florrie Date: Sun, 3 Sep 2017 20:34:34 -0300 Subject: [PATCH 01/22] Begin work on 'old text' costumes --- src/import/load-old-text-costume.js | 99 +++++++++++++++++++++++++++++ src/serialization/sb2.js | 8 ++- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/import/load-old-text-costume.js diff --git a/src/import/load-old-text-costume.js b/src/import/load-old-text-costume.js new file mode 100644 index 000000000..a7063ffda --- /dev/null +++ b/src/import/load-old-text-costume.js @@ -0,0 +1,99 @@ +// @todo this file's name is a little bit unwieldly; should it be merged with load-costume.js (which could also contain common code between the two functions)? + +const StringUtil = require('../util/string-util'); +const log = require('../util/log'); + +// @todo should [bitmapResolution] (in the documentation comment) not be optional? After all, the resulting image is always a bitmap. + +/** + * Load an "old text" costume's asset into memory asynchronously. + * "Old text" costumes are ones who have a text part from Scratch 1.4. + * See the issue LLK/scratch-vm#672 for more information. + * Do not call this unless there is a renderer attached. + * @param {string} baseMD5ext - the MD5 and extension of the base layer of the costume to be loaded. + * @param {string} textMD5ext - the MD5 and extension of the text layer of the costume to be loaded. + * @param {!object} costume - the Scratch costume object. + * @property {int} skinId - the ID of the costume's render skin, once installed. + * @property {number} rotationCenterX - the X component of the costume's origin. + * @property {number} rotationCenterY - the Y component of the costume's origin. + * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume. + * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. + * @returns {?Promise} - a promsie which will resolve after skinId is set, or null on error. + */ +const loadOldTextCostume = function(baseMD5ext, textMD5ext, costume, runtime) { + if (!runtime.storage) { + log.error('No storage module present; cannot load costume asset: ', baseMD5ext, textMD5ext); + return Promise.resolve(costume); + } + + const [baseMD5, baseExt] = StringUtil.splitFirst(baseMD5ext, '.'); + const [textMD5, textExt] = StringUtil.splitFirst(textMD5ext, '.'); + + if (baseExt === 'svg' || textExt === 'svg') { + log.error('Old text costumes should never be SVGs'); + return Promise.resolve(costume); + } + + const assetType = runtime.storage.AssetType.ImageBitmap; + + // @todo should this be in a separate function, which could also be used by loadCostume? + const rotationCenter = [ + costume.rotationCenterX / costume.bitmapResolution, + costume.rotationCenterY / costume.bitmapResolution + ]; + + // @todo what should the assetId be? Probably unset, since we'll be doing image processing (which will produce a completely new image)? + // @todo what about the dataFormat? This depends on how the image processing is implemented. + + return Promise.all([ + runtime.storage.load(assetType, baseMD5, baseExt), + runtime.storage.load(assetType, textMD5, textExt) + ]).then(costumeAssets => ( + new Promise((resolve, reject) => { + const baseImageElement = new Image(); + const textImageElement = new Image(); + + let loadedOne = false; + + const onError = function () { + // eslint-disable-next-line no-use-before-define + removeEventListeners(); + reject(); + }; + const onLoad = function () { + if (loadedOne) { + removeEventListeners(); + resolve([baseImageElement, textImageElement]); + } else { + loadedOne = true; + } + }; + const removeEventListeners = function () { + baseImageElement.removeEventListener('error', onError); + textImageElement.removeEventListener('error', onError); + baseImageElement.removeEventListener('load', onLoad); + textImageElement.removeEventListener('load', onLoad); + }; + + baseImageElement.addEventListener('error', onError); + textImageElement.addEventListener('error', onError); + baseImageElement.addEventListener('load', onLoad); + textImageElement.addEventListener('load', onLoad); + + const [baseAsset, textAsset] = costumeAssets; + + baseImageElement.src = baseAsset.encodeDataURI(); + textImageElement.src = textAsset.encodeDataURI(); + }) + )).then(imageElements => { + const [baseImageElement, textImageElement] = imageElements; + + // @todo flatten the base and text images. The renderer should probably do the image processing that'll be needed here. + // The text part is currently displayed only for debugging. + costume.skinId = runtime.renderer.createBitmapSkin(textImageElement, costume.bitmapResolution, rotationCenter); + + return costume; + }); +}; + +module.exports = loadOldTextCostume; diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 48e89f448..d32c29d6e 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -16,6 +16,7 @@ const Variable = require('../engine/variable'); const List = require('../engine/list'); const loadCostume = require('../import/load-costume.js'); +const loadOldTextCostume = require('../import/load-old-text-costume.js'); const loadSound = require('../import/load-sound.js'); /** @@ -186,7 +187,12 @@ const parseScratchObject = function (object, runtime, topLevel) { rotationCenterY: costumeSource.rotationCenterY, skinId: null }; - costumePromises.push(loadCostume(costumeSource.baseLayerMD5, costume, runtime)); + + if ('textLayerMD5' in costumeSource) { + costumePromises.push(loadOldTextCostume(costumeSource.baseLayerMD5, costumeSource.textLayerMD5, costume, runtime)); + } else { + costumePromises.push(loadCostume(costumeSource.baseLayerMD5, costume, runtime)); + } } } // Sounds from JSON From 430dbf0bd6cb13c6f0d0d0337111fa2c1e12e2c0 Mon Sep 17 00:00:00 2001 From: Florrie Date: Tue, 23 Jan 2018 11:53:23 -0400 Subject: [PATCH 02/22] Remove old dead import file --- src/import/load-old-text-costume.js | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/import/load-old-text-costume.js diff --git a/src/import/load-old-text-costume.js b/src/import/load-old-text-costume.js deleted file mode 100644 index 85308a3b0..000000000 --- a/src/import/load-old-text-costume.js +++ /dev/null @@ -1,6 +0,0 @@ -// @todo this file's name is a little bit unwieldly; should it be merged with load-costume.js (which could also contain common code between the two functions)? - -const StringUtil = require('../util/string-util'); -const log = require('../util/log'); - -module.exports = loadOldTextCostume; From 78c54852c6489b6fba0d5c24c2bee60ebcd3184a Mon Sep 17 00:00:00 2001 From: Florrie Date: Tue, 23 Jan 2018 12:03:39 -0400 Subject: [PATCH 03/22] Merge base/text layers --- src/import/load-costume.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index fe8ea5ef4..502e5ec36 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -169,9 +169,15 @@ const loadOldTextCostume = function(baseMD5ext, textMD5ext, costume, runtime) { )).then(imageElements => { const [baseImageElement, textImageElement] = imageElements; - // @todo flatten the base and text images. The renderer should probably do the image processing that'll be needed here. - // The text part is currently displayed only for debugging. - costume.skinId = runtime.renderer.createBitmapSkin(textImageElement, costume.bitmapResolution, rotationCenter); + const canvas = document.createElement('canvas'); + canvas.width = baseImageElement.width; + canvas.height = baseImageElement.height; + + const ctx = canvas.getContext('2d') + ctx.drawImage(baseImageElement, 0, 0); + ctx.drawImage(textImageElement, 0, 0); + + costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter); return costume; }); From ad96ec952b706577defd50489eda7173814648fc Mon Sep 17 00:00:00 2001 From: Florrie Date: Tue, 23 Jan 2018 12:07:03 -0400 Subject: [PATCH 04/22] Fix 'promsie' typo in docs for loadOldTextCostume --- src/import/load-costume.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 502e5ec36..b936b54cd 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -97,7 +97,7 @@ const loadCostume = function (md5ext, costume, runtime) { * @property {number} rotationCenterY - the Y component of the costume's origin. * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume. * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. - * @returns {?Promise} - a promsie which will resolve after skinId is set, or null on error. + * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error. */ const loadOldTextCostume = function(baseMD5ext, textMD5ext, costume, runtime) { // @todo should [bitmapResolution] (in the documentation comment) not be optional? After all, the resulting image is always a bitmap. From 9180f9cc24f4eca8a2e70ac01c761175576e2be3 Mon Sep 17 00:00:00 2001 From: Florrie Date: Tue, 23 Jan 2018 16:10:39 -0400 Subject: [PATCH 05/22] Fix eslint errors --- src/import/.load-costume.js.swp | Bin 0 -> 24576 bytes src/import/load-costume.js | 96 +++++++++++++++++--------------- src/serialization/.sb2.js.swp | Bin 0 -> 45056 bytes src/serialization/sb2.js | 4 +- 4 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 src/import/.load-costume.js.swp create mode 100644 src/serialization/.sb2.js.swp diff --git a/src/import/.load-costume.js.swp b/src/import/.load-costume.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..d5692b3a83d5268c64bd23542d9f9647f1c731a0 GIT binary patch literal 24576 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 literal 0 HcmV?d00001 diff --git a/src/import/load-costume.js b/src/import/load-costume.js index b936b54cd..7a9aa9588 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -99,8 +99,9 @@ const loadCostume = function (md5ext, costume, runtime) { * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error. */ -const loadOldTextCostume = function(baseMD5ext, textMD5ext, costume, runtime) { - // @todo should [bitmapResolution] (in the documentation comment) not be optional? After all, the resulting image is always a bitmap. +const loadOldTextCostume = function (baseMD5ext, textMD5ext, costume, runtime) { + // @todo should [bitmapResolution] (in the documentation comment) not be optional? After all, the resulting image + // is always a bitmap. if (!runtime.storage) { log.error('No storage module present; cannot load costume asset: ', baseMD5ext, textMD5ext); @@ -123,64 +124,69 @@ const loadOldTextCostume = function(baseMD5ext, textMD5ext, costume, runtime) { costume.rotationCenterY / costume.bitmapResolution ]; - // @todo what should the assetId be? Probably unset, since we'll be doing image processing (which will produce a completely new image)? + // @todo what should the assetId be? Probably unset, since we'll be doing image processing (which will produce + // a completely new image)? // @todo what about the dataFormat? This depends on how the image processing is implemented. return Promise.all([ runtime.storage.load(assetType, baseMD5, baseExt), runtime.storage.load(assetType, textMD5, textExt) - ]).then(costumeAssets => ( - new Promise((resolve, reject) => { - const baseImageElement = new Image(); - const textImageElement = new Image(); + ]) + .then(costumeAssets => ( + new Promise((resolve, reject) => { + const baseImageElement = new Image(); + const textImageElement = new Image(); - let loadedOne = false; + let loadedOne = false; - const onError = function () { - // eslint-disable-next-line no-use-before-define - removeEventListeners(); - reject(); - }; - const onLoad = function () { - if (loadedOne) { + const onError = function () { + // eslint-disable-next-line no-use-before-define removeEventListeners(); - resolve([baseImageElement, textImageElement]); - } else { - loadedOne = true; - } - }; - const removeEventListeners = function () { - baseImageElement.removeEventListener('error', onError); - textImageElement.removeEventListener('error', onError); - baseImageElement.removeEventListener('load', onLoad); - textImageElement.removeEventListener('load', onLoad); - }; + reject(); + }; + const onLoad = function () { + if (loadedOne) { + // eslint-disable-next-line no-use-before-define + removeEventListeners(); + resolve([baseImageElement, textImageElement]); + } else { + loadedOne = true; + } + }; + + const removeEventListeners = function () { + baseImageElement.removeEventListener('error', onError); + textImageElement.removeEventListener('error', onError); + baseImageElement.removeEventListener('load', onLoad); + textImageElement.removeEventListener('load', onLoad); + }; - baseImageElement.addEventListener('error', onError); - textImageElement.addEventListener('error', onError); - baseImageElement.addEventListener('load', onLoad); - textImageElement.addEventListener('load', onLoad); + baseImageElement.addEventListener('error', onError); + textImageElement.addEventListener('error', onError); + baseImageElement.addEventListener('load', onLoad); + textImageElement.addEventListener('load', onLoad); - const [baseAsset, textAsset] = costumeAssets; + const [baseAsset, textAsset] = costumeAssets; - baseImageElement.src = baseAsset.encodeDataURI(); - textImageElement.src = textAsset.encodeDataURI(); - }) - )).then(imageElements => { - const [baseImageElement, textImageElement] = imageElements; + baseImageElement.src = baseAsset.encodeDataURI(); + textImageElement.src = textAsset.encodeDataURI(); + }) + )) + .then(imageElements => { + const [baseImageElement, textImageElement] = imageElements; - const canvas = document.createElement('canvas'); - canvas.width = baseImageElement.width; - canvas.height = baseImageElement.height; + const canvas = document.createElement('canvas'); + canvas.width = baseImageElement.width; + canvas.height = baseImageElement.height; - const ctx = canvas.getContext('2d') - ctx.drawImage(baseImageElement, 0, 0); - ctx.drawImage(textImageElement, 0, 0); + const ctx = canvas.getContext('2d'); + ctx.drawImage(baseImageElement, 0, 0); + ctx.drawImage(textImageElement, 0, 0); - costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter); + costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter); - return costume; - }); + return costume; + }); }; module.exports = { diff --git a/src/serialization/.sb2.js.swp b/src/serialization/.sb2.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..f01fd2e6044fb768c0809edf381cd0f20f0f4a65 GIT binary patch literal 45056 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 literal 0 HcmV?d00001 diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index b16cac49c..66424b8a2 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -228,7 +228,9 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) { }; if ('textLayerMD5' in costumeSource) { - costumePromises.push(loadOldTextCostume(costumeSource.baseLayerMD5, costumeSource.textLayerMD5, costume, runtime)); + costumePromises.push( + loadOldTextCostume(costumeSource.baseLayerMD5, costumeSource.textLayerMD5, costume, runtime) + ); } else { costumePromises.push(loadCostume(costumeSource.baseLayerMD5, costume, runtime)); } From 8f0130e4397abb5b7616212b2621d28e3ca29406 Mon Sep 17 00:00:00 2001 From: Florrie Date: Tue, 23 Jan 2018 18:34:28 -0400 Subject: [PATCH 06/22] Automatically create asset for generated 'flattened' image --- src/import/load-costume.js | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 7a9aa9588..d08aae2bf 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -153,7 +153,7 @@ const loadOldTextCostume = function (baseMD5ext, textMD5ext, costume, runtime) { loadedOne = true; } }; - + const removeEventListeners = function () { baseImageElement.removeEventListener('error', onError); textImageElement.removeEventListener('error', onError); @@ -183,9 +183,36 @@ const loadOldTextCostume = function (baseMD5ext, textMD5ext, costume, runtime) { ctx.drawImage(baseImageElement, 0, 0); ctx.drawImage(textImageElement, 0, 0); - costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter); - - return costume; + return new Promise((resolve, reject) => { + canvas.toBlob(blob => { + const reader = new FileReader(); + 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(); + costume.assetId = runtime.storage.builtinHelper.cache( + assetType, + runtime.storage.DataFormat.PNG, + new Buffer(reader.result) + ); + costume.skinId = runtime.renderer.createBitmapSkin( + canvas, costume.bitmapResolution, rotationCenter + ); + resolve(costume); + }; + const removeEventListeners = function () { + reader.removeEventListener('error', onError); + reader.removeEventListener('load', onLoad); + }; + reader.addEventListener('error', onError); + reader.addEventListener('load', onLoad); + reader.readAsArrayBuffer(blob); + }, 'image/png'); + }); }); }; From 6c2e0338a1228317fd7ecce55ff4820e4a722adc Mon Sep 17 00:00:00 2001 From: Florrie Date: Tue, 23 Jan 2018 18:36:33 -0400 Subject: [PATCH 07/22] Fix eslint whitespace error --- src/import/load-costume.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index d08aae2bf..d02b69e1c 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -186,7 +186,7 @@ const loadOldTextCostume = function (baseMD5ext, textMD5ext, costume, runtime) { return new Promise((resolve, reject) => { canvas.toBlob(blob => { const reader = new FileReader(); - const onError = function() { + const onError = function () { // eslint-disable-next-line no-use-before-define removeEventListeners(); reject(); From 86149325b1f2f14eb9cabc3cdfe0eb6a71846d4e Mon Sep 17 00:00:00 2001 From: DD Date: Mon, 5 Nov 2018 15:50:28 -0500 Subject: [PATCH 08/22] Try to make loadCostume more readable, and revert deserializeCostume to changing the costume object directly --- src/import/load-costume.js | 36 ++++++++++++++++--------- src/serialization/deserialize-assets.js | 10 +++++-- src/serialization/sb2.js | 5 +--- src/serialization/sb3.js | 5 +--- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 31eac9dbc..648a82a1c 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -152,21 +152,31 @@ const loadCostumeFromAsset = function (costume, costumeAsset, runtime, optVersio * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error. */ const loadCostume = function (md5ext, costume, runtime, optVersion) { - if (!runtime.storage) { - log.error('No storage module present; cannot load costume asset: ', md5ext); - return Promise.resolve(costume); + let costumePromise; + if (costume.asset) { + // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or sb file + costumePromise = Promise.resolve(costume.asset); + } else { + // Need to load the costume from storage. The server should have a reference to this md5. + if (!runtime.storage) { + log.error('No storage module present; cannot load costume asset: ', md5ext); + return Promise.resolve(costume); + } + + const AssetType = runtime.storage.AssetType; + const idParts = StringUtil.splitFirst(md5ext, '.'); + const md5 = idParts[0]; + const ext = idParts[1].toLowerCase(); + const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap; + costume.dataFormat = ext; + costumePromise = runtime.storage.load(assetType, md5, ext); + if (!costumePromise) { + log.error(`Couldn't fetch costume asset: ${md5ext}`); + return; + } } - const AssetType = runtime.storage.AssetType; - const idParts = StringUtil.splitFirst(md5ext, '.'); - const md5 = idParts[0]; - const ext = idParts[1].toLowerCase(); - const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap; - costume.dataFormat = ext; - return ( - (costume.asset && Promise.resolve(costume.asset)) || - runtime.storage.load(assetType, md5, ext) - ).then(costumeAsset => { + return costumePromise.then(costumeAsset => { costume.asset = costumeAsset; return loadCostumeFromAsset(costume, costumeAsset, runtime, optVersion); }) diff --git a/src/serialization/deserialize-assets.js b/src/serialization/deserialize-assets.js index cc787b9c6..603a1b8e2 100644 --- a/src/serialization/deserialize-assets.js +++ b/src/serialization/deserialize-assets.js @@ -79,7 +79,10 @@ const deserializeCostume = function (costume, runtime, zip, assetFileName) { costume.asset.dataFormat, new Uint8Array(Object.keys(costume.asset.data).map(key => costume.asset.data[key])), costume.asset.assetId - )); + )) + .then(asset => { + costume.asset = asset; + }); } if (!zip) { @@ -112,7 +115,10 @@ const deserializeCostume = function (costume, runtime, zip, assetFileName) { costumeFormat, data, assetId - )); + )) + .then(asset => { + costume.asset = asset; + }); }; module.exports = { diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index d30ef7bd6..b0c4bae4f 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -413,10 +413,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip) // the file name of the costume should be the baseLayerID followed by the file ext const assetFileName = `${costumeSource.baseLayerID}.${ext}`; costumePromises.push(deserializeCostume(costume, runtime, zip, assetFileName) - .then(asset => { - costume.asset = asset; - return loadCostume(costume.md5, costume, runtime, 2 /* optVersion */); - }) + .then(() => loadCostume(costume.md5, costume, runtime, 2 /* optVersion */)) ); } } diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index e3433258b..98c24be48 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -847,10 +847,7 @@ const parseScratchObject = function (object, runtime, extensions, zip) { // any translation that needs to happen will happen in the process // of building up the costume object into an sb3 format return deserializeCostume(costume, runtime, zip) - .then(asset => { - costume.asset = asset; - return loadCostume(costumeMd5Ext, costume, runtime); - }); + .then(() => loadCostume(costumeMd5Ext, costume, runtime)); // Only attempt to load the costume after the deserialization // process has been completed }); From 88b6bdd06ca365c428f8ebc4bf43ca87a5f87cb8 Mon Sep 17 00:00:00 2001 From: DD Date: Mon, 5 Nov 2018 18:07:56 -0500 Subject: [PATCH 09/22] Get text assets from zip --- src/serialization/deserialize-assets.js | 52 +++++++++++++++++++------ src/serialization/sb2.js | 6 ++- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/serialization/deserialize-assets.js b/src/serialization/deserialize-assets.js index 603a1b8e2..71e8fe346 100644 --- a/src/serialization/deserialize-assets.js +++ b/src/serialization/deserialize-assets.js @@ -56,11 +56,13 @@ const deserializeSound = function (sound, runtime, zip, assetFileName) { * @param {string} assetFileName Optional file name for the given asset * (sb2 files have filenames of the form [int].[ext], * sb3 files have filenames of the form [md5].[ext]) + * @param {string} textLayerFileName Optional file name for the given asset's text layer + * (sb2 only; files have filenames of the form [int].png) * @return {Promise} Promise that resolves after the described costume has been stored * into the runtime storage cache, the costume was already stored, or an error has * occurred. */ -const deserializeCostume = function (costume, runtime, zip, assetFileName) { +const deserializeCostume = function (costume, runtime, zip, assetFileName, textLayerFileName) { const storage = runtime.storage; const assetId = costume.assetId; const fileName = assetFileName ? assetFileName : @@ -109,16 +111,44 @@ const deserializeCostume = function (costume, runtime, zip, assetFileName) { return Promise.resolve(null); } - return costumeFile.async('uint8array').then(data => storage.createAsset( - assetType, - // TODO eventually we want to map non-png's to their actual file types? - costumeFormat, - data, - assetId - )) - .then(asset => { - costume.asset = asset; - }); + // textLayerMD5 exists if there is a text layer, which is a png of text from Scratch 1.4 + // that was opened in Scratch 2.0. In this case, set costume,textLayerAsset. + let textLayerFilePromise; + if (costume.textLayerMD5) { + textLayerFileName = textLayerFileName ? textLayerFileName : + `${costume.textLayerMD5}.png`; + const textLayerFile = zip.file(textLayerFileName); + if (!textLayerFile) { + log.error(`Could not find text layer file associated with the ${costume.name} costume.`); + return Promise.resolve(null); + } + textLayerFilePromise = textLayerFile.async('uint8array') + .then(data => storage.createAsset( + storage.AssetType.ImageBitmap, + 'png', + data, + costume.textLayerMD5 + )) + .then(asset => { + costume.textLayerAsset = asset; + }); + } else { + textLayerFilePromise = Promise.resolve(null); + } + + return Promise.all([textLayerFilePromise, + costumeFile.async('uint8array') + .then(data => storage.createAsset( + assetType, + // TODO eventually we want to map non-png's to their actual file types? + costumeFormat, + data, + assetId + )) + .then(asset => { + costume.asset = asset; + }) + ]); }; module.exports = { diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index b0c4bae4f..d6fbd827d 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -408,11 +408,15 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip) const ext = idParts[1].toLowerCase(); costume.dataFormat = ext; costume.assetId = md5; + if (costumeSource.textLayerMD5) { + costume.textLayerMD5 = StringUtil.splitFirst(costumeSource.textLayerMD5, '.')[0]; + } // If there is no internet connection, or if the asset is not in storage // for some reason, and we are doing a local .sb2 import, (e.g. zip is provided) // the file name of the costume should be the baseLayerID followed by the file ext const assetFileName = `${costumeSource.baseLayerID}.${ext}`; - costumePromises.push(deserializeCostume(costume, runtime, zip, assetFileName) + const textLayerFileName = costumeSource.textLayerID ? `${costumeSource.textLayerID}.png` : null; + costumePromises.push(deserializeCostume(costume, runtime, zip, assetFileName, textLayerFileName) .then(() => loadCostume(costume.md5, costume, runtime, 2 /* optVersion */)) ); } From b6b3ca21d75ed131b8ada3a34db83cf7e17fc0d1 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 10:36:52 -0500 Subject: [PATCH 10/22] Load text layer asset --- src/import/load-costume.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 648a82a1c..34a3296fc 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -153,7 +153,11 @@ const loadCostumeFromAsset = function (costume, costumeAsset, runtime, optVersio */ const loadCostume = function (md5ext, costume, runtime, optVersion) { let costumePromise; + let textLayerPromise; if (costume.asset) { + // TODO if text asset exists, merge the 2 assets, put it back in storage, clean up text layer + // data from costume and return the costume with merged asset here. + // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or sb file costumePromise = Promise.resolve(costume.asset); } else { @@ -174,10 +178,19 @@ const loadCostume = function (md5ext, costume, runtime, optVersion) { log.error(`Couldn't fetch costume asset: ${md5ext}`); return; } + + if (costume.textLayerMD5) { + textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png'); + } else { + textLayerPromise = Promise.resolve(null); + } } - return costumePromise.then(costumeAsset => { - costume.asset = costumeAsset; + return Promise.all(costumePromise, textLayerPromise).then(assetArray => { + costume.asset = assetArray[0]; + if (assetArray[1]) { + costume.textLayerAsset = assetArray[1]; + } return loadCostumeFromAsset(costume, costumeAsset, runtime, optVersion); }) .catch(e => { From 998c5a186e4f8a290ba7dc89996f12442216dab4 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 16:44:17 -0500 Subject: [PATCH 11/22] Merge images as part of fetch bitmap --- src/import/load-costume.js | 311 +++++++++++++++---------------------- 1 file changed, 121 insertions(+), 190 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 41e69f2b6..851d0bf4c 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -1,8 +1,8 @@ const StringUtil = require('../util/string-util'); const log = require('../util/log'); -const loadVector_ = function (costume, costumeAsset, runtime, rotationCenter, optVersion) { - let svgString = costumeAsset.decodeText(); +const loadVector_ = function (costume, runtime, rotationCenter, optVersion) { + let svgString = costume.asset.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.'); @@ -30,64 +30,128 @@ const loadVector_ = function (costume, costumeAsset, runtime, rotationCenter, op return Promise.resolve(costume); }; -const loadBitmap_ = function (costume, costumeAsset, runtime, rotationCenter) { +/** + * Fetch bitmap from storage and return a canvas. + * If the costume has bitmapResolution 1, it will be converted to bitmapResolution 2 here (the standard for Scratch 3) + * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function + * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information. + * @param {!object} costume - the Scratch costume object. + * @param {?object} rotationCenter - optionally passed in coordinates for the center of rotation for the image. If + * none is given, the rotation center of the costume will be set to the middle of the costume later on. + * @property {number} costume.bitmapResolution - the resolution scale for a bitmap costume. + * @returns {?Promise} - a promise which will resolve to an object {canvas, rotationCenter, assetMatchesBase}, + * or reject on error. + * assetMatchesBase is true if the asset matches the base layer; false if it required adjustment + */ +const fetchBitmapCanvas_ = function (costume, rotationCenter) { + if (!costume || !costume.asset) { + return Promise.reject('Costume load failed. Assets were missing.'); + } return new Promise((resolve, reject) => { - const imageElement = new Image(); + const baseImageElement = new Image(); + const textImageElement = new Image(); + + let loadedOne = false; + const onError = function () { // eslint-disable-next-line no-use-before-define removeEventListeners(); - reject('Image load failed'); + reject('Costume load failed. Asset could not be read.'); }; const onLoad = function () { - // eslint-disable-next-line no-use-before-define - removeEventListeners(); - resolve(imageElement); + if (loadedOne) { + // eslint-disable-next-line no-use-before-define + removeEventListeners(); + resolve([baseImageElement, textImageElement]); + } else { + loadedOne = true; + } }; + const removeEventListeners = function () { - imageElement.removeEventListener('error', onError); - imageElement.removeEventListener('load', onLoad); + baseImageElement.removeEventListener('error', onError); + textImageElement.removeEventListener('error', onError); + baseImageElement.removeEventListener('load', onLoad); + textImageElement.removeEventListener('load', onLoad); }; - imageElement.addEventListener('error', onError); - imageElement.addEventListener('load', onLoad); - 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) { - runtime.v2BitmapAdapter.convertResolution1Bitmap(src, (error, dataURI) => { - if (error) { - log.error(error); - } else if (dataURI) { - // Put back into storage - const storage = runtime.storage; - costume.asset = storage.createAsset( - storage.AssetType.ImageBitmap, - storage.DataFormat.PNG, - runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI), - null, - true // generate md5 - ); - costume.assetId = costume.asset.assetId; - costume.md5 = `${costume.assetId}.${costume.dataFormat}`; - } - // 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; - }); + + baseImageElement.addEventListener('error', onError); + textImageElement.addEventListener('error', onError); + baseImageElement.addEventListener('load', onLoad); + textImageElement.addEventListener('load', onLoad); + + if (costume.textLayerAsset) { + textImageElement.src = costume.textLayerAsset.encodeDataURI(); } else { - imageElement.src = src; + loadedOne = true; } - }).then(imageElement => { + baseImageElement.src = costume.asset.encodeDataURI(); + }) + .then(imageElements => { + const [baseImageElement, textImageElement] = imageElements; + + const canvas = document.createElement('canvas'); + canvas.getContext('2d').imageSmoothingEnabled = false; + const scale = costume.bitmapResolution === 1 ? 2 : 1; + canvas.width = baseImageElement.width * scale; + canvas.height = baseImageElement.height * scale; + + const ctx = canvas.getContext('2d'); + ctx.drawImage(baseImageElement, 0, 0, canvas.width, canvas.height); + if (textImageElement.src) { + ctx.drawImage(textImageElement, 0, 0, canvas.width, canvas.height); + } + + // By scaling, we've converted it to bitmap resolution 2 + if (rotationCenter) { + rotationCenter[0] = rotationCenter[0] * scale; + rotationCenter[1] = rotationCenter[1] * scale; + costume.rotationCenterX = rotationCenter[0]; + costume.rotationCenterY = rotationCenter[1]; + } + costume.bitmapResolution = 2; + + return { + canvas: canvas, + rotationCenter: rotationCenter, + // True if the asset matches the base layer; false if it required adjustment + assetMatchesBase: scale !== 1 || textImageElement.src + }; + }) + .catch(e => Promise.reject(e)) + .finally(() => { + // Clean up the costume object + delete costume.textLayerMD5; + delete costume.textLayerAsset; + }); +}; + +const loadBitmap_ = function (costume, runtime, rotationCenter) { + return fetchBitmapCanvas_(costume, rotationCenter).then(fetched => new Promise(resolve => { + rotationCenter = fetched.rotationCenter; + + const saveAssetToStorage = function (dataURI) { + const storage = runtime.storage; + costume.asset = storage.createAsset( + storage.AssetType.ImageBitmap, + storage.DataFormat.PNG, + runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI), + null, + true // generate md5 + ); + costume.assetId = costume.asset.assetId; + costume.md5 = `${costume.assetId}.${costume.dataFormat}`; + }; + + if (!fetched.assetMatchesBase) { + saveAssetToStorage(fetched.canvas.toDataURL()); + } + resolve(fetched.canvas); + })) + .catch(e => Promise.reject(e)) + .then(canvas => { // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined... - costume.skinId = runtime.renderer.createBitmapSkin(imageElement, costume.bitmapResolution, rotationCenter); + costume.skinId = runtime.renderer.createBitmapSkin(canvas, 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 @@ -110,14 +174,14 @@ const loadBitmap_ = function (costume, costumeAsset, runtime, rotationCenter) { * @property {number} rotationCenterX - the X component of the costume's origin. * @property {number} rotationCenterY - the Y component of the costume's origin. * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume. - * @param {!Asset} costumeAsset - the asset of the costume loaded from storage. + * @param {!Asset} costume.asset - the asset of the costume loaded from storage. * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set * to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0. * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error. */ -const loadCostumeFromAsset = function (costume, costumeAsset, runtime, optVersion) { - costume.assetId = costumeAsset.assetId; +const loadCostumeFromAsset = function (costume, runtime, optVersion) { + costume.assetId = costume.asset.assetId; const renderer = runtime.renderer; if (!renderer) { log.error('No rendering module present; cannot load costume: ', costume.name); @@ -131,10 +195,10 @@ const loadCostumeFromAsset = function (costume, costumeAsset, runtime, optVersio typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) { rotationCenter = [costume.rotationCenterX, costume.rotationCenterY]; } - if (costumeAsset.assetType === AssetType.ImageVector) { - return loadVector_(costume, costumeAsset, runtime, rotationCenter, optVersion); + if (costume.asset.assetType === AssetType.ImageVector) { + return loadVector_(costume, runtime, rotationCenter, optVersion); } - return loadBitmap_(costume, costumeAsset, runtime, rotationCenter, optVersion); + return loadBitmap_(costume, runtime, rotationCenter, optVersion); }; /** @@ -186,152 +250,19 @@ const loadCostume = function (md5ext, costume, runtime, optVersion) { } } - return Promise.all(costumePromise, textLayerPromise).then(assetArray => { + return Promise.all([costumePromise, textLayerPromise]).then(assetArray => { costume.asset = assetArray[0]; if (assetArray[1]) { costume.textLayerAsset = assetArray[1]; } - return loadCostumeFromAsset(costume, costumeAsset, runtime, optVersion); + return loadCostumeFromAsset(costume, runtime, optVersion); }) .catch(e => { log.error(e); }); }; -/** - * Load an "old text" costume's asset into memory asynchronously. - * "Old text" costumes are ones who have a text part from Scratch 1.4. - * See the issue LLK/scratch-vm#672 for more information. - * Do not call this unless there is a renderer attached. - * @param {string} baseMD5ext - the MD5 and extension of the base layer of the costume to be loaded. - * @param {string} textMD5ext - the MD5 and extension of the text layer of the costume to be loaded. - * @param {!object} costume - the Scratch costume object. - * @property {int} skinId - the ID of the costume's render skin, once installed. - * @property {number} rotationCenterX - the X component of the costume's origin. - * @property {number} rotationCenterY - the Y component of the costume's origin. - * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume. - * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. - * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error. - */ -const loadOldTextCostume = function (baseMD5ext, textMD5ext, costume, runtime) { - // @todo should [bitmapResolution] (in the documentation comment) not be optional? After all, the resulting image - // is always a bitmap. - - if (!runtime.storage) { - log.error('No storage module present; cannot load costume asset: ', baseMD5ext, textMD5ext); - return Promise.resolve(costume); - } - - const [baseMD5, baseExt] = StringUtil.splitFirst(baseMD5ext, '.'); - const [textMD5, textExt] = StringUtil.splitFirst(textMD5ext, '.'); - - if (baseExt === 'svg' || textExt === 'svg') { - log.error('Old text costumes should never be SVGs'); - return Promise.resolve(costume); - } - - const assetType = runtime.storage.AssetType.ImageBitmap; - - // @todo should this be in a separate function, which could also be used by loadCostume? - const rotationCenter = [ - costume.rotationCenterX / costume.bitmapResolution, - costume.rotationCenterY / costume.bitmapResolution - ]; - - // @todo what should the assetId be? Probably unset, since we'll be doing image processing (which will produce - // a completely new image)? - // @todo what about the dataFormat? This depends on how the image processing is implemented. - - return Promise.all([ - runtime.storage.load(assetType, baseMD5, baseExt), - runtime.storage.load(assetType, textMD5, textExt) - ]) - .then(costumeAssets => ( - new Promise((resolve, reject) => { - const baseImageElement = new Image(); - const textImageElement = new Image(); - - let loadedOne = false; - - const onError = function () { - // eslint-disable-next-line no-use-before-define - removeEventListeners(); - reject(); - }; - const onLoad = function () { - if (loadedOne) { - // eslint-disable-next-line no-use-before-define - removeEventListeners(); - resolve([baseImageElement, textImageElement]); - } else { - loadedOne = true; - } - }; - - const removeEventListeners = function () { - baseImageElement.removeEventListener('error', onError); - textImageElement.removeEventListener('error', onError); - baseImageElement.removeEventListener('load', onLoad); - textImageElement.removeEventListener('load', onLoad); - }; - - baseImageElement.addEventListener('error', onError); - textImageElement.addEventListener('error', onError); - baseImageElement.addEventListener('load', onLoad); - textImageElement.addEventListener('load', onLoad); - - const [baseAsset, textAsset] = costumeAssets; - - baseImageElement.src = baseAsset.encodeDataURI(); - textImageElement.src = textAsset.encodeDataURI(); - }) - )) - .then(imageElements => { - const [baseImageElement, textImageElement] = imageElements; - - const canvas = document.createElement('canvas'); - canvas.width = baseImageElement.width; - canvas.height = baseImageElement.height; - - const ctx = canvas.getContext('2d'); - ctx.drawImage(baseImageElement, 0, 0); - ctx.drawImage(textImageElement, 0, 0); - - return new Promise((resolve, reject) => { - canvas.toBlob(blob => { - const reader = new FileReader(); - 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(); - costume.assetId = runtime.storage.builtinHelper.cache( - assetType, - runtime.storage.DataFormat.PNG, - new Buffer(reader.result) - ); - costume.skinId = runtime.renderer.createBitmapSkin( - canvas, costume.bitmapResolution, rotationCenter - ); - resolve(costume); - }; - const removeEventListeners = function () { - reader.removeEventListener('error', onError); - reader.removeEventListener('load', onLoad); - }; - reader.addEventListener('error', onError); - reader.addEventListener('load', onLoad); - reader.readAsArrayBuffer(blob); - }, 'image/png'); - }); - }); -}; - module.exports = { loadCostume, - loadCostumeFromAsset, - loadOldTextCostume + loadCostumeFromAsset }; From 2bd468950f40135cb0dbf29ac3f71f1cf7021d5c Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 16:54:41 -0500 Subject: [PATCH 12/22] Why do our lint rules require such weird formatting --- src/import/load-costume.js | 45 ++++++++++++------------- src/serialization/deserialize-assets.js | 41 +++++++++++----------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 851d0bf4c..accbb5a31 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -86,8 +86,7 @@ const fetchBitmapCanvas_ = function (costume, rotationCenter) { loadedOne = true; } baseImageElement.src = costume.asset.encodeDataURI(); - }) - .then(imageElements => { + }).then(imageElements => { const [baseImageElement, textImageElement] = imageElements; const canvas = document.createElement('canvas'); @@ -118,12 +117,12 @@ const fetchBitmapCanvas_ = function (costume, rotationCenter) { assetMatchesBase: scale !== 1 || textImageElement.src }; }) - .catch(e => Promise.reject(e)) - .finally(() => { - // Clean up the costume object - delete costume.textLayerMD5; - delete costume.textLayerAsset; - }); + .catch(e => Promise.reject(e)) + .finally(() => { + // Clean up the costume object + delete costume.textLayerMD5; + delete costume.textLayerAsset; + }); }; const loadBitmap_ = function (costume, runtime, rotationCenter) { @@ -148,22 +147,22 @@ const loadBitmap_ = function (costume, runtime, rotationCenter) { } resolve(fetched.canvas); })) - .catch(e => Promise.reject(e)) - .then(canvas => { - // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined... - costume.skinId = runtime.renderer.createBitmapSkin(canvas, 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 + .catch(e => Promise.reject(e)) + .then(canvas => { + // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined... + costume.skinId = runtime.renderer.createBitmapSkin(canvas, 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); - // Actual rotation center, since all bitmaps are resolution 2 - costume.rotationCenterX = rotationCenter[0] * 2; - costume.rotationCenterY = rotationCenter[1] * 2; - costume.bitmapResolution = 2; - } - return costume; - }); + 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; + }); }; /** diff --git a/src/serialization/deserialize-assets.js b/src/serialization/deserialize-assets.js index 71e8fe346..4e0a0bafa 100644 --- a/src/serialization/deserialize-assets.js +++ b/src/serialization/deserialize-assets.js @@ -81,8 +81,7 @@ const deserializeCostume = function (costume, runtime, zip, assetFileName, textL costume.asset.dataFormat, new Uint8Array(Object.keys(costume.asset.data).map(key => costume.asset.data[key])), costume.asset.assetId - )) - .then(asset => { + )).then(asset => { costume.asset = asset; }); } @@ -123,31 +122,31 @@ const deserializeCostume = function (costume, runtime, zip, assetFileName, textL return Promise.resolve(null); } textLayerFilePromise = textLayerFile.async('uint8array') - .then(data => storage.createAsset( - storage.AssetType.ImageBitmap, - 'png', - data, - costume.textLayerMD5 - )) - .then(asset => { - costume.textLayerAsset = asset; - }); + .then(data => storage.createAsset( + storage.AssetType.ImageBitmap, + 'png', + data, + costume.textLayerMD5 + )) + .then(asset => { + costume.textLayerAsset = asset; + }); } else { textLayerFilePromise = Promise.resolve(null); } return Promise.all([textLayerFilePromise, costumeFile.async('uint8array') - .then(data => storage.createAsset( - assetType, - // TODO eventually we want to map non-png's to their actual file types? - costumeFormat, - data, - assetId - )) - .then(asset => { - costume.asset = asset; - }) + .then(data => storage.createAsset( + assetType, + // TODO eventually we want to map non-png's to their actual file types? + costumeFormat, + data, + assetId + )) + .then(asset => { + costume.asset = asset; + }) ]); }; From 6763355d00fa3f8b91ff79a62a34404b27aad974 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 17:22:25 -0500 Subject: [PATCH 13/22] Fix scaling to perform nearest neighbor --- src/import/load-costume.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index accbb5a31..db043a903 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -36,6 +36,7 @@ const loadVector_ = function (costume, runtime, rotationCenter, optVersion) { * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information. * @param {!object} costume - the Scratch costume object. + * @param {!Runtime} runtime - Scratch runtime, used to access the v2BitmapAdapter * @param {?object} rotationCenter - optionally passed in coordinates for the center of rotation for the image. If * none is given, the rotation center of the costume will be set to the middle of the costume later on. * @property {number} costume.bitmapResolution - the resolution scale for a bitmap costume. @@ -43,7 +44,7 @@ const loadVector_ = function (costume, runtime, rotationCenter, optVersion) { * or reject on error. * assetMatchesBase is true if the asset matches the base layer; false if it required adjustment */ -const fetchBitmapCanvas_ = function (costume, rotationCenter) { +const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { if (!costume || !costume.asset) { return Promise.reject('Costume load failed. Assets were missing.'); } @@ -89,16 +90,21 @@ const fetchBitmapCanvas_ = function (costume, rotationCenter) { }).then(imageElements => { const [baseImageElement, textImageElement] = imageElements; - const canvas = document.createElement('canvas'); - canvas.getContext('2d').imageSmoothingEnabled = false; + let canvas = document.createElement('canvas'); const scale = costume.bitmapResolution === 1 ? 2 : 1; - canvas.width = baseImageElement.width * scale; - canvas.height = baseImageElement.height * scale; + canvas.width = baseImageElement.width; + canvas.height = baseImageElement.height; const ctx = canvas.getContext('2d'); - ctx.drawImage(baseImageElement, 0, 0, canvas.width, canvas.height); + ctx.drawImage(baseImageElement, 0, 0); if (textImageElement.src) { - ctx.drawImage(textImageElement, 0, 0, canvas.width, canvas.height); + ctx.drawImage(textImageElement, 0, 0); + } + if (scale !== 1) { + if (!runtime.v2BitmapAdapter) { + return Promise.reject('No V2 Bitmap adapter present.'); + } + canvas = runtime.v2BitmapAdapter.resize(canvas, canvas.width * scale, canvas.height * scale); } // By scaling, we've converted it to bitmap resolution 2 @@ -114,7 +120,7 @@ const fetchBitmapCanvas_ = function (costume, rotationCenter) { canvas: canvas, rotationCenter: rotationCenter, // True if the asset matches the base layer; false if it required adjustment - assetMatchesBase: scale !== 1 || textImageElement.src + assetMatchesBase: scale === 1 && !textImageElement.src }; }) .catch(e => Promise.reject(e)) @@ -126,10 +132,14 @@ const fetchBitmapCanvas_ = function (costume, rotationCenter) { }; const loadBitmap_ = function (costume, runtime, rotationCenter) { - return fetchBitmapCanvas_(costume, rotationCenter).then(fetched => new Promise(resolve => { + return fetchBitmapCanvas_(costume, runtime, rotationCenter).then(fetched => new Promise(resolve => { rotationCenter = fetched.rotationCenter; const saveAssetToStorage = function (dataURI) { + if (!runtime.v2BitmapAdapter) { + return Promise.reject('No V2 Bitmap adapter present.'); + } + const storage = runtime.storage; costume.asset = storage.createAsset( storage.AssetType.ImageBitmap, From 2a032be1a9bf349a72fc9630e9d9fa415ca9c44f Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 17:34:07 -0500 Subject: [PATCH 14/22] Remove todo --- src/import/load-costume.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index db043a903..13bd2b679 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -228,11 +228,11 @@ const loadCostume = function (md5ext, costume, runtime, optVersion) { let costumePromise; let textLayerPromise; if (costume.asset) { - // TODO if text asset exists, merge the 2 assets, put it back in storage, clean up text layer - // data from costume and return the costume with merged asset here. - - // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or sb file + // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or file costumePromise = Promise.resolve(costume.asset); + if (costume.textLayerPromise) { + textLayerPromise = Promise.resolve(costume.textLayerAsset); + } } else { // Need to load the costume from storage. The server should have a reference to this md5. if (!runtime.storage) { From 3c95b32da050f8ecf8c251c80a778be69c82ad82 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 17:39:26 -0500 Subject: [PATCH 15/22] Rearrange so that if we already have the asset, there's no need to go through promise.all --- src/import/load-costume.js | 58 ++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 13bd2b679..f6b5a6831 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -213,7 +213,7 @@ const loadCostumeFromAsset = function (costume, runtime, optVersion) { /** * Load a costume's asset into memory asynchronously. * Do not call this unless there is a renderer attached. - * @param {string} md5ext - the MD5 and extension of the costume to be loaded. + * @param {!string} md5ext - the MD5 and extension of the costume to be loaded. * @param {!object} costume - the Scratch costume object. * @property {int} skinId - the ID of the costume's render skin, once installed. * @property {number} rotationCenterX - the X component of the costume's origin. @@ -225,38 +225,36 @@ const loadCostumeFromAsset = function (costume, runtime, optVersion) { * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error. */ const loadCostume = function (md5ext, costume, runtime, optVersion) { - let costumePromise; - let textLayerPromise; + const idParts = StringUtil.splitFirst(md5ext, '.'); + const md5 = idParts[0]; + const ext = idParts[1].toLowerCase(); + costume.dataFormat = ext; + if (costume.asset) { // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or file - costumePromise = Promise.resolve(costume.asset); - if (costume.textLayerPromise) { - textLayerPromise = Promise.resolve(costume.textLayerAsset); - } + return loadCostumeFromAsset(costume, runtime, optVersion); + } + + // Need to load the costume from storage. The server should have a reference to this md5. + if (!runtime.storage) { + log.error('No storage module present; cannot load costume asset: ', md5ext); + return Promise.resolve(costume); + } + + const AssetType = runtime.storage.AssetType; + const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap; + + const costumePromise = runtime.storage.load(assetType, md5, ext); + if (!costumePromise) { + log.error(`Couldn't fetch costume asset: ${md5ext}`); + return; + } + + let textLayerPromise; + if (costume.textLayerMD5) { + textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png'); } else { - // Need to load the costume from storage. The server should have a reference to this md5. - if (!runtime.storage) { - log.error('No storage module present; cannot load costume asset: ', md5ext); - return Promise.resolve(costume); - } - - const AssetType = runtime.storage.AssetType; - const idParts = StringUtil.splitFirst(md5ext, '.'); - const md5 = idParts[0]; - const ext = idParts[1].toLowerCase(); - const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap; - costume.dataFormat = ext; - costumePromise = runtime.storage.load(assetType, md5, ext); - if (!costumePromise) { - log.error(`Couldn't fetch costume asset: ${md5ext}`); - return; - } - - if (costume.textLayerMD5) { - textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png'); - } else { - textLayerPromise = Promise.resolve(null); - } + textLayerPromise = Promise.resolve(null); } return Promise.all([costumePromise, textLayerPromise]).then(assetArray => { From 1cef52e180908845a84750ea802a96cab7bbbee2 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 17:43:26 -0500 Subject: [PATCH 16/22] Remove mysterious swp file --- src/import/.load-costume.js.swp | Bin 24576 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/import/.load-costume.js.swp diff --git a/src/import/.load-costume.js.swp b/src/import/.load-costume.js.swp deleted file mode 100644 index d5692b3a83d5268c64bd23542d9f9647f1c731a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 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 From 5ad193b96e165ce0d7746515182668ccc17b1764 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 6 Nov 2018 17:51:39 -0500 Subject: [PATCH 17/22] Better comment --- src/import/load-costume.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index f6b5a6831..4e1e46e59 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -31,7 +31,7 @@ const loadVector_ = function (costume, runtime, rotationCenter, optVersion) { }; /** - * Fetch bitmap from storage and return a canvas. + * Return a promise to fetch a bitmap from storage and return it as a canvas * If the costume has bitmapResolution 1, it will be converted to bitmapResolution 2 here (the standard for Scratch 3) * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information. From 0b726086247ad7efa7bdf03f8ce19f90ff34b1c2 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 13 Nov 2018 15:09:47 -0500 Subject: [PATCH 18/22] Remove swp file --- src/serialization/.sb2.js.swp | Bin 45056 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/serialization/.sb2.js.swp diff --git a/src/serialization/.sb2.js.swp b/src/serialization/.sb2.js.swp deleted file mode 100644 index f01fd2e6044fb768c0809edf381cd0f20f0f4a65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45056 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 From 679ddf9fb7df3136a89afa034cb486a421d84223 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 13 Nov 2018 15:31:00 -0500 Subject: [PATCH 19/22] Comment fixes and instantiate textImageElement only as necessary --- src/import/load-costume.js | 26 +++++++++++++------------ src/serialization/deserialize-assets.js | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 4e1e46e59..5afdbb09d 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -50,7 +50,7 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { } return new Promise((resolve, reject) => { const baseImageElement = new Image(); - const textImageElement = new Image(); + let textImageElement; let loadedOne = false; @@ -71,17 +71,19 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { const removeEventListeners = function () { baseImageElement.removeEventListener('error', onError); - textImageElement.removeEventListener('error', onError); baseImageElement.removeEventListener('load', onLoad); - textImageElement.removeEventListener('load', onLoad); + if (textImageElement) { + textImageElement.removeEventListener('error', onError); + textImageElement.removeEventListener('load', onLoad); + } }; - baseImageElement.addEventListener('error', onError); - textImageElement.addEventListener('error', onError); baseImageElement.addEventListener('load', onLoad); - textImageElement.addEventListener('load', onLoad); - + baseImageElement.addEventListener('error', onError); if (costume.textLayerAsset) { + textImageElement = new Image(); + textImageElement.addEventListener('load', onLoad); + textImageElement.addEventListener('error', onError); textImageElement.src = costume.textLayerAsset.encodeDataURI(); } else { loadedOne = true; @@ -97,7 +99,7 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { const ctx = canvas.getContext('2d'); ctx.drawImage(baseImageElement, 0, 0); - if (textImageElement.src) { + if (textImageElement) { ctx.drawImage(textImageElement, 0, 0); } if (scale !== 1) { @@ -120,7 +122,7 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { canvas: canvas, rotationCenter: rotationCenter, // True if the asset matches the base layer; false if it required adjustment - assetMatchesBase: scale === 1 && !textImageElement.src + assetMatchesBase: scale === 1 && !textImageElement }; }) .catch(e => Promise.reject(e)) @@ -135,7 +137,7 @@ const loadBitmap_ = function (costume, runtime, rotationCenter) { return fetchBitmapCanvas_(costume, runtime, rotationCenter).then(fetched => new Promise(resolve => { rotationCenter = fetched.rotationCenter; - const saveAssetToStorage = function (dataURI) { + const updateCostumeAsset = function (dataURI) { if (!runtime.v2BitmapAdapter) { return Promise.reject('No V2 Bitmap adapter present.'); } @@ -153,7 +155,7 @@ const loadBitmap_ = function (costume, runtime, rotationCenter) { }; if (!fetched.assetMatchesBase) { - saveAssetToStorage(fetched.canvas.toDataURL()); + updateCostumeAsset(fetched.canvas.toDataURL()); } resolve(fetched.canvas); })) @@ -183,7 +185,7 @@ const loadBitmap_ = function (costume, runtime, rotationCenter) { * @property {number} rotationCenterX - the X component of the costume's origin. * @property {number} rotationCenterY - the Y component of the costume's origin. * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume. - * @param {!Asset} costume.asset - the asset of the costume loaded from storage. + * @property {!Asset} costume.asset - the asset of the costume loaded from storage. * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set * to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0. diff --git a/src/serialization/deserialize-assets.js b/src/serialization/deserialize-assets.js index 4e0a0bafa..519e93223 100644 --- a/src/serialization/deserialize-assets.js +++ b/src/serialization/deserialize-assets.js @@ -111,7 +111,7 @@ const deserializeCostume = function (costume, runtime, zip, assetFileName, textL } // textLayerMD5 exists if there is a text layer, which is a png of text from Scratch 1.4 - // that was opened in Scratch 2.0. In this case, set costume,textLayerAsset. + // that was opened in Scratch 2.0. In this case, set costume.textLayerAsset. let textLayerFilePromise; if (costume.textLayerMD5) { textLayerFileName = textLayerFileName ? textLayerFileName : From c68da6605bdafe4d42533c0e17a01ddce51e2b01 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 20 Nov 2018 15:08:25 -0500 Subject: [PATCH 20/22] Move reject earlier and add loadedOne comment --- src/import/load-costume.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 5afdbb09d..05b214f7f 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -48,10 +48,16 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { if (!costume || !costume.asset) { return Promise.reject('Costume load failed. Assets were missing.'); } + if (!runtime.v2BitmapAdapter) { + return Promise.reject('No V2 Bitmap adapter present.'); + } + return new Promise((resolve, reject) => { const baseImageElement = new Image(); let textImageElement; + // We need to wait for 2 images total to load. loadedOne will be true when one + // is done, and we are just waiting for one more. let loadedOne = false; const onError = function () { @@ -103,9 +109,6 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { ctx.drawImage(textImageElement, 0, 0); } if (scale !== 1) { - if (!runtime.v2BitmapAdapter) { - return Promise.reject('No V2 Bitmap adapter present.'); - } canvas = runtime.v2BitmapAdapter.resize(canvas, canvas.width * scale, canvas.height * scale); } From 94227c00fa6385f620bea3860767734fc0dc44a4 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Wed, 21 Nov 2018 12:01:48 -0500 Subject: [PATCH 21/22] Remove catches that do nothing --- src/import/load-costume.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index 05b214f7f..8802acbb8 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -128,7 +128,6 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { assetMatchesBase: scale === 1 && !textImageElement }; }) - .catch(e => Promise.reject(e)) .finally(() => { // Clean up the costume object delete costume.textLayerMD5; @@ -162,7 +161,6 @@ const loadBitmap_ = function (costume, runtime, rotationCenter) { } resolve(fetched.canvas); })) - .catch(e => Promise.reject(e)) .then(canvas => { // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined... costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter); From 17d0033f1cf1c2fcf664a6b4b318883a3bf14a0e Mon Sep 17 00:00:00 2001 From: DD Liu Date: Wed, 21 Nov 2018 12:03:52 -0500 Subject: [PATCH 22/22] Remove conditional because text layer file name always comes from sb2 --- src/serialization/deserialize-assets.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/serialization/deserialize-assets.js b/src/serialization/deserialize-assets.js index 519e93223..451643649 100644 --- a/src/serialization/deserialize-assets.js +++ b/src/serialization/deserialize-assets.js @@ -114,8 +114,6 @@ const deserializeCostume = function (costume, runtime, zip, assetFileName, textL // that was opened in Scratch 2.0. In this case, set costume.textLayerAsset. let textLayerFilePromise; if (costume.textLayerMD5) { - textLayerFileName = textLayerFileName ? textLayerFileName : - `${costume.textLayerMD5}.png`; const textLayerFile = zip.file(textLayerFileName); if (!textLayerFile) { log.error(`Could not find text layer file associated with the ${costume.name} costume.`);