Scale 9 in Silverlight

Silverlight Scale 9Samples at bottom.

In creating video players at work in Silverlight, we started getting designs that needed Scale 9. Silverlight, Blend, and/or Design don’t seem to have a Scale 9 option that I can find, so I built one in JavaScript for Silverlight 1.0. Porting to C# for all you Silverlight 1.1 peeps out there should be trivial since it’s not a lot of code.

What is Scale 9?

Scale 9 is the ability to have an image scale in a nice, visually appealing way by breaking up the image into 9 sections. It is used primarily with containers such as the windows you use on your OS. For example, if you scale the browser or RSS reader you are reading this in right now, you’ll notice that the top task bar can shrink it’s width, yet still look consistent and nice. If you shrink it vertically, you’ll notice that the sides shrink, yet still keep aligned, and looking nice as well.

Scale 9 was in my experience done a lot in Flash because as Flash got used more in the application development field, people started creating controls. Those controls were re-usable visual components, and a lot could redraw themselves via code. This code would allow visual layout changes very easily. So, a Button could be as big or as small as you wanted it to be just by calling the Button’s setSize function, and passing in a width and height. The Button would “handle” sizing itself to that size.

However, a lot of anything in Flash before ActionScript 3 had a lot of overhead in CPU and RAM. So, by implementing Scale 9 natively in the compilers & Flash Player, you reduced the image count from 9 to 1. For 30 buttons, that’s 270 visual objects being moved and sized to 30; 1 to 1. You can see how this is an awesome feature, especially when the runtime does the scaling for you taking the code out of the equation as well.

RectanglesObviously, for uniform rectangles, you don’t need scale 9. For rounded rectangles, or for anything that has non-uniform edges, if you start scaling it, it’ll stretch and look nasty.

Here’s a good article by Aral that shows how scale 9 works in Flex (scroll mid-way).

How does Scale 9 work?

Image GridScale 9 works by breaking the images into 9 sections. The 4 corners do not scale their width and height, but do reposition themselves to their respective corners. The top left always stays in the top left. The top right always stays in the top right. And so on.

The top and bottom can scale their width larger or smaller, but not their height. The left and right side can scale their height, but not their width. The center scales normally, and positions himself in the middle of everyone.

Combining this with custom sliced images, you have 9 pieces that form your image, and can scale to any size, though usually have a minimum width and height.

How does my implementation differ from Flash & Flex?

Flash 8 and Flash CS3 do vector only. Additionally, they only require 1 image whereas in this example, you need 9 bitmaps sliced up. Flex can do vector and bitmap, but has the same advantages of Flash; 1 image. Both use the compiler to define the drawing matrix where mine just re-positions things on the fly based on the passed in width and height. Mine’s written in JavaScript which isn’t compiled like C# or ActionScript 3 so runs slower.

The performance for me, however, is beyond acceptable; for a video player in a browser, it performs very well.

How do you make an interface capable of being scaled via scale 9?

A few steps.

  1. Get a designer to create an appropriate comp.
  2. Slice the image up into 9 pieces.
  3. Implement those 9 images into Blend.
  4. Put code to re-position & resize them in your JavaScript.
  5. Tweak.

Explain to the designer that the border & background, typically a video player, will be scaled to a variety of width’s and height. If you say, “Emulate how Windows XP with the silver theme has those nice, scaleable windows that look good at most any size”, that helps. Things to watch out for are drop-shadows, gradients that go the same direction you are scaling, and lots of textures on the non-corners. Those 3 make it really hard for the design to scale. If the designer can scale it in their image editing program as a bitmap (not vector) and it still looks good, that’s a good acid test for the design. “How would it look with a 4×3 video in it… say, 320×240? How about an HD 16×9 one that’s 420×270?”

Fireworks RedOnce you get the image, slice the mofo up into pieces. I use Firefox because it’s the shiz for doing quick production work. You could also use Design or Photoshop, for example… I’m just fast at “doing the work the designer doesn’t want to do” in Fireworks. You typically align 4 guides to make your 9 sections. You then make sure the tops can scale width wise, and the sides veritcally. Then you export the slices as PNG’s. You do PNG because it’s lossless comperssion (meaning no image quality loss from compression), and it supports transparency. Then…

Blend Red…import those bad boys into Blend. Make sure the width and height of the XAML Image tag matches your exported PNG’s so you don’t get any weird scaling. If you see transform matrix tags nested in your image tags for the background pieces, delete that mess; you are using code, not Blend, to position and size your background pieces.

Use the example scale 9 code below in your JavaScript to scale 9 your interface. Keep in mind CSS has a major impact on this actually working in browsers other than IE. Here’s some example code (at the bottom, 2nd to last thread) on how to get this to work in both browsers.

