4

I am loading my sprite images using SVG images so that they smoothly scale to match device resolutions. Currently I am naively rendering the SVG data for each sprite, but I would like to reduce memory overhead and improve performance by sharing the rendered image across multiple sprite instances.

How can this be achieved using OpenFL / Haxe?

For example:

The tile implementation below is wasteful since SVG image is rendered for each tile upon creation.

// Each of the following tile sprites contain copies of the same image.
var xyz1:Tile = new Tile("xyz");
var xyz2:Tile = new Tile("xyz");
var xyz3:Tile = new Tile("xyz");

Tile Implementation

package;

import flash.display.Shape;
import format.SVG;
import openfl.Assets;

class Tile extends Shape {

    // Static cache of SVG data to avoid loading asset each time.
    private static var tileImageMap:Map<String, SVG> = new Map<String, SVG>();

    private static function lookupSVG(tile:String):SVG {
        var svg:SVG = tileImageMap.get(tile);
        if (svg == null) {
            svg = new SVG(Assets.getText("img/" + tile + ".svg"));
            tileImageMap.set(tile, svg);
        }
        return svg;
    }

    public var tile(get,set):String;

    private var _tile:String;

    private function get_tile():String {
        return _tile;
    }

    private function set_tile(value:String):String {
        if (value != _tile) {
            _tile = value;

            // Render tile SVG to tile sprite.
            // How can this be cached and reused by multiple tile instances?
            graphics.clear();
            lookupSVG(value).render(graphics, 0, 0, 56, 56);
        }
        return _tile;
    }

    public function new(tile:String) {
        super();

        cacheAsBitmap = true;

        this.tile = tile;
    }

}
Gama11
  • 31,714
  • 9
  • 78
  • 100
Lea Hayes
  • 62,536
  • 16
  • 62
  • 111

2 Answers2

4

With all regard to a good question and your approach, I personally find it too complex and unnecessary sophisticated.

If you never resize the tile, why not make a bitmapData that you could reuse unlimited number of times with great rendering performance? Just render the SVG once before and make a bitmapData:

var bd:BitmapData = new BitmapData( tileWidth, tileHeight );
bd.draw(tile);
// add the bd to some array of your tile set or assign it to a tile skin variable

It's later easy to reuse it with the graphics object (bitmapFill) or by making a Bitmap object. You can even animate in Bitmap object by changing the bitmapData property!

If you do plan to resize it, I would make a few size variations of the tile set and scale it. If you'll use this method please note that using allowSmooting will help rendering the bitmapData if it's resized or/and rotated, but will slow down the rendering as smooting counts as a filter.

Creative Magic
  • 3,143
  • 3
  • 28
  • 47
  • Does `bd.draw(tile);` copy the bitmap data into the image buffer of the tile sprite? Is there a way to have a singular image buffer which is shared by all instances of a sprite? So far it seems that each sprite ends up with its own internal image buffer which seems unnecessary for my needs. – Lea Hayes Aug 14 '13 at 02:11
  • Not sure if you can add the same object on the screen twice keeping both objects. You can escape the rendering process by rendering it once and passing the rendered data. I am not aware if the object then makes it's own copy of the passed render or just keeps a pointer to it. – Creative Magic Aug 14 '13 at 02:19
  • the following code in Haxe (OpenFL) worked perfectly: var s:Sprite = new Sprite(); s.graphics.beginFill(0xFF00F0); s.graphics.drawCircle(15, 15, 15); s.graphics.endFill(); var bd:BitmapData = new BitmapData(30, 30, true, 0x00000000); bd.draw(s); var s1:Sprite = new Sprite(); s1.graphics.beginBitmapFill(bd); s1.graphics.drawRect(0, 0, 30, 30); s1.graphics.endFill(); addChild(s1); var s2:Sprite = new Sprite(); s2.graphics.beginBitmapFill(bd); s2.graphics.drawRect(0, 0, 30, 30); s2.graphics.endFill(); s2.x = 200; addChild(s2); – Creative Magic Aug 14 '13 at 02:19
  • Interesting, it looks like the solution is to create the bitmap data like you demonstrate, and then to create one [`Bitmap`](http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Sprite.html) instance for each tile which references the very same `BitmapData` instance and thus saving memory. – Lea Hayes Aug 14 '13 at 02:32
  • Glad I could help. I did look into the render() method of SVG, but I found out that it does not return any value, thus I couldn't save the product of this process to use like I did with BitmapData. – Creative Magic Aug 14 '13 at 02:36
  • I have added the working solution as an answer below as reference for future readers. Thank you for your help!!! – Lea Hayes Aug 14 '13 at 02:50
2

This answer was made possible with thanks to @Creative Magic.

I have changed Tile from a Shape into a Bitmap which makes reference to a shared BitmapData instance. This is beneficial because it avoids duplicating the same image data multiple times (once for each sprite instance).

When a particular type of tile is instantiated for the very first time, its bitmap data is generated from SVG data which is then cached. The tile image can then be changed merely by adjusting its bitmapData reference to the artwork of another tile:

package ;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import format.SVG;
import openfl.Assets;

// Tile is now a Bitmap
class Tile extends Bitmap {

    // Static cache of bitmap data to avoid loading asset each time.
    private static var tileImageMap:Map<String, BitmapData> = new Map<String, BitmapData>();
    private static var tempSprite:Sprite = new Sprite();

    // Lookup cached version of tile bitmap.
    private static function lookupBitmapData(tile:String):BitmapData {
        var data:BitmapData = tileImageMap.get(tile);
        if (data == null) {
            // Render tile from SVG into temporary sprite.
            var svg:SVG = new SVG(Assets.getText("img/" + tile + ".svg"));
            tempSprite.graphics.clear();
            svg.render(tempSprite.graphics, 0, 0, 56, 56);

            // Extract bitmap data from temporary sprite and cache.
            data = new BitmapData(56, 56, true, 0x00FFFFFF);
            data.draw(tempSprite);
            tileImageMap.set(tile, data);
        }
        return data;
    }

    public var tile(get,set):String;

    private var _tile:String;

    private function get_tile():String {
        return _tile;
    }

    private function set_tile(value:String):String {
        if (value != _tile) {
            _tile = value;
            // Merely adjust reference of bitmap data.
            bitmapData = lookupBitmapData(value);
        }
        return _tile;
    }

    public function new(tile:String) {
        super();

        this.tile = tile;
    }

}
Community
  • 1
  • 1
Lea Hayes
  • 62,536
  • 16
  • 62
  • 111