scrawl-canvas
Version:
Version 8.9.4 - 19 Nov 2022
197 lines (143 loc) • 9.82 kB
JavaScript
// # Asset mixin
// The asset factories - [ImageAsset](../factory/imageAsset.html), [SpriteAsset](../factory/spriteAsset.html), and [VideoAsset](../factory/videoAsset.html) - are wrappers around images and videos which can either be pulled from the current document (DOM-based assets) or fetched from the server using an URL address.
// + Assets are used (consumed) by Picture entitys and Pattern styles.
// + This mixin adds functionality common to all three factories to them as part of their initialization.
// + The mixin is also used by the [Cell](../factory/cell.html) factory, as <canvas> elements can be used by Picture entitys and Pattern styles as their image sources.
// Assets can be loaded into scrawl using dedicated import and create functions:
// + `scrawl.importImage`
// + `scrawl.importDomImage`
// + `scrawl.importDomVideo`
// + `scrawl.importVideo`
// + `scrawl.importMediaStream`
// + `scrawl.importSprite`
// + `scrawl.createImageFromCell`
// + `scrawl.createImageFromGroup`
// + `scrawl.createImageFromEntity`
//
// Assets will also be created when Picture entitys or Pattern styles are defined using a 'scrawl.make' function (`makePicture`, `makePattern`), or updated with the `set` function, where the _imageSource_, _videoSource_ or _spriteSource_ key in the argument object has been set to a valid URL path string.
//
// Be aware that __loading assets takes time!__
// + Performing a single render operation immediately after defining or updating a Picture entity or Pattern style will almost certainly fail to render the expected image/sprite/video in the canvas.
// + The load functionality is asynchronous (using Promises).
// + To display the resulting images in the canvas, it needs to be running an animation object (for instance, __scrawl.makeRender__) so that updates appear as soon as they have loaded, as part of the animation's Display cycle functionality.
// #### Imports
import { mergeOver, pushUnique, λnull, Ωempty } from '../core/utilities.js';
// #### Export function
export default function (P = Ωempty) {
// #### Shared attributes
let defaultAttributes = {
// __sourceLoaded__ - A flag to indicate that the source image or video has completed sufficient loading (or streaming) to be usefully consumed by Picture entitys and Pattern styles.
// + __Note that this flag cannot be directly or indirectly set.__ The asset wrapper's functionality will handle the checking of load progress, and notify its subscribers that the load has completed as-and-when required.
sourceLoaded: false,
// __source__ - A handle to the DOM element supplying the image - either an <img>, <video> or <canvas> element.
// + Note that the ___Web Canvas API does not support___ using the <picture> element as a legitimate image source.
// + However most browsers allow the <img> element to use __srcset__ and __sizes__ attributes, which will give them the same type of functionality as picture elements - for example to determine the most appropriately sized image file for the browser/device's viewport dimensions.
// + Asset wrappers will detect, and handle actions required, if/when a browser decides to download a new image file, to update the <img> element with a more detailed image, for instance when the browser viewport changes its dimensions, or a device is rotated from portrait to landscape viewing.
// + The web Canvas API claims that __SVG__ images can be used as legitimate image sources for the canvas element. To make this happen, the SVG file needs to be set as the src attribute of an <img> element, and the resulting image will be entirely static, and rasterized!
// + For this reason, we don't recommend trying to use SVG files for canvas image sources; instead, the SVG can be included in a Scrawl-canvas display by adding it - as an <svg> element - to a Scrawl-canvas stack (see Demo DOM-003 for an example of this approach).
// + <img> elements using an animated GIF as their src are not supported.
// + ___Be aware that this attribute cannot be directly or indirectly set.___ Scrawl-canvas will update it as part of its asset loading and wrapper creation functionality.
source: null,
// __subscribers__ - An Array containing the Picture entity and Pattern style Objects, who wish to use the asset as their source.
// + Pictures and Patterns may subscribe to a maximum of ONE asset at any given time, though they may set/update that asset subscription to a different asset whenever required (which involves unsubscribing their existing asset).
// + ___Note that the contents of this Array cannot be directly or indirectly set.___ Picture entitys and Pattern styles will subscribe and unsubscribe to an asset as part of their source acquisition functionality (via the asset wrapper's __subscribe__ and __unsubscribe__ functions).
subscribers: null,
};
P.defs = mergeOver(P.defs, defaultAttributes);
// #### Packet management
// Assets do not include their source images (or videos!) in their packet output. They do include the String name values of each of their subscribers.
P.packetExclusions = pushUnique(P.packetExclusions, ['sourceLoaded', 'source', 'subscribers']);
P.finalizePacketOut = function (copy, items) {
if (this.subscribers && this.subscribers.length) {
copy.subscribers = this.subscribers.map(sub => sub.name);
}
return copy;
};
// #### Clone management
// No additional clone functionality defined here
// #### Kill management
P.kill = function (removeDomEntity = false) {
if (removeDomEntity && this.source) this.source.remove();
return this.deregister();
};
// #### Get, Set, deltaSet
let G = P.getters,
S = P.setters,
D = P.deltaSetters;
// __source__ - imageAsset.js and videoAsset.js overwrite this function, thus only put here so cell.js also gains the function - which I don't think it will ever need as cells ARE their own source.
S.source = function (item) {
if (item) {
// No action required for Canvas objects as they don't have a source; they ARE the source!
if (this.sourceLoaded) this.notifySubscribers();
}
};
// __subscribers__ - we disable the ability to set the subscribers Array directly. Picture entitys and Pattern styles will manage their subscription to the asset using their subscribe() and unsubscribe() functions.
S.subscribers = λnull;
// #### Prototype functions
// `assetConstructor` - Common actions required by __imageAsset__, __spriteAsset__, and __videoAsset__ factories as part if their instance constructor work.
P.assetConstructor = function (items) {
this.makeName(items.name);
this.register();
this.subscribers = [];
this.set(this.defs);
this.set(items);
if (items.subscribe) this.subscribers.push(items.subscribe);
return this;
};
// ##### Subscribe and unsubscribe to an asset
// `subscribe`
P.subscribe = function (sub) {
if (sub && sub.name) {
let name = sub.name;
if (this.subscribers.every(item => item.name !== name)) this.subscribeAction(sub);
}
};
// `subscribeAction` - separated out because cells handle things differently (they ARE the source)
P.subscribeAction = function (sub) {
if (sub) {
this.subscribers.push(sub);
sub.asset = this;
sub.source = this.source;
this.notifySubscriber(sub);
}
};
// `unsubscribe`
P.unsubscribe = function (sub) {
if (sub && sub.name) {
let name = sub.name,
index = this.subscribers.findIndex(item => item.name === name);
if (index >= 0) {
sub.source = null;
sub.asset = null;
sub.sourceNaturalHeight = 0;
sub.sourceNaturalWidth = 0;
sub.sourceLoaded = false;
this.subscribers.splice(index, 1)
}
}
};
// `notifySubscribers`, `notifySubscriber` - Subscriber notification in the asset factories will happen when something changes with the image. Changes vary across the different types of asset:
// + __imageAsset__ - needs to update its subscribers when an image completes loading - or, for <img> sources with srcset (and sizes) attributes, when the image completes a reload of its source data.
// + __spriteAsset__ - will also update its subscribers each time it moves to a new sprite image frame, if the sprite is being animated
// + __videoAsset__ - will update its subscribers for every RAF tick while the video is playing, or if the video is halted and seeks to a different time in the video play stream.
// + __noiseAsset__ - will update its subscribers when any of its attributes changes (Note: factory/noiseAsset.js overwrites these functions).
// + __reactionDiffusionAsset__ - will update its subscribers when any of its attributes changes (Note: factory/reactionDiffusionAsset.js overwrites these functions).
//
// All notifications are push; the notification is achieved by setting various attributes and flags in each subscriber.
P.notifySubscribers = function () {
this.subscribers.forEach(sub => this.notifySubscriber(sub), this);
};
P.notifySubscriber = function (sub) {
sub.sourceNaturalWidth = this.sourceNaturalWidth;
sub.sourceNaturalHeight = this.sourceNaturalHeight;
sub.sourceLoaded = this.sourceLoaded;
sub.source = this.source;
sub.dirtyImage = true;
sub.dirtyCopyStart = true;
sub.dirtyCopyDimensions = true;
sub.dirtyImageSubscribers = true;
sub.dirtyFilterIdentifier = true;
};
// Return the prototype
return P;
};