mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 23:12:24 -05:00
Break load costume into more functions, and update how bitmaps are loaded
This commit is contained in:
parent
e156b0536c
commit
6aefd13af6
3 changed files with 114 additions and 65 deletions
|
@ -995,6 +995,15 @@ class Runtime extends EventEmitter {
|
||||||
this.v2SvgAdapter = svgAdapter;
|
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
|
* Attach the storage module
|
||||||
* @param {!ScratchStorage} storage The storage module to attach
|
* @param {!ScratchStorage} storage The storage module to attach
|
||||||
|
|
|
@ -1,6 +1,99 @@
|
||||||
const StringUtil = require('../util/string-util');
|
const StringUtil = require('../util/string-util');
|
||||||
const log = require('../util/log');
|
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.
|
* Initialize a costume from an asset asynchronously.
|
||||||
* Do not call this unless there is a renderer attached.
|
* 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
|
// Use provided rotation center and resolution if they are defined. Bitmap resolution
|
||||||
// should only ever be 1 or 2.
|
// should only ever be 1 or 2.
|
||||||
if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) &&
|
if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) &&
|
||||||
typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY) &&
|
typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) {
|
||||||
costume.bitmapResolution) {
|
rotationCenter = [costume.rotationCenterX, costume.rotationCenterY];
|
||||||
rotationCenter = [
|
|
||||||
costume.rotationCenterX / costume.bitmapResolution,
|
|
||||||
costume.rotationCenterY / costume.bitmapResolution
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (costumeAsset.assetType === AssetType.ImageVector) {
|
if (costumeAsset.assetType === AssetType.ImageVector) {
|
||||||
let svgString = costumeAsset.decodeText();
|
return loadVector_(costume, costumeAsset, runtime, rotationCenter, optVersion);
|
||||||
// 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
|
return loadBitmap_(costume, costumeAsset, runtime, rotationCenter, optVersion);
|
||||||
// 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 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;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -895,6 +895,14 @@ class VirtualMachine extends EventEmitter {
|
||||||
this.runtime.attachV2SVGAdapter(svgAdapter);
|
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
|
* Set the storage module for the VM/runtime
|
||||||
* @param {!ScratchStorage} storage The storage module to attach
|
* @param {!ScratchStorage} storage The storage module to attach
|
||||||
|
|
Loading…
Reference in a new issue