HaxeFlixel/flixel

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

Summary

Maintainability
Test Coverage
package flixel.graphics.frames;

import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxFrame.FlxFrameType;
import flixel.graphics.frames.FlxFramesCollection.FlxFrameCollectionType;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.util.FlxBitmapDataUtil;
import flixel.util.FlxDestroyUtil;
import openfl.display.BitmapData;

/**
 * Single-frame collection.
 * Could be useful for non-animated sprites.
 */
class FlxImageFrame extends FlxFramesCollection
{
    /**
     * Single frame of this frame collection.
     * Added this var for faster access, so you don't need to type something like: `imageFrame.frames[0]`
     */
    public var frame(get, never):FlxFrame;

    function new(parent:FlxGraphic, ?border:FlxPoint)
    {
        super(parent, FlxFrameCollectionType.IMAGE, border);
    }

    /**
     * Generates a `FlxImageFrame` object with empty frame of specified size.
     *
     * @param   graphic     Graphic for the `FlxImageFrame`.
     * @param   frameRect   The size of the empty frame to generate
     *                      (only `width` and `height` of the `frameRect` need to be set properly).
     * @return  Newly created `FlxImageFrame` object with empty frame of specified size.
     */
    public static function fromEmptyFrame(graphic:FlxGraphic, frameRect:FlxRect):FlxImageFrame
    {
        if (graphic == null || frameRect == null)
            return null;

        // find ImageFrame, if there is one already
        var imageFrame = FlxImageFrame.findEmptyFrame(graphic, frameRect);
        if (imageFrame != null)
            return imageFrame;

        // or create it, if there is no such object
        imageFrame = new FlxImageFrame(graphic);
        imageFrame.addEmptyFrame(frameRect);
        return imageFrame;
    }

    /**
     * Generates a `FlxImageFrame` object from the specified `FlxFrame`.
     *
     * @param   source   `FlxFrame` to generate `FlxImageFrame` from.
     * @return  Created `FlxImageFrame` object.
     */
    public static function fromFrame(source:FlxFrame):FlxImageFrame
    {
        var graphic:FlxGraphic = source.parent;
        var rect:FlxRect = source.frame;

        var imageFrame:FlxImageFrame = FlxImageFrame.findFrame(graphic, rect);
        if (imageFrame != null)
            return imageFrame;

        imageFrame = new FlxImageFrame(graphic);
        imageFrame.addSpriteSheetFrame(rect.copyTo(FlxRect.get()));
        return imageFrame;
    }

    /**
     * Creates a `FlxImageFrame` object for the whole image.
     *
     * @param   source   image graphic for the `FlxImageFrame`.
     * @return  Newly created `FlxImageFrame` object for specified graphic.
     */
    public static function fromImage(source:FlxGraphicAsset):FlxImageFrame
    {
        return fromRectangle(source, null);
    }

    /**
     * Creates `FlxImageFrame` for the specified region of `FlxGraphic`.
     *
     * @param   graphic   Graphic for `FlxImageFrame`.
     * @param   region    Region of image to create the `FlxImageFrame` for.
     * @return  Newly created `FlxImageFrame` object for the specified region of `FlxGraphic` object.
     */
    public static function fromGraphic(graphic:FlxGraphic, ?region:FlxRect):FlxImageFrame
    {
        // TODO: look into this
        if (graphic == null || graphic.isDestroyed)
            return null;

        // find ImageFrame, if there is one already
        final checkRegion = FlxRect.get(0, 0, graphic.width, graphic.height);
        if (region != null)
            region.copyTo(checkRegion);
        
        final imageFrame:FlxImageFrame = FlxImageFrame.findFrame(graphic, checkRegion);
        checkRegion.put();
        if (imageFrame != null)
        {
            if (region != null)
                region.putWeak();
            
            return imageFrame;
        }

        // or create it, if there is no such object
        final imageFrame = new FlxImageFrame(graphic);

        if (region == null)
        {
            region = FlxRect.weak(0, 0, graphic.width, graphic.height);
        }
        else
        {
            if (region.width == 0)
                region.width = graphic.width - region.x;

            if (region.height == 0)
                region.height = graphic.height - region.y;
        }

        imageFrame.addSpriteSheetFrame(region);
        return imageFrame;
    }

    /**
     * Creates a `FlxImageFrame` object for specified region of the image.
     *
     * @param   source   Image graphic for `FlxImageFrame`.
     * @param   region   Region of the image to create the `FlxImageFrame` for.
     * @return  Newly created `FlxImageFrame` object for specified region of image.
     */
    public static function fromRectangle(source:FlxGraphicAsset, ?region:FlxRect):FlxImageFrame
    {
        var graphic:FlxGraphic = FlxG.bitmap.add(source, false);
        return fromGraphic(graphic, region);
    }

