Actuate scaling animation jerky with SVG's

I’ve tested Starling and it worked great in scaled images, using automatic mipmapping. But I have to say, this is not exactly easy to setup and adapt to an more than half baked app.

Starling is great and for what I’ve tested so far is what I need for my app, in terms of performance and image quality wise when scaling images. I’ve already have most of my app develop using display objects of OpenFL, but the way that Starling works is to have an extra stage, just for Starling display objects. No major issue there, only a little more work in switching display objects, keeping the UI in the OpenFL side, because the Starling stage stays behind OpenFL stage. Two stages aren’t the best thing to manage, but in this case, helps to separate the workspace from the UI.

My main grip started when I have to switch the event listeners from the OpenFL to Starling. First of, there’s no MouseEvent in Starling, the “equivalent” is TouchEvent that handles both touch and mouse event. My first impression was “great! touch and mouse in one go!”. But the mouse part is not to easy to deal with. If we want left clicks, fine, easy to do, right click, we have to implement ourself, and for mouse wheel, even more cumbersome, to the point that the advised solution, that I’ve found, is to fork the Starling git, create a new branch, implement this events, and then keep merging the main with our branch. So for my case, with so many work made in OpenFL, it wont be so easy to change to Starling, because of what I’ve talked before and because some others quirks that I even didn’t tackled. (please someone correct me if I’m wrong on this, but it was what I found in the matter).
Well, it’s doable, but a warning for someone that is reading this and wants to use Starling, I recommend using it from the start and save same headaches.

I don’t know if it is planned to implement mipmaps in OpenFL, but I think that it would be a major development for some type of graphical apps, games and even for responsive UI, to have better performance and graphical quality, all without having to use other framework and the hassle of shifting between stages and having to reimplement stuff that is consolidated in the OpenFL framework.

Later on, I’m going to do other post to present the solution to my problem using Starling with a bit of code explanation.

1 Like

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?

3 Likes

Starling is not a dependency of the Haxe version of Feathers UI. It is pure OpenFL.

Thanks for your answer, and this is good news, that means that I can use the Starling stage for the graphical part of the workspace, and Feathers in the OpenFL stage for the UI part, keeping the two separated and the UI will always stay on top of the workspace without needing to add layers to control that part.