From 4f264d3e50c09bc3859f8692bf15ba767e9d1227 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 23 Jul 2015 20:44:05 -0700 Subject: [PATCH] Brought in updated PixelRendr with lazy loading Renders and SpriteMultiples internally. --- Source/PixelDrawr.ts | 53 +-- Source/References/PixelRendr-0.2.0.ts | 656 ++++++++++++++++---------- 2 files changed, 430 insertions(+), 279 deletions(-) diff --git a/Source/PixelDrawr.ts b/Source/PixelDrawr.ts index 4355339..677ac19 100644 --- a/Source/PixelDrawr.ts +++ b/Source/PixelDrawr.ts @@ -336,9 +336,9 @@ module PixelDrawr { thing.sprite = this.PixelRender.decode(this.generateObjectKey(thing), thing); // To do: remove dependency on .numSprites - // For now, kit's used to know whether it's had its sprite set, but + // For now, it's used to know whether it's had its sprite set, but // wouldn't physically having a .sprite do that? - if ((thing.sprite).multiple) { + if (thing.sprite.constructor === PixelRendr.SpriteMultiple) { thing.numSprites = 0; this.refillThingCanvasMultiple(thing); } else { @@ -381,7 +381,7 @@ module PixelDrawr { return; } - var spritesRaw: PixelRendr.ISpriteMultiple = thing.sprite, + var spritesRaw: PixelRendr.SpriteMultiple = thing.sprite, canvases: any = thing.canvases = { "direction": spritesRaw.direction, "multiple": true @@ -497,8 +497,7 @@ module PixelDrawr { && quadrant[this.keyTop] < this.MapScreener[this.keyHeight] && quadrant[this.keyRight] > 0 && quadrant[this.keyBottom] > 0 - && quadrant[this.keyLeft] < this.MapScreener[this.keyWidth] - ) { + && quadrant[this.keyLeft] < this.MapScreener[this.keyWidth]) { this.refillQuadrant(quadrant); this.context.drawImage(quadrant.canvas, quadrant[this.keyLeft], quadrant[this.keyTop]); } @@ -528,8 +527,7 @@ module PixelDrawr { 0, 0, quadrant.canvas[this.keyWidth], - quadrant.canvas[this.keyHeight] - ); + quadrant.canvas[this.keyHeight]); } for (i = this.groupNames.length - 1; i >= 0; i -= 1) { @@ -560,8 +558,7 @@ module PixelDrawr { || this.getTop(thing) > this.MapScreener[this.keyHeight] || this.getRight(thing) < 0 || this.getBottom(thing) < 0 - || this.getLeft(thing) > this.MapScreener[this.keyWidth] - ) { + || this.getLeft(thing) > this.MapScreener[this.keyWidth]) { return; } @@ -594,8 +591,7 @@ module PixelDrawr { || this.getRight(thing) < quadrant[this.keyLeft] || this.getBottom(thing) < quadrant[this.keyTop] || this.getLeft(thing) > quadrant[this.keyRight] - || thing.opacity < this.epsilon - ) { + || thing.opacity < this.epsilon) { return; } @@ -606,8 +602,7 @@ module PixelDrawr { thing.canvas, thing, this.getLeft(thing) - quadrant[this.keyLeft], - this.getTop(thing) - quadrant[this.keyTop] - ); + this.getTop(thing) - quadrant[this.keyTop]); } else { // For multiple sprites, some calculations will be needed return this.drawThingOnContextMultiple( @@ -615,8 +610,7 @@ module PixelDrawr { thing.canvases, thing, this.getLeft(thing) - quadrant[this.keyLeft], - this.getTop(thing) - quadrant[this.keyTop] - ); + this.getTop(thing) - quadrant[this.keyTop]); } } @@ -666,7 +660,7 @@ module PixelDrawr { thing: IThing, left: number, top: number): void { - var sprite: PixelRendr.ISpriteMultiple = thing.sprite, + var sprite: PixelRendr.SpriteMultiple = thing.sprite, topreal: number = top, leftreal: number = left, rightreal: number = left + thing.unitwidth, @@ -744,8 +738,7 @@ module PixelDrawr { topreal, widthdrawn, heightdrawn, - opacity - ); + opacity); this.drawPatternOnContext( context, canvases[this.keyLeft].canvas, @@ -753,8 +746,7 @@ module PixelDrawr { topreal + diffvert, widthdrawn, heightreal - diffvert * 2, - opacity - ); + opacity); this.drawPatternOnContext( context, canvases.bottomLeft.canvas, @@ -762,8 +754,7 @@ module PixelDrawr { bottomreal - diffvert, widthdrawn, heightdrawn, - opacity - ); + opacity); leftreal += diffhoriz; widthreal -= diffhoriz; @@ -776,8 +767,7 @@ module PixelDrawr { topreal, widthreal - diffhoriz, heightdrawn, - opacity - ); + opacity); this.drawPatternOnContext( context, canvases.topRight.canvas, @@ -785,8 +775,7 @@ module PixelDrawr { topreal, widthdrawn, heightdrawn, - opacity - ); + opacity); topreal += diffvert; heightreal -= diffvert; @@ -799,8 +788,7 @@ module PixelDrawr { topreal, widthdrawn, heightreal - diffvert, - opacity - ); + opacity); this.drawPatternOnContext( context, canvases.bottomRight.canvas, @@ -808,8 +796,7 @@ module PixelDrawr { bottomreal - diffvert, widthdrawn, heightdrawn, - opacity - ); + opacity); this.drawPatternOnContext( context, canvases[this.keyBottom].canvas, @@ -817,8 +804,7 @@ module PixelDrawr { bottomreal - diffvert, widthreal - diffhoriz, heightdrawn, - opacity - ); + opacity); rightreal -= diffhoriz; widthreal -= diffhoriz; bottomreal -= diffvert; @@ -928,8 +914,7 @@ module PixelDrawr { 0, 0, // Math.max(width, left - MapScreener[keyRight]), // Math.max(height, top - MapScreener[keyBottom]) - width, height - ); + width, height); context.translate(-left, -top); context.globalAlpha = 1; } diff --git a/Source/References/PixelRendr-0.2.0.ts b/Source/References/PixelRendr-0.2.0.ts index 653a3aa..93078cd 100644 --- a/Source/References/PixelRendr-0.2.0.ts +++ b/Source/References/PixelRendr-0.2.0.ts @@ -2,15 +2,63 @@ /// declare module PixelRendr { + export interface ILibrary { + raws: any; + sprites?: IRenderLibrary; + } + + export interface IRender { + reference: string[]; + source: string | any[]; + sprites: IRenderSprites; + container: IRenderLibrary; + key: string; + filter: IFilterAttributes; + } + + export interface IRenderLibrary { + [i: string]: IRenderLibrary | IRender; + } + + export interface IRenderSprites { + [i: string]: Uint8ClampedArray | ISpriteMultiple; + } + + export interface IGeneralSpriteGenerator { + (render: IRender, key: string, attributes: ISpriteAttributes): Uint8ClampedArray | ISpriteMultiple; + } + export interface IPixelRendrEncodeCallback { (result: string, image: HTMLImageElement, source: any): any; } + export interface IClampedArraysContainer { + [i: string]: Uint8ClampedArray; + } + + export interface ISpriteAttributes { + filter?: IFilter; + [i: string]: number | IFilter; + } + + export interface IFilter { + 0: string; + 1: { + [i: string]: string; + } + } + + export interface IFilterContainer { + [i: string]: IFilter; + } + + export interface IFilterAttributes { + filter: IFilter; + } + export interface ISpriteMultiple { + sprites: IClampedArraysContainer; direction: string; - multiple: boolean; - sprites: any; - processed: boolean; topheight: number; rightwidth: number; bottomheight: number; @@ -33,7 +81,7 @@ declare module PixelRendr { /** * Filters that may be used by sprites in the library. */ - filters?: any; + filters?: IFilterContainer; /** * An amount to expand sprites by when processing (by default, 1 for not at @@ -94,7 +142,7 @@ declare module PixelRendr { /** * A typed array of 8-bit unsigned integer values. The contents are initialized * to 0. If the requested number of bytes could not be allocated an exception is - * raised. + * raised. */ interface Uint8ClampedArray extends ArrayBufferView { [index: number]: number; @@ -111,7 +159,8 @@ interface Uint8ClampedArray extends ArrayBufferView { /** * Gets the element at the specified index. - * @param index The index at which to get the element of the array. + * + * @param {Number} index The index at which to get the element of the array. */ get(index: number): number; @@ -162,32 +211,138 @@ declare var Uint8ClampedArray: { } +/** + * + */ module PixelRendr { "use strict"; + /** + * Summary container for a single PixelRendr sprite source. The original source + * is stored, along with any generated outputs, information on its container, + * and any filter. + */ + export class Render implements IRender { + /** + * The original command to create this Render, which is either a plain + * String source or an Array representing a command. + */ + source: string | any[]; + + /** + * Output sprites generated by the source, keyed by the calling key. + */ + sprites: IRenderSprites; + + /** + * The container directory storing this Render. + */ + container: IRenderLibrary; + + /** + * The key under which this Render is stored in its container directory. + */ + key: string; + + /** + * An optional path to another Render to reference, if source is a command. + */ + reference: string[]; + + /** + * An optional filter to change colors by, if source is a "filter" command. + */ + filter: IFilterAttributes; + + /** + * Resets the Render. No sprite computation is done here, so sprites is + * initialized to an empty container. + */ + constructor(source: string | any[], reference?: string[], filter?: IFilterAttributes) { + this.source = source; + this.reference = reference; + this.filter = filter; + + this.sprites = {}; + } + } + + /** + * Container for a "multiple" sprite, which is a sprite that contains separate + * Uint8ClampedArray pieces of data for different sections (such as top, middle, etc.) + */ + export class SpriteMultiple implements ISpriteMultiple { + /** + * Storage for each internal Uint8ClampedArray sprite, keyed by container. + */ + sprites: IClampedArraysContainer; + + /** + * The direction of sprite, such as "horizontal". + */ + direction: string; + + /** + * How many pixels tall the top section is, if it exists. + */ + topheight: number; + + /** + * How many pixels wide the right section is, if it exists. + */ + rightwidth: number; + + /** + * How many pixels tall the bottom section is, if it exists. + */ + bottomheight: number; + + /** + * How many pixels wide the left section is, if it exists. + */ + leftwidth: number; + + /** + * Whether the middle section should be stretched to fill the remaining + * space instead of filling as a pattern. + */ + middleStretch: boolean; + + /** + * Stores an already-computed container of sprites, and sets the direction + * sizes and middleStretch accordingly. + */ + constructor(sprites: IClampedArraysContainer, render: Render) { + var sources: any = render.source[2]; + + this.sprites = sprites; + this.direction = render.source[1]; + + if (this.direction === "vertical" || this.direction === "corners") { + this.topheight = sources.topheight | 0; + this.bottomheight = sources.bottomheight | 0; + } + + if (this.direction === "horizontal" || this.direction === "corners") { + this.rightwidth = sources.rightwidth | 0; + this.leftwidth = sources.leftwidth | 0; + } + + this.middleStretch = sources.middleStretch || false; + } + } + /** * A moderately unusual graphics module designed to compress images as * compressed text blobs and store the text blobs in a StringFilr. These tasks * are performed and cached quickly enough for use in real-time environments, * such as real-time video games. - * - * @todo - * The first versions of this library were made many years ago by an - * inexperienced author, and have undergone only moderate structural revisions - * since. There are two key improvements that should happen: - * 1. On reset, the source library should be mapped to a PartialRender class - * that stores loading status and required ("post") references, to enable - * lazy loading. See #71. - * 2. Once lazy loading is implemented for significantly shorter startup times, - * an extra layer of compression should be added to compress the technically - * human-readable String sources to a binary-ish format. See #236. - * 3. Rewrite the heck out of this piece of crap. */ export class PixelRendr implements IPixelRendr { /** * The base container for storing sprite information. */ - private library: any; + private library: ILibrary; /** * A StringFilr interface on top of the base library. @@ -227,7 +382,6 @@ module PixelRendr { */ private digitsplit: RegExp; - // How much to "scale" each sprite by (repeat the pixels this much). /** * How much to "scale" each sprite by (repeat the pixels this much). */ @@ -238,7 +392,7 @@ module PixelRendr { * based on supplied attributes. */ private flipVert: string; - + /** * String key to know whether to flip a processed sprite horizontally, * based on supplied attributes. @@ -249,7 +403,7 @@ module PixelRendr { * String key to obtain sprite width from supplied attributes. */ private spriteWidth: string; - + /** * String key to obtain sprite height from supplied attributes. */ @@ -258,7 +412,14 @@ module PixelRendr { /** * Filters for processing sprites. */ - private filters: any; + private filters: IFilterContainer; + + /** + * Generators used to generate Renders from sprite commands. + */ + private commandGenerators: { + [i: string]: IGeneralSpriteGenerator; + }; /** * A reference for window.Uint8ClampedArray, or replacements such as @@ -273,22 +434,20 @@ module PixelRendr { if (!settings) { throw new Error("No settings given to PixelRendr."); } - if (!settings.paletteDefault) { throw new Error("No paletteDefault given to PixelRendr."); } + this.paletteDefault = settings.paletteDefault; this.digitsizeDefault = this.getDigitSize(this.paletteDefault); this.digitsplit = new RegExp(".{1," + this.digitsizeDefault + "}", "g"); this.library = { - "raws": settings.library || {}, - "posts": [] + "raws": settings.library || {} }; this.filters = settings.filters || {}; - this.scale = settings.scale || 1; this.flipVert = settings.flipVert || "flip-vert"; @@ -299,8 +458,7 @@ module PixelRendr { this.Uint8ClampedArray = (settings.Uint8ClampedArray || (window).Uint8ClampedArray - || (window).Uint8Array - ); + || (window).Uint8Array); // The first ChangeLinr does the raw processing of Strings to sprites // This is used to load & parse sprites into memory on startup @@ -349,16 +507,19 @@ module PixelRendr { "doUseCache": false }); - this.library.sprites = this.libraryParse(this.library.raws, ""); - - // Post commands are evaluated after the first processing run - this.libraryPosts(); + this.library.sprites = this.libraryParse(this.library.raws); // The BaseFiler provides a searchable 'view' on the library of sprites this.BaseFiler = new StringFilr.StringFilr({ "library": this.library.sprites, "normal": "normal" // to do: put this somewhere more official? }); + + this.commandGenerators = { + "multiple": this.generateSpriteCommandMultipleFromRender.bind(this), + "same": this.generateSpriteCommandSameFromRender.bind(this), + "filter": this.generateSpriteCommandFilterFromRender.bind(this) + }; } @@ -406,8 +567,8 @@ module PixelRendr { /** * @param {String} key * @return {Mixed} Returns the base sprite for a key. This will either be a - * Uint8ClampedArray if a sprite is found, or the deepest - * Object in the library. + * Uint8ClampedArrayor SpriteMultiple if a sprite is found, + * or the deepest matching Object in the library. */ getSpriteBase(key: string): void { return this.BaseFiler.get(key); @@ -428,26 +589,23 @@ module PixelRendr { * and height Numbers are required. * @return {Uint8ClampedArray} */ - decode(key: string, attributes: any): Uint8ClampedArray | ISpriteMultiple { - // BaseFiler stores the cache of the base sprites. Note that it doesn't - // actually require the extra attributes - var sprite: Uint8ClampedArray | ISpriteMultiple = this.BaseFiler.get(key); + decode(key: string, attributes: ISpriteAttributes): Uint8ClampedArray | SpriteMultiple { + var render: Render = this.BaseFiler.get(key), + sprite: Uint8ClampedArray | SpriteMultiple; - if (!sprite) { - throw new Error("No raw sprite found for " + key + "."); + if (!render) { + throw new Error("No sprite found for " + key + "."); } - // Multiple sprites have their sizings taken from attributes - if ((sprite).multiple) { - if (!(sprite).processed) { - this.processSpriteMultiple(sprite, key, attributes); - } - } else { - // Single (actual) sprites process for size (row) scaling, and flipping - if (!(sprite instanceof this.Uint8ClampedArray)) { - throw new Error("No single raw sprite found for: '" + key + "'"); - } - sprite = this.ProcessorDims.process(sprite, key, attributes); + // If the render doesn't have a listing for this key, create one + if (!render.sprites.hasOwnProperty(key)) { + this.generateRenderSprite(render, key, attributes); + } + + sprite = render.sprites[key]; + + if (!sprite || (sprite.constructor === Uint8ClampedArray && (sprite).length === 0)) { + throw new Error("Could not generate sprite for " + key + "."); } return sprite; @@ -484,7 +642,7 @@ module PixelRendr { */ encodeUri(uri: string, callback: IPixelRendrEncodeCallback): void { var image: HTMLImageElement = document.createElement("img"); - image.onload = this.encode.bind(self, image, callback); + image.onload = this.encode.bind(this, image, callback); image.src = uri; } @@ -528,8 +686,7 @@ module PixelRendr { tree[data[i]] && tree[data[i]][data[i + 1]] && tree[data[i]][data[i + 1]][data[i + 2]] - && tree[data[i]][data[i + 1]][data[i + 2]][data[i + 3]] - ) { + && tree[data[i]][data[i + 1]][data[i + 2]][data[i + 3]]) { continue; } @@ -610,17 +767,7 @@ module PixelRendr { readloc: number = 0, writeloc: number = 0, writelength: number = Math.max(0, Math.min(source.length, destination.length))): void { - if (!source || !destination || readloc < 0 || writeloc < 0 || writelength <= 0) { - return; - } - if (readloc >= source.length || writeloc >= destination.length) { - // console.log("Alert: memcpyU8 requested out of bounds!"); - // console.log("source, destination, readloc, writeloc, writelength"); - // console.log(arguments); - return; - } - - // JIT compilcation help + // JIT compilation help var lwritelength: number = writelength + 0, lwriteloc: number = writeloc + 0, lreadloc: number = readloc + 0; @@ -635,17 +782,16 @@ module PixelRendr { */ /** - * Recursive Function to go throw a library and parse it. A copy of the - * structure is made where each result is either a parsed sprite, a - * placeholder for a post command, or a recursively generated child Object. + * Recursively travels through a library, turning all raw String sprites + * and any[] commands into Renders. * * @param {Object} reference The raw source structure to be parsed. * @param {String} path The path to the current place within the library. * @return {Object} The parsed library Object. */ - private libraryParse(reference: any, path: string): any { - var setnew: any = {}, - objref: any, + private libraryParse(reference: any): IRenderLibrary { + var setNew: IRenderLibrary = {}, + source: any, i: string; // For each child of the current layer: @@ -654,167 +800,99 @@ module PixelRendr { continue; } - objref = reference[i]; - switch (objref.constructor) { - // If it's a string, parse it + source = reference[i]; + + switch (source.constructor) { case String: - setnew[i] = this.ProcessorBase.process(objref, path + " " + i); + // Strings directly become IRenders + setNew[i] = new Render(source); break; - // If it's an array, it should have a command such as 'same' to be post-processed + case Array: - this.library.posts.push({ - caller: setnew, - name: i, - command: reference[i], - path: path + " " + i - }); + // Arrays contain a String filter, a String[] source, and any + // number of following arguments + setNew[i] = new Render(source, source[1]); break; - // If it's anything else, simply recurse + default: - setnew[i] = this.libraryParse(objref, path + " " + i); + // If it's anything else, simply recurse + setNew[i] = this.libraryParse(source); break; } - } - return setnew; - } - - /** - * Driver to evaluate post-processing commands, such as copies and filters. - * This is run after the main processing finishes. Each post command is - * given to evaluatePost. - */ - private libraryPosts(): void { - var posts: any[] = this.library.posts, - post: any, - i: number; - - for (i = 0; i < posts.length; i += 1) { - post = posts[i]; - post.caller[post.name] = this.evaluatePost(post.caller, post.command, post.path); - } - } - - /** - * Evaluates a post command and returns the result to be used in the - * library. It can be "same", "filter", or "vertical". - * - * @param {Object} caller The place within the library store results in. - * @param {Array} command The command from the library, represented as - * ["type", [info...]] - * @param {String} path The path to the caller. - */ - private evaluatePost(caller: any, command: any[], path: string): any { - var spriteRaw: any, - filter: any; - - switch (command[0]) { - // Same: just returns a reference to the target - // ["same", ["container", "path", "to", "target"]] - case "same": - spriteRaw = this.followPath(this.library.raws, command[1], 0); - - if (spriteRaw.constructor === String) { - return this.ProcessorBase.process(spriteRaw, path); - } else if (spriteRaw.constructor === Array) { - return this.evaluatePost(caller, spriteRaw, path); - } - return this.libraryParse(spriteRaw, path); - - // Filter: takes a reference to the target, and applies a filter to it - // ["filter", ["container", "path", "to", "target"], filters.DoThisFilter] - case "filter": - // Find the sprite this should be filtering from - spriteRaw = this.followPath(this.library.raws, command[1], 0); - filter = this.filters[command[2]]; - - if (!filter) { - console.warn("Invalid filter provided:", command[2], this.filters); - filter = {}; - } - return this.evaluatePostFilter(spriteRaw, path, filter); - - // Multiple: uses more than one image, either vertically or horizontally - // Not to be confused with having .repeat = true. - // ["multiple", "vertical", { - // top: "...", // (just once at the top) - // middle: "..." // (repeated after top) - // } - case "multiple": - return this.evaluatePostMultiple(path, command); - - // Commands not evaluated by the switch are unknown and bad - default: - console.warn("Unknown post command: '" + command[0] + "'.", caller, command, path); - } - - } - - /** - * Driver function to recursively apply a filter on a sprite or Object. - * - * @param {Mixed} spriteRaw What the filter is being applied on (either a - * sprite, or a collection of sprites). - * @param {String} path The path to the spriteRaw in the library. - * @param {Object} filter The pre-determined filter to apply. - */ - private evaluatePostFilter(spriteRaw: any, path: string, filter: any): any { - // If it's just a String, process the sprite normally - if (spriteRaw.constructor === String) { - return this.ProcessorBase.process(spriteRaw, path, { - filter: filter - }); - } - - // If it's an Array, that's a post that hasn't yet been evaluated: evaluate it by the path - if (spriteRaw instanceof Array) { - return this.evaluatePostFilter(this.followPath(this.library.raws, spriteRaw[1], 0), spriteRaw[1].join(" "), filter); - } - - // If it's a generic Object, go recursively on its children - if (spriteRaw instanceof Object) { - var output: any = {}, - i: string; - - for (i in spriteRaw) { - if (spriteRaw.hasOwnProperty(i)) { - output[i] = this.evaluatePostFilter(spriteRaw[i], path + " " + i, filter); - } + // If a Render was created, its container should be setNew + if (setNew[i].constructor === Render) { + (setNew[i]).container = setNew; + (setNew[i]).key = i; } - - return output; } - // Anything else is a complaint - console.warn("Invalid sprite provided for a post filter.", spriteRaw, path, filter); + return setNew; } /** - * Creates a SpriteMultiple based on a library's command. + * Generates a sprite for a Render based on its internal source and an + * externally given String key and attributes Object. The sprite is stored + * in the Render's sprites container under that key. * - * @param {String} path The path to the SpriteMultiple. - * @param {Array} command The instructions from the library, in the form - * ["multiple", "{direction}", {Information}]. + * @param {Render} render A render whose sprite is being generated. + * @param {String} key The key under which the sprite is stored. + * @param {Object} attributes Any additional information to pass to the + * sprite generation process. + * @return {Mixed} The output sprite; either a Uint8ClampedArray or SpriteMultiple. */ - private evaluatePostMultiple(path: string, command: any[]): ISpriteMultiple { - var direction: string = command[1], - sections: any = command[2], - output: ISpriteMultiple = { - "direction": direction, - "multiple": true, - "sprites": {}, - "processed": false, - "topheight": sections.topheight | 0, - "rightwidth": sections.rightwidth | 0, - "bottomheight": sections.bottomheight | 0, - "leftwidth": sections.leftwidth | 0, - "middleStretch": sections.middleStretch || false - }, + private generateRenderSprite(render: Render, key: string, attributes: ISpriteAttributes): void { + var sprite: Uint8ClampedArray | SpriteMultiple; + + if (render.source.constructor === String) { + sprite = this.generateSpriteSingleFromRender(render, key, attributes); + } else { + sprite = this.commandGenerators[render.source[0]](render, key, attributes); + } + + render.sprites[key] = sprite; + } + + /** + * Generates the pixel data for a single sprite. + * + * @param {Render} render A render whose sprite is being generated. + * @param {String} key The key under which the sprite is stored. + * @param {Object} attributes Any additional information to pass to the + * sprite generation process. + * @return {Mixed} The output sprite; either a Uint8ClampedArray or SpriteMultiple. + */ + private generateSpriteSingleFromRender(render: Render, key: string, attributes: ISpriteAttributes): Uint8ClampedArray { + var base: Uint8ClampedArray = this.ProcessorBase.process(render.source, key, render.filter), + sprite: Uint8ClampedArray = this.ProcessorDims.process(base, key, attributes); + + return sprite; + } + + /** + * Generates the pixel data for a SpriteMultiple to be generated by creating + * a container in a new SpriteMultiple and filing it with processed single + * sprites. + * + * @param {Render} render A render whose sprite is being generated. + * @param {String} key The key under which the sprite is stored. + * @param {Object} attributes Any additional information to pass to the + * sprite generation process. + * @return {Mixed} The output sprite; either a Uint8ClampedArray or SpriteMultiple. + */ + private generateSpriteCommandMultipleFromRender(render: Render, key: string, attributes: ISpriteAttributes): SpriteMultiple { + var sources: any = render.source[2], + sprites: IClampedArraysContainer = {}, + sprite: Uint8ClampedArray, + path: string, + output: SpriteMultiple = new SpriteMultiple(sprites, render), i: string; - for (i in sections) { - if (sections.hasOwnProperty(i)) { - output.sprites[i] = this.ProcessorBase.process(sections[i], path + direction + i); + for (i in sources) { + if (sources.hasOwnProperty(i)) { + path = key + " " + i; + sprite = this.ProcessorBase.process(sources[i], path, render.filter); + sprites[i] = this.ProcessorDims.process(sprite, path, attributes); } } @@ -822,26 +900,114 @@ module PixelRendr { } /** - * Processes each of the components in a SpriteMultiple. These are all - * individually processed using the attributes by the dimensions processor. - * Each sub-sprite will be processed as if it were in a sub-Object referred - * to by the path (so if path is "foo bar", "foo bar middle" will be the - * middle sprite's key). + * Generates the output of a "same" command. The referenced Render or + * directory are found, assigned to the old Render's directory, and + * this.decode is used to find the output. * - * @param {SpriteMultiple} sprite - * @param {String} key - * @param {Object} attributes + * @param {Render} render A render whose sprite is being generated. + * @param {String} key The key under which the sprite is stored. + * @param {Object} attributes Any additional information to pass to the + * sprite generation process. + * @return {Mixed} The output sprite; either a Uint8ClampedArray or SpriteMultiple. */ - private processSpriteMultiple(sprite: ISpriteMultiple, key: string, attributes: any): void { - var i: string; + private generateSpriteCommandSameFromRender( + render: Render, + key: string, + attributes: ISpriteAttributes): Uint8ClampedArray | SpriteMultiple { + // The (now temporary) Render's container is given the Render or directory + // referenced by the source path + render.container[render.key] = this.followPath(this.library.sprites, render.source[1], 0); - for (i in sprite.sprites) { - if (sprite.sprites[i] instanceof this.Uint8ClampedArray) { - sprite.sprites[i] = this.ProcessorDims.process(sprite.sprites[i], key + " " + i, attributes); + // BaseFiler will need to remember the new entry for the key, + // so the cache is cleared and decode restarted + this.BaseFiler.clearCached(key); + return this.decode(key, attributes); + } + + /** + * Generates the output of a "filter" command. The referenced Render or + * directory are found, converted into a filtered Render or directory, and + * this.decode is used to find the output. + * + * @param {Render} render A render whose sprite is being generated. + * @param {String} key The key under which the sprite is stored. + * @param {Object} attributes Any additional information to pass to the + * sprite generation process. + * @return {Mixed} The output sprite; either a Uint8ClampedArray or SpriteMultiple. + */ + private generateSpriteCommandFilterFromRender( + render: Render, + key: string, + attributes: ISpriteAttributes): Uint8ClampedArray | SpriteMultiple { + var filter: IFilter = this.filters[render.source[2]], + found: Render | IRenderLibrary = this.followPath(this.library.sprites, render.source[1], 0), + filtered: Render | IRenderLibrary; + + if (!filter) { + console.warn("Invalid filter provided: " + render.source[2]); + } + + // If a Render was found, create a new one as a filtered copy + if (found.constructor === Render) { + filtered = new Render( + (found).source, + (found).reference, + { + "filter": filter + }); + + this.generateRenderSprite(filtered, key, attributes); + } else { + // Otherwise it's an IRenderLibrary; go through that recursively + filtered = this.generateRendersFromFilter(found, filter); + } + + // The (now temporary) container is given the filtered Render or directory + render.container[render.key] = filtered; + + if (filtered.constructor === Render) { + return (filtered).sprites[key]; + } else { + this.BaseFiler.clearCached(key); + return this.decode(key, attributes); + } + } + + /** + * Recursively generates a directory of Renders from a filter. This is + * similar to this.libraryParse, though the filter is added and references + * aren't. + * + * @param {Object} directory The current directory of Renders to create + * filtered versions of. + * @param {Array} filter The filter being applied. + * @return {Object} An output directory containing Renders with the filter. + */ + private generateRendersFromFilter(directory: IRenderLibrary, filter: IFilter): IRenderLibrary { + var output: IRenderLibrary = {}, + child: Render | IRenderLibrary, + i: string; + + for (i in directory) { + if (!directory.hasOwnProperty(i)) { + continue; + } + + child = directory[i]; + + if (child.constructor === Render) { + output[i] = new Render( + (child).source, + undefined, + { + "filter": filter + }); + } else { + output[i] = this.generateRendersFromFilter(child, filter); } } - sprite.processed = true; + return output; } @@ -950,13 +1116,13 @@ module PixelRendr { * @param {Object} attributes * @return {String} */ - private spriteApplyFilter(colors: string, key: string, attributes: any): string { - // If there isn't a filter (as is the normal), just return the sprite + private spriteApplyFilter(colors: string, key: string, attributes: ISpriteAttributes): string { + // If there isn't a filter (as is the norm), just return the sprite if (!attributes || !attributes.filter) { return colors; } - var filter: any = attributes.filter, + var filter: IFilter = attributes.filter, filterName: string = filter[0]; if (!filterName) { @@ -1033,19 +1199,19 @@ module PixelRendr { * height numbers. * @return {Uint8ClampedArray} */ - private spriteRepeatRows(sprite: Uint8ClampedArray, key: string, attributes: any): Uint8ClampedArray { + private spriteRepeatRows(sprite: Uint8ClampedArray, key: string, attributes: ISpriteAttributes): Uint8ClampedArray { var parsed: Uint8ClampedArray = new this.Uint8ClampedArray(sprite.length * this.scale), - rowsize: number = attributes[this.spriteWidth] * 4, - heightscale: number = attributes[this.spriteHeight] * this.scale, + rowsize: number = attributes[this.spriteWidth] * 4, + height: number = attributes[this.spriteHeight] / this.scale, readloc: number = 0, writeloc: number = 0, - si: number, - sj: number; + i: number, + j: number; // For each row: - for (si = 0; si < heightscale; ++si) { + for (i = 0; i < height; ++i) { // Add it to parsed x scale - for (sj = 0; sj < this.scale; ++sj) { + for (j = 0; j < this.scale; ++j) { this.memcpyU8(sprite, parsed, readloc, writeloc, rowsize); writeloc += rowsize; } @@ -1065,7 +1231,7 @@ module PixelRendr { * @param {Object} attributes * @return {Uint8ClampedArray} */ - private spriteFlipDimensions(sprite: Uint8ClampedArray, key: string, attributes: any): Uint8ClampedArray { + private spriteFlipDimensions(sprite: Uint8ClampedArray, key: string, attributes: ISpriteAttributes): Uint8ClampedArray { if (key.indexOf(this.flipHoriz) !== -1) { if (key.indexOf(this.flipVert) !== -1) { return this.flipSpriteArrayBoth(sprite); @@ -1087,9 +1253,9 @@ module PixelRendr { * @param {Object} attributes * @return {Uint8ClampedArray} */ - private flipSpriteArrayHoriz(sprite: Uint8ClampedArray, attributes: any): Uint8ClampedArray { - var length: number = sprite.length, - width: number = attributes[this.spriteWidth] + 0, + private flipSpriteArrayHoriz(sprite: Uint8ClampedArray, attributes: ISpriteAttributes): Uint8ClampedArray { + var length: number = sprite.length + 0, + width: number = attributes[this.spriteWidth] + 0, newsprite: Uint8ClampedArray = new this.Uint8ClampedArray(length), rowsize: number = width * 4, newloc: number, @@ -1124,9 +1290,9 @@ module PixelRendr { * @param {Object} attributes * @return {Uint8ClampedArray} */ - private flipSpriteArrayVert(sprite: Uint8ClampedArray, attributes: any): Uint8ClampedArray { + private flipSpriteArrayVert(sprite: Uint8ClampedArray, attributes: ISpriteAttributes): Uint8ClampedArray { var length: number = sprite.length, - width: number = attributes[this.spriteWidth] + 0, + width: number = attributes[this.spriteWidth] + 0, newsprite: Uint8ClampedArray = new this.Uint8ClampedArray(length), rowsize: number = width * 4, newloc: number = 0,