CreateJS / EaselJS

The Easel Javascript library provides a full, hierarchical display list, a core interaction model, and helper classes to make working with the HTML5 Canvas element much easier.
http://createjs.com/
MIT License
8.13k stars 1.97k forks source link

Filters on SpriteSheets use WAY to much system resources #1056

Open WindowsTV opened 3 years ago

WindowsTV commented 3 years ago

This is a question and a bug. I have tried Stack Overflow and I was just being ignored so I am here now, hope that's okay. I am working on a game that uses a lot of Sprite Sheets on screen, one of them requires a filter during game play.

I tried:

mySprite.filters = [new createjs.BlurFilter(10,10,1)];
mySprite.cache(0,0,640,480);

mySprite.on("tick", this.updateCostumeColor, this);
function updateCostumeColor (evt) {          
    evt.currentTarget.updateCache();
};

As suggested but all it does is boggle down my game's performance due to the amount of rendering on the screen. I have provided a work around that someone has made, I was able to get it to partially work. I load in my SpriteSheets through Preload.js and can't automate their system to use the details from my spritesheet.json.

I am asking for an internal way to add filters on to the sprite's image set similar to how the workaround acts.

Thank.

Issue Details

danzen commented 3 years ago

Could you create a blurred version of the sprite in an image processing app like photoshop? Then swap out the sprite for the blurred sprite?

WindowsTV commented 3 years ago

Could you create a blurred version of the sprite in an image processing app like photoshop? Then swap out the sprite for the blurred sprite? I

I would but this particular filter was just for testing and besides all the filters added to the spites need to be dynamic as the player will change the values. The actual filter I’m going to use is a color filter, the player gets a “paint job” and adjusts their color on fly for over a hundred player sprites.

MannyC commented 3 years ago

I did something with this before. Basically I created filtered versions of the whole spritesheet once and then used the cached version.

I doubt my implementation works anymore, but you may be able to do something with the code: https://github.com/MannyC/FilteredSprite.js

WindowsTV commented 3 years ago

I did something with this before. Basically I created filtered versions of the whole spritesheet once and then used the cached version.

I doubt my implementation works anymore, but you may be able to do something with the code: https://github.com/MannyC/FilteredSprite.js

Many thanks for this, a few minor adjustments were needed to be made but it works like a charm!

danzen commented 3 years ago

@WindowsTV - glad you worked it out. If you would like, you could let us know the changes and perhaps we can see about getting this implemented. Thanks, too, @lannymcnie ;-)

WindowsTV commented 3 years ago

Sure can, @danzen! Provided below is the modified code to work with Sprites and SpriteSheets:

/*
 * FilteredSprite
 * Visit https://github.com/MannyC/FilteredSprite.js for documentation, updates and examples.
 *
 * Created by MannyC
 *
 * Modified by WindowsTV
 */

