HaxeFlixel / flixel

Free, cross-platform 2D game engine powered by Haxe and OpenFL
https://haxeflixel.com/
MIT License
1.96k stars 431 forks source link

Invalid Bitmap Error when removing glow Filter from a FlxSprite #2050

Open sano98 opened 7 years ago

sano98 commented 7 years ago

Code snippet reproducing the issue:

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.graphics.FlxGraphic;
import flixel.system.FlxAssets.GraphicLogo;
import flixel.graphics.frames.FlxFilterFrames;
import flash.filters.BitmapFilter;
import flash.filters.GlowFilter;

/**
 * Test project for button glow filter.
 * Press "G" on the keyboard to toggle the glow over the flixel icon.
 * Removing the glow without the presence of the buffer sprite causes an Invalid Bitmap Error.
 * Written by sano98.
 * 
 */

class PlayState extends FlxState
{
    /**
     * The test sprite. Press "G" to give it a glow, and again to remove it.
     */
    public var testIcon:FlxSprite;

    /**
     * The "buffer sprite". It's presence keeps the bug from happening.
     */
    public var testIcon2:FlxSprite;

    /**
     * Glow Filter for the flixel icon
     */
    public var glowFilter:GlowFilter;
    public var filterFrame:FlxFilterFrames;

    override public function create():Void
    {
        FlxG.worldBounds.set( 0, 0, 640, 480); 
        FlxG.camera.setScrollBoundsRect(0, 0, 640, 480, false);

        super.create();

        this.bgColor = 0xFF77AAFF;

        this.testIcon = new FlxSprite(300, 300, FlxGraphic.fromClass(GraphicLogo));
        this.add(this.testIcon);

        //Comment this line out to reproduce the bug.
        this.testIcon2 = new FlxSprite(300, 300, FlxGraphic.fromClass(GraphicLogo));

        this.glowFilter = new GlowFilter(0xFFFFFFFF, 0.8, 5, 5, 3);
        this.filterFrame = this.createFilterFrames(this.testIcon);

    }

    private function createFilterFrames(sprite:FlxSprite):FlxFilterFrames
    { 
        var filterFrames = FlxFilterFrames.fromFrames(sprite.frames, 5, 5, []); 
        return filterFrames; 
    } 

    function updateFilter(spr:FlxSprite, sprFilter:FlxFilterFrames)
    {
        sprFilter.applyToSprite(spr, false, true);
    }

    override public function update(elapsed:Float):Void
    {
        super.update(elapsed);

        if (FlxG.keys.justPressed.G)
        {
            if (this.filterFrame.filters.length > 0)
            {
                this.filterFrame.clearFilters();
                this.updateFilter(testIcon, this.filterFrame);
            }
            else
            {
                this.filterFrame.addFilter(glowFilter);
                this.updateFilter(testIcon, this.filterFrame);
            }
        }
    }
}

Observed behavior: When the testIcon2 is not initialised, the code crashs with an invalid bitmap error when pressing the "G"-Button on the keyboard for the second time.

Expected behavior: Pressing G should simply toggle the glow.

Gama11 commented 7 years ago

This seems to be a caching issue. As soon as the filter is applied, the original asset is removed from the cache, causing the subsequent crash when the filter is removed again. testIcon2 keeps the graphic in the cache, even if it is otherwise unused.

@Beeblerox I guess the proper fix would be for filters not to remove anything from the cache?

JoeCreates commented 7 years ago

I had a related issue and I have a workaround, but I don't really understand this well enough to know what a correct solution would be.

My related issue is that applyToSprite sets the frames of the sprite to the frames of the filter. FlxSprite#set_frames sets the graphic of the sprite to the new frame's parent, which in the case of a FlxFilterFrames is null. This means both the graphic and pixels of the FlxSprite become null.

I'm wondering if the graphic should persist on the FlxSprite, which can be achieved by setting the parent of the FlxFilterFrames to the sprite's graphic as I have done in #2069.

I've no idea if that is sensible though. It solves this issue because the graphic's uses does not go below 0 as it is maintained on the sprite.

Beeblerox commented 7 years ago

as a temp solution i could advice to set graphic's destroyOnNoUse to false:

this.testIcon = new FlxSprite(300, 300, FlxGraphic.fromClass(GraphicLogo));
this.testIcon.graphic.destroyOnNoUse = false;
this.add(this.testIcon);

This should prevent removal and destroy of FlxGraphic object. And later i'll remove this behavior from the engine (will remove destroyOnNoUse property), and let users decide when they want to destroy graphic. It was bad idea to automatically clean these graphic objects and creates such issues.

Gama11 commented 7 years ago

@Beeblerox So should we close or merge #2069?

Beeblerox commented 7 years ago

@Gama11 i think it could be closed

JoeCreates commented 7 years ago

This doesn't solve the related issue I had, though. If you apply a filter to a sprite, that sprite's graphic and pixels become null. If you are procedurally generating the graphics then this is not desired, as the you wish to keep hold of that bitmapData for rendering.

It seemed to me that applying a filter to a sprite shouldn't remove the sprites graphic?

On 20 May 2017 21:11, "Zaphod" notifications@github.com wrote:

@Gama11 https://github.com/gama11 i think it could be closed

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/HaxeFlixel/flixel/issues/2050#issuecomment-302895927, or mute the thread https://github.com/notifications/unsubscribe-auth/ACbEvEZvJ-2qqBRzHpIf_qes-tP2sj2Zks5r70j7gaJpZM4Mx4aG .

Beeblerox commented 7 years ago

@JoeCreates ok, i think that i hadn't understood your problem correctly. Sorry about that. i need to look into it in more details.