HaxeFlixel/flixel

View on GitHub
flixel/graphics/frames/FlxFilterFrames.hx

Summary

Maintainability
Test Coverage
package flixel.graphics.frames;

import openfl.display.BitmapData;
import openfl.geom.Point;
import openfl.geom.Rectangle;
import flixel.FlxSprite;
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxFramesCollection.FlxFrameCollectionType;
import flixel.util.FlxColor;
import openfl.filters.BitmapFilter;

/**
 * Frames collection which you can apply bitmap filters to.
 * WARNING: this frame collection doesn't use caching, so be careful or you will "leak" out memory very fast.
 * You should destroy frames collections of this type manually.
 */
class FlxFilterFrames extends FlxFramesCollection
{
    static var point:Point = new Point();
    static var rect:Rectangle = new Rectangle();

    /**
     * Generates new frames collection from specified frames.
     *
     * @param   frames      Frames collection to generate filters for.
     * @param   widthInc    How much frames should expand horizontally.
     * @param   heightInc   How much frames should expend vertically.
     * @param   filters     Optional filters array to apply.
     * @return  New frames collection which you can apply filters to.
     */
    public static inline function fromFrames(frames:FlxFramesCollection, widthInc:Int = 0, heightInc:Int = 0, ?filters:Array<BitmapFilter>):FlxFilterFrames
    {
        return new FlxFilterFrames(frames, widthInc, heightInc, filters);
    }

    /**
     * Original frames collection
     */
    public var sourceFrames(default, null):FlxFramesCollection;

    /**
     * How much frames should expand horizontally
     */
    public var widthInc(default, null):Int = 0;

    /**
     * How much frames should expand vertically
     */
    public var heightInc(default, null):Int = 0;

    /**
     * Filters applied to these frames
     */
    public var filters(default, set):Array<BitmapFilter>;

    function new(sourceFrames:FlxFramesCollection, widthInc:Int = 0, heightInc:Int = 0, ?filters:Array<BitmapFilter>)
    {
        super(null, FlxFrameCollectionType.FILTER);

        this.sourceFrames = sourceFrames;

        widthInc = (widthInc >= 0) ? widthInc : 0;
        heightInc = (heightInc >= 0) ? heightInc : 0;

        widthInc = 2 * Math.ceil(0.5 * widthInc);
        heightInc = 2 * Math.ceil(0.5 * heightInc);

        this.widthInc = widthInc;
        this.heightInc = heightInc;

        this.filters = (filters == null) ? [] : filters;

        genFrames();
        applyFilters();
    }

    /**
     * Just helper method which "centers" sprite offsets
     *
     * @param   spr              Sprite to apply this frame collection.
     * @param   saveAnimations   Whether to save sprite's animations or not.
     * @param   updateFrames     Whether to regenerate frame `BitmapData`s or not.
     */
    public function applyToSprite(spr:FlxSprite, saveAnimations:Bool = false, updateFrames:Bool = false):Void
    {
        if (updateFrames)
            set_filters(filters);

        var w:Float = spr.width;
        var h:Float = spr.height;
        spr.setFrames(this, saveAnimations);
        spr.offset.add(0.5 * widthInc, 0.5 * heightInc);
        spr.setSize(w, h);
    }

    function genFrames():Void
    {
        var canvas:BitmapData;
        var graph:FlxGraphic;
        var filterFrame:FlxFrame;

        for (frame in sourceFrames.frames)
        {
            canvas = new BitmapData(Std.int(frame.sourceSize.x + widthInc), Std.int(frame.sourceSize.y + heightInc), true, FlxColor.TRANSPARENT);
            graph = FlxGraphic.fromBitmapData(canvas, false, null, false);

            filterFrame = graph.imageFrame.frame;

            frames.push(filterFrame);
            if (frame.name != null)
            {
                filterFrame.name = frame.name;
                framesByName.set(frame.name, filterFrame);
            }
        }

        regenBitmaps(false);
    }

    /**
     * Adds a filter to this frames collection.
     *
     * @param   filter   The filter to be added.
     */
    public inline function addFilter(filter:BitmapFilter):Void
    {
        if (filter != null)
        {
            filters.push(filter);
            applyFilter(filter);
        }
    }

    /**
     * Removes a filter from this frames collection.
     *
     * @param   filter   The filter to be removed.
     */
    public function removeFilter(filter:BitmapFilter):Void
    {
        if (filters.length == 0 || filter == null)
            return;

        if (filters.remove(filter))
            regenAndApplyFilters();
    }

    /**
     * Removes all filters from the frames.
     */
    public function clearFilters():Void
    {
        if (filters.length == 0)
            return;

        filters.splice(0, filters.length);
        regenBitmaps();
    }

    function regenAndApplyFilters():Void
    {
        regenBitmaps();
        applyFilters();
    }

    function regenBitmaps(fill:Bool = true):Void
    {
        var numFrames:Int = frames.length;
        var frame:FlxFrame;
        var sourceFrame:FlxFrame;
        var frameOffset:Point = point;

        for (i in 0...numFrames)
        {
            sourceFrame = sourceFrames.frames[i];
            frame = frames[i];

            if (fill)
                frame.parent.bitmap.fillRect(frame.parent.bitmap.rect, FlxColor.TRANSPARENT);

            frameOffset.setTo(widthInc * 0.5, heightInc * 0.5);

            sourceFrame.paint(frame.parent.bitmap, frameOffset, true);
        }
    }

    function applyFilter(filter:BitmapFilter)
    {
        var bitmap:BitmapData;

        for (frame in frames)
        {
            point.setTo(0, 0);
            rect.setTo(0, 0, frame.sourceSize.x, frame.sourceSize.y);
            bitmap = frame.parent.bitmap;
            bitmap.applyFilter(bitmap, rect, point, filter);
        }
    }

    function applyFilters():Void
    {
        for (filter in filters)
            applyFilter(filter);
    }

    override public function destroy():Void
    {
        sourceFrames = null;
        filters = null;

        for (frame in frames)
            frame.parent.destroy();

        super.destroy();
    }

    function set_filters(value:Array<BitmapFilter>):Array<BitmapFilter>
    {
        filters = value;

        if (value != null)
            regenAndApplyFilters();

        return filters;
    }
}