Tweak. You may not get it right the first time. Additionally, I swear Silverlight is either scaling my images a tad, or I’m just using Canvas.Left and Canvas.Top wrong. You’ll notice magic numbers in the redraw function; these change EVERY design. For some reason (maybe half-pixel values I’m not seeing) some designs either bleed into another by a pixel, or miss one. The “1”‘s in the redraw function are there to make it, like Goldielocks, just right. Test in both IE, Firefox, and Safari if you gotta Mac or are brave enough to install Safari on your PC.

Scale 9 JavaScript Code

Scale9 = function(tl, tc, tr, cl, c, cr, bl, bc, br){

	this.tl = tl;

	this.tc = tc;

	this.tr = tr;

	this.cl = cl;

	this.c  = c;

	this.cr = cr;

	this.bl = bl;

	this.bc = bc;

	this.br = br;

};
Scale9.prototype.redraw = function(sender, width, height, offsetX, offsetY){

	if(offsetX == null) offsetX = 0;

	if(offsetY == null) offsetY = 0;

	this.move(this.tl, offsetX, offsetY);

	this.move(this.tc, this.getX(this.tl) + this.tl.width, this.getY(this.tl));

	this.move(this.tr, width - this.tr.width, this.getY(this.tc));

	this.tc.width = width - this.getX(this.tc) - this.tr.width;

	this.move(this.cl, this.getX(this.tl), this.getY(this.tl) + this.tl.height);

	this.move(this.c, this.getX(this.cl) + this.cl.width, this.getY(this.cl));

	this.move(this.cr, width - this.cr.width, this.getY(this.cl));

	this.c.width = width - this.getX(this.c) - this.cr.width;

	this.move(this.bl, this.getX(this.cl), height - this.bl.height - 1);

	this.cl.height = height - this.getY(this.cl) - this.bl.height - 1;

	this.c.height = this.cl.height;

	this.cr.height = this.cl.height;

	this.move(this.br, width - this.br.width, height - this.br.height - 1);

	this.move(this.bc, this.getX(this.bl) + this.bl.width, this.getY(this.bl));

	this.bc.width = width - this.getX(this.bc) - this.br.width;

};
Scale9.prototype.move = function(obj, x, y){

	obj["Canvas.Left"] = x;

	obj["Canvas.Top"] = y;

};

Scale9.prototype.getX = function(obj)

{

	return obj["Canvas.Left"];

};

Scale9.prototype.getY = function(obj)

{

	return obj["Canvas.Top"];

};

Example Usage

function onLoaded(sender, args){

	var plugin = sender.getHost();

	plugin.content.onFullScreenChange = onFullScreenChanged;

	plugin.content.onResize = onResized;

}

function onResized(sender, eventArgs)
{
        var plugin = sender.getHost();
        setSize(sender, plugin.content.actualWidth, plugin.content.actualHeight);
}

function onFullScreenChanged(sender, eventArgs)

{

	var plugin = sender.getHost();

	setSize(sender, plugin.content.actualWidth, plugin.content.actualHeight);

}

function fullscreenThisMugUpInHere(sender, args)

{

	var plugin = sender.getHost();

	plugin.content.fullScreen = !plugin.content.fullScreen;

}
function setSize(sender, width, height)
{

	var scale9 = new Scale9(sender.findName("bg_tl"),

	sender.findName("bg_top"),

	sender.findName("bg_tr"),

	sender.findName("bg_cl"),

	sender.findName("bg_center"),

	sender.findName("bg_cr"),

	sender.findName("bg_bl"),

	sender.findName("bg_bottom"),

	sender.findName("bg_br"));

	scale9.redraw(sender, width, height);

}

Conclusions

While vector graphics can be redrawn to get around scale 9 problems with code, not all designs are that simple. Furthermore, as much as Microsoft wants the design community to use Design to create interfaces, they don’t; they use Illustrator, Photoshop, and other 3D and video compositing software NOT made by Microsoft. As such, you’re likely to get a bitmap file that you’re expected to implement into Silverlight instead of an easy, vector design with XAML behind it. Blend is pretty pimp in it’s handling of PNG’s, so if you gotta go raster instead of vector, go with the best.

Also, keep in mind scale 9 works the same for fullscreen; you just call the scale9.redraw function in the onFullScreen as well as the onResize and it’ll nicely scale to the size of your monitor.

Thanks for the Gray frame design on such short notice, Charlie!

Samples

Red Video Player – Example | Source ZIP

Gray Video Player – Example | Source ZIP

Released under a Creative Commons GNU General Public License.

4 Replies to “Scale 9 in Silverlight”

  1. You bastard :P didn’t expect that video in a code example, caught me off guard reaaaaaaal good.

Comments are closed.