From f4488d1b6059727e89059b6a26f8103c328d5fa8 Mon Sep 17 00:00:00 2001 From: Florrie Date: Sun, 3 Sep 2017 20:34:34 -0300 Subject: [PATCH 1/7] 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 0000000000..a7063ffdaa --- /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 48e89f4489..d32c29d6e2 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 2/7] 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 85308a3b05..0000000000 --- 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 3/7] 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 fe8ea5ef46..502e5ec366 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 4/7] 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 502e5ec366..b936b54cd6 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 5/7] Fix eslint errors --- src/import/.load-costume.js.swp | Bin 0 -> 24576 bytes src/import/load-costume.js | 114 +++++++++++++++++--------------- src/serialization/.sb2.js.swp | Bin 0 -> 45056 bytes src/serialization/sb2.js | 4 +- 4 files changed, 63 insertions(+), 55 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 b936b54cd6..7a9aa95886 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(); - - 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; - - 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); + ]) + .then(costumeAssets => ( + new Promise((resolve, reject) => { + const baseImageElement = new Image(); + const textImageElement = new Image(); - return costume; - }); + 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); + + costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter); + + 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 b16cac49c2..66424b8a24 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 6/7] 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 7a9aa95886..d08aae2bf6 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 7/7] 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 d08aae2bf6..d02b69e1c9 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();