Nardan’s Toolshed: TransformUtils Scaling

On February 8, 2010, in actionscript, by Alastair

As a coder I find that I have to do some tasks again and again, with slight differences each time, and every time I think I must create a library for this. So I started one, more a collection of useful code than a project with a single aim, which is reflected in the choice of name “Nardan’s Toolshed”. “Nardan” that’s a name I’ve used on the net for a very long time, and  “Toolshed” well it’s a place to tinker with ideas, to create & keep tools (also toolbox didn’t have enough whimsy!). The toolshed is organised into tools and parts, tools are applied to something to make it suitable, where as parts become part of the application rather than bashing something into the correct shape!

http://code.google.com/p/nardans-toolshed/

One of the tasks I find ubiquitous is scaling an image to fit into a particular portion of a design, but there is always a slight differences: should it scale and keep aspect ratio? should it fit into the box or fill the box? how should it be aligned in the box? And so the TransformationUtils Class was born, with lessons learned from experience.

Firstly one often needs to know if the object will fit into the rectangle anyway and if it does align it according to the design. To this end I wrote two basic functions fitsInRect & alignInRect, very basic and don’t need much explanation.

/**
 * Checks if clip would fit into a rectangle
 * @param	clip: left typeless so it can apply to DisplayObject or Rectangle.
 * @param	rect: Rectangle(to fit clip into)
 * @return
 */
public static function fitsInRect(clip:*, rect:Rectangle):Boolean
{
	return Boolean(clip.width

Hang on why is clip not a DisplayObject shouldn’t you correctly type your parameters? Well yes you should but I once found myself needing to use a Rectangle to do the calculations on because a Sprite with a mask not giving me the width and height I wanted, so I made that parameter type-less as Rectangle and DisplayObject don’t share a suitable interface. Also you’ll (hopefully) note that alignInRect has alignment parameters which are numbers (which should be between 0 & 1) I’ve provided some static constants to help with the most common positioning, but as it’s a number you don’t have to be limited to these, why you would want to have it horizontally aligned a third of the way along I don’t know but I’m not going to stop you! alignInRect is used by the other scaling functions so understanding it will help with the others.

public static const ALIGN_TOP:Number = 0;
public static const ALIGN_BOTTOM:Number = 1;
public static const ALIGN_LEFT:Number = 0;
public static const ALIGN_RIGHT:Number = 1;
public static const ALIGN_CENTRE:Number = 0.5;

Right onto the three scaling functions scaleIntoRect, scaleFillRect, fillRect. fillRect is the only scaling function that doesn’t maintain aspect ratio. The other two keep the aspect ratio, but differ in whether the clip get cropped (presuming a mask is applied), scaleIntoRect displays the entirety of the clip whereas scaleFillRect will only show a portion.

/*
 * Scales an Object to fit into a Rectangle whilst maintaining aspect-ratio
 * @param	clip: left typeless so it can apply to DisplayObject or Rectangle.
 * @param	rect: Rectangle(to fit clip into)
 * @param	vAlign: String(vertical alignment)
 * @param	hAlign: String(horizontal alignment)
 */
public static function scaleIntoRect(clip:*, rect:Rectangle, vAlign:Number = 0.5, hAlign:Number = 0.5):void {
	var clipRatio:Number = clip.width / clip.height;

	if (clipRatio > rect.width / rect.height) { // treat as landscape
		clip.width = rect.width;
		clip.height = rect.width / clipRatio;
	}else { // treat as portrait
		clip.height = rect.height;
		clip.width = rect.height * clipRatio;
	}
	alignInRect(clip, rect, vAlign, hAlign);
}

/* Scales an Object to fill a Rectangle whilst maintaining aspect-ratio, some parts of the clip map be outside of the rectangle
 * @param	clip: left typeless so it can apply to DisplayObject or Rectangle.
 * @param	rect: Rectangle(to fit clip into)
 * @param	vAlign: String(vertical alignment)
 * @param	hAlign: String(horizontal alignment)
 */
public static function scaleFillRect(clip:*, rect:Rectangle, vAlign:Number = 0.5, hAlign:Number = 0.5):void {
	var clipRatio:Number = clip.width / clip.height;
	if (clipRatio > rect.width / rect.height) { // treat as landscape
		clip.height = rect.height;
		clip.width = rect.height * clipRatio;
		alignInRect(clip, rect, ALIGN_TOP, hAlign);
	}else { // treat as portrait
		clip.width = rect.width;
		clip.height = rect.width / clipRatio;
		alignInRect(clip, rect, vAlign, ALIGN_LEFT);
	}
}

/**
 * Forces a clip to fill a rectangle
 * @param	clip: left typeless so it can apply to DisplayObject or Rectangle.
 * @param	rect: Rectangle(to fit clip into)
 */
public static function fillRect(clip:*, rect:Rectangle):void
{
	clip.width = rect.width;
	clip.height = rect.height
	alignInRect(clip, rect, ALIGN_TOP, ALIGN_LEFT);
}

If this is all a bit confusing try using this tool to play with it :)

Tagged with:
 

Comments are closed.