    /**
     * Gets source BitmapData, generates new BitmapData (if there is no such BitmapData in the cache already)
     * and creates FlxImageFrame collection.
     *
     * @param   source   The source of graphic for frame collection.
     * @param   border   Border to add around tiles (helps to avoid "tearing" problem).
     * @param   region   Region of image to generate image frame from. Default value is `null`, which means that
     *                   whole image will be used for it.
     * @return  Newly created image frame collection.
     */
    public static function fromBitmapAddSpacesAndBorders(source:FlxGraphicAsset, border:FlxPoint, ?region:FlxRect):FlxImageFrame
    {
        var graphic:FlxGraphic = FlxG.bitmap.add(source, false);
        if (graphic == null)
            return null;

        var key:String = FlxG.bitmap.getKeyWithSpacesAndBorders(graphic.key, null, null, border, region);
        var result:FlxGraphic = FlxG.bitmap.get(key);
        if (result == null)
        {
            var bitmap:BitmapData = FlxBitmapDataUtil.addSpacesAndBorders(graphic.bitmap, null, null, border, region);
            result = FlxG.bitmap.add(bitmap, false, key);
        }

        var imageFrame:FlxImageFrame = FlxImageFrame.fromGraphic(graphic);
        return imageFrame.addBorder(border);
    }

    /**
     * Gets `FlxFrame` object, generates new `BitmapData` with border pixels around
     * (if there is no such BitmapData in the cache already) and creates image frame collection.
     *
     * @param   frame    Frame to generate tiles from.
     * @param   border   Border to add around frame image (helps to avoid "tearing" problem).
     * @return  Newly created image frame collection.
     */
    public static function fromFrameAddSpacesAndBorders(frame:FlxFrame, border:FlxPoint):FlxImageFrame
    {
        var bitmap:BitmapData = frame.paint();
        return FlxImageFrame.fromBitmapAddSpacesAndBorders(bitmap, border);
    }

    /**
     * Searches `FlxImageFrame` object for specified `FlxGraphic` object which have the same frame rectangle.
     *
     * @param    graphic     `FlxGraphic` object to search the `FlxImageFrame` for.
     * @param    frameRect   `FlxImageFrame` object should have frame with
     *                        the same position and dimensions as specified with this argument.
     * @return   `FlxImageFrame` object which corresponds to specified rectangle.
     *           Could be `null` if there is no such `FlxImageFrame`.
     */
    public static function findFrame(graphic:FlxGraphic, frameRect:FlxRect, ?frameBorder:FlxPoint):FlxImageFrame
    {
        if (frameBorder == null)
            frameBorder = FlxPoint.weak();
        
        var imageFrames:Array<FlxImageFrame> = cast graphic.getFramesCollections(FlxFrameCollectionType.IMAGE);
        for (imageFrame in imageFrames)
        {
            if (imageFrame.equals(frameRect, frameBorder) && imageFrame.frame.type != FlxFrameType.EMPTY)
                return imageFrame;
        }
        
        frameBorder.putWeak();
        frameRect.putWeak();
        return null;
    }

    /**
     * `FlxImageFrame` comparison method. For internal use.
     */
    inline function equals(rect:FlxRect, border:FlxPoint):Bool
    {
        return rect.equals(frame.frame) && border.equals(this.border);
    }

    /**
     * Searches `FlxImageFrame` object with the empty frame which have specified size.
     *
     * @param   graphic     `FlxGraphic` object to search `FlxImageFrame` for.
     * @param   frameRect   The size of empty frame to search for.
     * @return  `FlxImageFrame` with empty frame.
     */
    public static function findEmptyFrame(graphic:FlxGraphic, frameRect:FlxRect):FlxImageFrame
    {
        var imageFrames:Array<FlxImageFrame> = cast graphic.getFramesCollections(FlxFrameCollectionType.IMAGE);

        for (imageFrame in imageFrames)
        {
            var frame = imageFrame.frame;
            if (frame.sourceSize.x == frameRect.width && frame.sourceSize.y == frameRect.height && frame.type == FlxFrameType.EMPTY)
                return imageFrame;
        }

        return null;
    }

    override public function addBorder(border:FlxPoint):FlxImageFrame
    {
        var resultBorder:FlxPoint = FlxPoint.weak().addPoint(this.border).addPoint(border);

        var imageFrame:FlxImageFrame = FlxImageFrame.findFrame(parent, frame.frame, resultBorder);
        if (imageFrame != null)
            return imageFrame;

        imageFrame = new FlxImageFrame(parent, resultBorder);
        imageFrame.pushFrame(frame.setBorderTo(border));
        return imageFrame;
    }

    function get_frame():FlxFrame
    {
        return frames[0];
    }
}