(function () {
    //"use strict";

    var p = createjs.SpriteSheet.prototype;

    /**
     * Object mapping filterName->Array of filtered images
     * @property _filteredImages
     * @type Object
     * @protected
     */
    //p._filteredImages = {};

    /**
     * Object mapping filterName->Array of filtered frames
     * @property _filteredFrames
     * @type Object
     * @protected
     */
    //p._filteredFrames = {};

    /**
     * Creates a filter with the given name.  This method creates a set of filtered frames
     * that can later be retrieved with the {getFilteredFrame} method by specifying the passed name
     * Make sure the spritesheet and images are fully loaded before calling this method, and don't change
     * the frame data afterwards.
     * @param {String} name The name to use for the new filter
     * @param {Array} filters The array of filters to apply
     * @return {Boolean} True if successful, otherwise false
     * @method createFilter
     */
    p.createFilter = function(name, filters) {

        this._filteredImages = this._filteredImages || {};
        this._filteredFrames = this._filteredFrames || {};

        if( this._filteredImages[name] || !this.complete) return false;

        this._filteredImages[name] = [];
        var images = this._images;

        // Create the set of filtered images
        for(var x = 0; x<images.length; x++){
            var img = images[x];
            var fi = new createjs.Bitmap(img);
            fi.filters = filters;
            fi.cache(0, 0, img.width, img.height);
            this._filteredImages[name][x] = fi.cacheCanvas;
        }

        // Create a filtered frame for all of the frames
        this._filteredFrames[name] = {};
        for(var x = 0; x<this._frames.length; x++){
            var frame = this._frames[x];

            if(!frame._imageIndex){
                // first time filtering this frame so search for image index
                for(var y = 0; y<images.length; y++){                   
                    if(frame.image === images[y]){
                        frame._imageIndex = y; // store the index of the image for subsequent use
                    }                       
                }
            }

            // Create the new frame with the filtered image
            this._filteredFrames[name][x] = {
                _imageIndex: frame._imageIndex,
                image: this._filteredImages[name][frame._imageIndex],
                rect: frame.rect,
                regX: frame.regX,
                regY: frame.regY
            }
        }       
        return true;
    };

    /**
     * Deletes a filter with the given name.  Note that this method doesn't check to see if there
     * are still BitmapAnimations that refer to this filter.
     * @param {String} name The name of the filter to delete
     * @return {Boolean} True if successful, otherwise false
     * @method deleteFilter
     */
    p.deleteFilter = function(name){
        if(!this._filteredImages || !this._filteredImages[name]) return false;

        delete this._filteredImages[name];
        delete this._filteredFrames[name];

        return true;
    };
    /**
     * Retrieves a filtered frame.  See getFrame.    
     * @param filterName the name of the filter to get the frame of
     * @method getFilteredFrame
     */
    p.getFilterFrame = function(frameIndex, filterName) {
        var frame;
        if (this.complete && this._filteredFrames && this._frames && (frame=this._filteredFrames[filterName][frameIndex])) { return frame; }
        else if ( this.getFrame ) return this.getFrame(frameIndex); 
        return null;
    };

    /**
     * Sprite Overrides
     */
    var q = createjs.Sprite.prototype;

    /**
     * The name of the currently applied filter
     * @property _appliedFilter
     * @type String
     * @protected
     */
    //p._appliedFilter = null;

    /**
     * Applies the named filter to this BitmapAnimation.  The filter must exist in the 
     * associated SpriteSheet or the method will fail.
     * @param {String} name The name of the filter to apply
     * @return {Boolean} True if successful, otherwise false
     * @method applyFilter
     */
    q.applyFilter = function(name){
        if(!this.spriteSheet._filteredImages || !this.spriteSheet._filteredImages[name]) return false;
        this._appliedFilter = name;

        return true;
    };

    /**
     * Removes the currently applied filter.     
     * @return {Boolean} True if successful, otherwise false
     * @method removeFilter
     */
    q.removeFilter = function(){
        if (!this._appliedFilter) return false;     

        this._appliedFilter = null;

        return true;
    };

    /**
     * Overrides the original draw method in order to call getFilteredFrame where applicable.  See documention 
     * for original method.  
     */

    q.draw = function(ctx, ignoreCache) {
        if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; }
        this._normalizeFrame();

        var o;
        if (this._appliedFilter) o = this.spriteSheet.getFilterFrame(this._currentFrame|0, this._appliedFilter);
        else o = this.spriteSheet.getFrame(this._currentFrame|0);

        if (!o) { return false; }
        var rect = o.rect;
        if (rect.width && rect.height) { ctx.drawImage(o.image, rect.x, rect.y, rect.width, rect.height, -o.regX, -o.regY, rect.width, rect.height); }
        return true;
    };  

}());

The way to activate the filters is the same in MannyC's version. I have to say props to them for making a great system because it was easily upgradable to the current Sprite API!

danzen commented 3 years ago

Thanks! @WindowsTV - looking forward to going through it. Will let you know what happens! Cheers.