Here it is the solution/workaround for the OpenFL problem of low performance when using a lot of SVG and for the low quality scaling of a bitmap of the SVG when trying to avoid the low performance. This might be a long post, sorry for that.
This was achieved using Starling for Haxe/OpenFL. This has is cons and pros, but basically this works well if we start using Starling from the start of the project, so if you are interested in using this alternative, I recommend to get used with it before starting to develop your app/game with Starling, so you have the dual stage in mind, as I explained in the previous post.
Here it is the code that works really well for representing a good number of SVG (or other image format) and animate scaling (or rotating, etc) in HTML5 target (and other targets too) without loss of image quality or performance, by converting them in to Starling textures that automatically create mipmaps for good quality downscaling (so you have to have a texture that has by its default size the max size it can be zoomed, so the quality remains excellent). Here is the Main Class:
package;
import openfl.display.BitmapData;
import openfl.display.Shape;
import openfl.display.Sprite;
import openfl.Assets;
import openfl.events.MouseEvent;
import format.SVG;
import starling.animation.Tween;
import starling.display.Image;
import starling.textures.Texture;
import starling.events.Event;
import starling.animation.Transitions;
import starling.core.Starling;
class Main extends Sprite {
public var board:Sprite = new Sprite(); // board
public var boardStarling:starling.display.Sprite = new starling.display.Sprite(); // Starling board
private var _starling:Starling;
public var starlingRoot:StarlingRoot;
public function new() {
super();
// Create Starling
_starling = new Starling(StarlingRoot, stage);
// The startup is asynchronous, so we could only work and add
// children display objects when the root is created
_starling.addEventListener(Event.ROOT_CREATED, startStarling);
// OpenFL invisible board Sprite to stay on top of the Starling
// board to enable the use of OpenFL wheel event
board.graphics.beginFill(0xFFFFFF, 0);
board.graphics.drawRect(0,0,700,400);
board.graphics.endFill();
// Event listener for the mouse wheel
board.addEventListener(MouseEvent.MOUSE_WHEEL, wheelEventHandler);
addChild(board);
}
private function startStarling(e:Event):Void {
_starling.removeEventListener(starling.events.Event.ROOT_CREATED, startStarling);
_starling.start();
starlingRoot = cast (_starling.root, StarlingRoot);
// After Starling is initiated, we could start the app
// and add children display objects
// NOTE: the children and the logic could have been done in
// StarlingRoot Class, but I needed the OpenFL wheel event...
initApp();
}
private function initApp():Void {
// Use the original SVG, and convert to bitmapData to feed the Starling texture
// NOTE: Optionally you could use a PNG of the vector with the max size of zoomed object like so:
// var bitmapData = Assets.getBitmapData ("img/openfl.png");
var svgShape:Shape = new Shape();
var itemSVG:SVG = new SVG(Assets.getText("img/openfl.svg"));
itemSVG.render (svgShape.graphics);
var bitmapData = new BitmapData(Std.int(svgShape.width), Std.int(svgShape.height), true, 0);
bitmapData.draw(svgShape);
var texture:Texture = Texture.fromBitmapData(bitmapData, true);
// Starling invisible board equivalente Sprite to the root layer
starlingRoot.addChild(boardStarling);
// add several images to test stress the tween
for (i in 1 ... 1000) {
var image = new Image(texture);
image.scaleX = image.scaleY = 0.25;
boardStarling.width = 700; boardStarling.height = 400;
boardStarling.addChild (image);
image.x = 650 * Math.random(); image.y = 350 * Math.random();
}
}
private function wheelEventHandler(event:MouseEvent):Void {
switch (event.type) {
case MouseEvent.MOUSE_WHEEL: {
if (event.delta > 0) { // wheel scroll up, scales up the starling board by 20%
var tween:Tween = new Tween(boardStarling, 1, Transitions.EASE_IN_OUT);
tween.animate("scaleX", boardStarling.scaleX * 1.2);
tween.animate("scaleY", boardStarling.scaleY * 1.2);
Starling.current.juggler.add(tween);
} else { // wheel scroll down, scales down the board by 20%
var tween:Tween = new Tween(boardStarling, 1, Transitions.EASE_IN_OUT);
tween.animate("scaleX", boardStarling.scaleX * 0.8);
tween.animate("scaleY", boardStarling.scaleY * 0.8);
Starling.current.juggler.add(tween);
}
}
}
}
}
And here is the Root Class to be instantiated in the Starling constructor:
package;
import starling.display.Sprite;
// Root Class Sprite layer to be instantiated by the Starling constructor.
// Starling Display Objects and their logic could be placed here.
class StarlingRoot extends Sprite {
public function new() {
super();
}
}
So a quick explanation of this code, in the Main Class constructor we create the Starling object instantiating in the constructor, the StarlingRoot Class as the root for the Starling stage. With Starling is always necessary to create a Starling Sprite Class, this is the way that Starling works, but we could use this class to be the starting point of our app/game and have all our display objects and their logic from here. I didn’t follow this logic because I needed to have a OpenFL Mouse Wheel Event listener, as you will see down the road.
As the Starling startup and root creation is asynchronous, we have to listen to the ROOT_CREATED event before we could work and add objects to Starling stage.
I use a OpenFL invisible Sprite (board) on top of the Starling stage and display objects, as a workaround for not having a Wheel Mouse Event in Starling, as I mentioned in the previous post. This way I can add an OpenFL MOUSE_WHEEL event and not have the pain of having to create my own Starling wheel event just for this example.
When the root for Starling is created the startStarling function is called and in here is when we put the Starling “engine” to a start and really initiate our App/Game.
I then use the SVG of the first example to render to a BitmapData to feed to a Starling Texture and so creating a Starling Image from that texture, ensuring that the original SVG quality is maintained throughout this process. In this example I put 1000 images randomly in the Starling stage layer with no degradation whatsoever, but probably could put many more before starting to see some performance degradation, making this a viable and excellent solution for this kind of graphical needs.
Finally, when we scroll up or down a Starling Tween is created to mimic the Actuate zoom in/out of the first example. I could probably use actuate again, but I believe it makes more sense to use the Starling alternative. One side note, the Starling Tween is a little different from actuate in the way the animation starts the execution, the actuate executes when it is created, the Starling Tween only starts when we manually call advanceTime
or throw the tween into Starling juggler and it takes care of continuously calling advanceTime
for us.
I think that this wraps it up, but before I finish this post I have to thank @Egis and @Matse users for their prompt help and attention, because without their help I couldn’t reach to this solution, and by the way, @Matse and others reading this post, there’s a free open source texture atlas packer named Free Texture Packer, that has Starling format export and other nice features that I didn’t have the chance to try it yet, but for what I’ve read, it seems a nice open source alternative.
ps1: If I said something wrong about Starling, please correct me, by all means.
ps2: @singmajesty, I know you are a busy, busy man, but when you have a chance, please add mipmap downscalling feature to OpenFL by parameter, it will help a bunch for us guys that are in the graphical apps creation! Thanks for giving us OpenFL!
ps3: @joshtynjala, I didn’t have the time to take a look at Feathers UI for Haxe, but can I ask you, does this version uses Starling for Haxe?