For a change, today’s article has nothing to do with performance. Instead, I’m going to tackle a simple task that I find myself doing all the time while working with Flash UIs: displaying icons. It sounds boring and, well, it really aught to be. However, I’m constantly asked by UI designers to populate some list item clips, frames, other other little holders with icons that are dynamically loaded by context. I figure that many others must be asked to do the same, so today’s article shows you a little function to help with that task.

At first glance there is no need for a function to put an icon into a frame since you can simply call frame.addChild(icon) and be done with it. The problem arises when the icon is not perfectly-sized to fit in the frame. Inevitably you will load many icons that are too small or too large for the frame. In this case it is desirable to scale them to fit the frame. Scaling down usually looks just fine and sometimes you even want to scale the icon up, so that too should be an option of this function. We also want to make sure that we preserve the aspect ratio of the icon so as not to warp it. This leads to the possibility of blank space which, just like standard definition TV displayed on a wide screen TV, we need to deal with by centering the image. In summary, we need these features:

  • Scale down
  • Scale up (optionally)
  • Preserve aspect ratio
  • Center image

Now that we’ve thought it through (with the help of our designer friends!), the implementation is not such a great feat:

/**
*   Put a display object in a container and, if necessary, scale it down
*   to fit while preserving its aspect ratio
*   @author Jackson Dunstan
*   @param obj Object to put in the rect
*   @param frame Frame to put the display object in
*   @param allowUpscale If scaling up is allowed (down is always allowed) 
*/
public static function putInFrame(obj:DisplayObject, frame:DisplayObjectContainer, allowUpscale:Boolean): void
{
	var widthRatio:Number = frame.width / obj.width;
	var heightRatio:Number = frame.height / obj.height;
	var scaleRatio:Number = widthRatio < heightRatio ? widthRatio : heightRatio;
 
	// Scaling up and upscaling isn't allowed, cap at 1
	if (scaleRatio > 1 && !allowUpscale) 
	{
		scaleRatio = 1;
	}
	obj.scaleX = scaleRatio;
	obj.scaleY = scaleRatio;
	obj.x = (frame.width - obj.width) / 2;
	obj.y = (frame.height - obj.height) / 2;
	frame.addChild(obj);
}

We simply compute which dimension– X or Y– would result in the least scaling, optionally cap at 1 to prevent upscaling, use the handy scaleX and scaleY properties to accomplish the scale, and center given the usual formula. Now for a little test app:

package
{
	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	import flash.ui.*;
	import flash.utils.*;
 
	/**
	*   An app to test putInFrame()
	*   @author Jackson Dunstan
	*/
	public class PutInFrame extends Sprite
	{
		/**
		*   Put a display object in a container and, if necessary, scale it down
		*   to fit while preserving its aspect ratio
		*   @author Jackson Dunstan
		*   @param obj Object to put in the rect
		*   @param frame Frame to put the display object in
		*   @param allowUpscale If scaling up is allowed (down is always allowed) 
		*/
		public static function putInFrame(obj:DisplayObject, frame:DisplayObjectContainer, allowUpscale:Boolean): void
		{
			var widthRatio:Number = frame.width / obj.width;
			var heightRatio:Number = frame.height / obj.height;
			var scaleRatio:Number = widthRatio < heightRatio ? widthRatio : heightRatio;
 
			// Scaling up and upscaling isn't allowed, cap at 1
			if (scaleRatio > 1 && !allowUpscale) 
			{
				scaleRatio = 1;
			}
			obj.scaleX = scaleRatio;
			obj.scaleY = scaleRatio;
			obj.x = (frame.width - obj.width) / 2;
			obj.y = (frame.height - obj.height) / 2;
			frame.addChild(obj);
		}
 
		private function test(frameWidth:Number, frameHeight:Number, circleRadius:Number, allowUpscale:Boolean, x:Number, y:Number, message:String): void
		{
			var label:TextField = new TextField();
			label.autoSize = TextFieldAutoSize.LEFT;
			label.text = message;
			label.setTextFormat(new TextFormat("_sans"));
			label.selectable = false;
 
			var frame:Sprite = new Sprite();
			frame.graphics.lineStyle(1, 0x000000, 0xff);
			frame.graphics.beginFill(0x000000, 0x00);
			frame.graphics.drawRect(0, 0, frameWidth, frameHeight);
			frame.graphics.endFill();
			frame.y = label.height;
 
			var circle:Sprite = new Sprite();
			circle.graphics.beginFill(0xffffff);
			circle.graphics.drawCircle(circleRadius, circleRadius, circleRadius);
			circle.graphics.endFill();
 
			putInFrame(circle, frame, allowUpscale);
 
			var container:Sprite = new Sprite();
			container.addChild(label);
			container.addChild(frame);
			container.x = x;
			container.y = y;
			addChild(container);
		}
 
		public function PutInFrame()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			test(100, 50, 200, false, 25, 25, "wide scale down");
			test(50, 100, 200, false, 25, 100, "tall scale down");
			test(100, 100, 200, false, 150, 25, "square scale down");
			test(100, 100, 20, true, 275, 25, "square scale up");
			test(100, 50, 20, true, 150, 150, "wide scale up");
			test(50, 100, 20, true, 275, 150, "tall scale up");
			test(100, 50, 20, false, 400, 25, "wide don't scale up");
			test(50, 100, 20, false, 400, 100, "tall don't scale up");
		}
	}
}

Here’s a screenshot of this in action. Everything seems to be in order:

Test app output

I hope you all find this tool good enough to make icon handling a boring task. Then you can move on to more fun tasks!