Content Scaling Made Easy

Posted by

(Read the follow up: Dynamic Image Resolution Made Easy)

Content scaling is a very useful feature of Corona, but it’s one that I’ve found myself explaining frequently. In this post, I’ll try to boil it down to the essentials, and demonstrate how to easily target multiple screens from the same code and assets.

The problem

Mobile device screens now come in many different shapes and sizes. At one extreme, the iPad screen is 768 x 1024, for a 1 : 1.33 aspect ratio. Meanwhile, the Motorola Droid (480 x 854) and Samsung Galaxy Tab (600 x 1024) have aspect ratios greater than 1 : 1.7.

In plain English, the iPad is more square than the iPhone, and most Android devices are taller and skinner than the iPhone. You can see this when the devices are scaled to similar sizes:

…and more clearly if you overlay them:

Until recently, you could ignore these considerations if you were an iPhone-only developer. But now that the iPhone 4 has arrived, even iPhone development requires targeting at least two screen sizes: the original 320 x 480 resolution, and the new 640 x 960 “Retina Display”.

So how do you address this growing variety of devices without rewriting your app?

The solution

If you happen to be one of the rare breed of ex-Flash Lite developers, you know that the answer is content scaling: automatically expanding your stage to fill bigger screens. However, you may also recall that content scaling was often confusing, especially when targeting different aspect ratios simultaneously.

This article is designed to walk you through the basic issues involved in content scaling, and then to introduce the additional scaling options offered only by Corona. The good news is that it is now possible to deploy to radically different screens with the same source code and assets — and it’s easier than you might think.

Getting started

The first step is to decide on your “content size”. Think of this as a virtual “stage”, which will then be expanded to fill the screen of your device. This will also provide the coordinate system for your Corona code, which will be independent of the actual number of pixels on the device screen.

This idea of “content size” is fundamental to content scaling, since the idea is that your code should remain exactly the same across multiple devices. In fact, your code never needs to know the actual screen size — everything will be written within the content size defined by your config.lua file, which may or may not match the screen’s physical resolution.

Today’s mobile developers often begin with traditional iPhone-sized content at 320 x 480, and then scale it up for deployment to various Android devices or the iPhone 4. This is also the easiest case to understand, so most of the examples in this article will assume this common content size.

A typical config.lua file will therefore look like this:

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "letterbox"
    },
}

What about scaling down rather than up?

Yes, content scaling works equally well in both directions! For example, the “SimplePool” sample project in Corona Game Edition has a content size defined as 768 x 1024, which are the dimensions of the iPad screen, but it can also be built for iPhone or Android devices, and will automatically resize itself to fit.

application =
{
    content =
    {
        width = 768,
        height = 1024,
        scale = "letterbox"
    }
}

The thing to keep in mind is that texture memory (which is used to store graphics) is the most valuable and limited resource on mobile devices, and higher-resolution graphics will consume more of it — and to make things worse, older devices will have far less texture memory available.

For this reason, it is generally not a good idea to use the iPhone 4 screen (640 x 960) as your content size and then scale everything down for the iPhone 3/3GS, because the higher resolution will quadruple the texture memory consumed, even while the previous iPhones only have half as much total texture memory available.

Instead, we recommend using automatic asset substitution, using the “Dynamic Image Resolution” feature of Corona. In addition to scaling up your content to fill higher-resolution screens, Corona lets you automatically swap in more detailed assets to take full advantage of better displays — both iPhone 4 and Android. To learn more about this feature, see this page, or the “DynamicImageResolution” sample project, currently found in the “Hardware” subdirectory in the SDK sample code.

The other catch is that all graphics larger than 512 x 512 must use the optional “true” flag in display.newImage(), which overrides the “safety feature” on older iPhones designed to automatically downscale large images.

bigImage = display.newImage( "bigImage.png", 100, 200, true )

Again, see the “SimplePool” sample code for an example. This does not apply to the newer multi-resolution display.newImageRect(), used in the “DynamicImageResolution” sample code.

“Letterbox” doesn’t mean black bars

The next thing to consider is which scaling mode you prefer. When in doubt, the mode you will probably want is “letterbox”, which is guaranteed to neither crop nor distort your main content region. This scaling mode is named “letterbox” because the effect is similar to playing a widescreen movie on an older TV set: all onstage content is visible, but there may be extra screen area beyond it.

However, unlike the movie example, these extra areas do not have to be black bars, and are not cropped or masked. Instead, they can be filled with any Corona elements you like. To borrow a printshop term, there may be extra “bleed” area around your main content region, either at the top and bottom or on the sides.

In general, you will want to fill this bleed area with optional but non-essential content, such as expanded background graphics. This will provide a more polished appearance than leaving the areas empty and black. For one easy way of doing this, see the “magic recipe” below.

Possible scaling modes

“letterbox” — probably the most common mode, since it doesn’t crop any of your content. See explanation above.

“zoomEven” — this is the most useful mode apart from “letterbox”. In this mode, the stage is expanded to entirely fill the screen, even if parts of the stage are cropped around the edges. This means that there will never be any black bars or excess area, but the edges of your main content may be offscreen.

“zoomStretch” — this mode is generally not recommended, since it distorts the content by stretching the stage to match the width and height of the screen. This will produce visual distortion if the target device has a different aspect ratio than your content. It may also look odd when rotated, since the distortion is determined by the initial orientation.

“none” — this turns off content scaling, and produces the same result as not having a config.lua file at all.

What do upscaled graphics look like?

We discovered right away that older iPhone content looks great on Android screens of roughly the same three- to four-inch physical size, such as the “Droid” device family, even where the resolution of the screens is somewhat higher. Since Corona’s OpenGL renderer automatically performs bitmap smoothing, it’s very hard to tell the difference between 320 x 480 content on the iPhone 3GS and the same content upscaled to 480 x 800 on the higher-density Nexus One.

However, on physically larger tablet devices like the iPad or the Galaxy Tab, upscaled iPhone 3GS content may seem somewhat fuzzy. This will depend on the graphics within the app: for example, a background image of clouds may look much better in an upscaled size than a sharp-edged grid.

You can preview scaled content using the Corona Simulator to show different devices, but keep in mind that our Simulator shows devices at full pixel resolution on your monitor, which can make the higher-resolution devices seem larger and more detailed than they will generally appear in reality. As always, it’s a good idea to test on your actual target hardware during your design phase.

Another thing to consider is that upscaled content can save a lot of texture memory, which is generally the most limited resource in mobile development. If you use up the GPU’s texture memory, the device may start swapping graphics data in and out of main memory, which is a notoriously slow pipeline. You can use

system.getInfo( "textureMemoryUsed" )

in Corona to see how much texture memory your app is using at a given time.

Therefore, while it may be tempting to create all your assets at full iPhone 4 resolution, the tradeoff is that doubling an image’s resolution consumes four times as much texture memory. As noted earlier, a better approach is probably a mix of higher- and lower-resolution images, depending on the target device.

The “magic recipe”

Here is a simple formula. If you’re scaling in “letterbox” mode, and you want to make sure there are no visible black bars on any currently targeted device, use the following settings in config.lua:

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "letterbox"
    }
}

..and create background graphics at 380 wide by 570 high. This is the current “magic size” that fills the screen on all common devices.

Then, you center the background graphics on the screen using these lines (note the true flag for the large image):

background = display.newImage( "background.png", true )
background.x = display.contentWidth / 2
background.y = display.contentHeight / 2

With these values, your background should fill every device screen available in Corona. Note that the declared content size of 320 x 480 is essentially a “safe zone” — any content outside this area might be cropped, either on both sides or at the top and bottom, depending on the target screen’s aspect ratio.

Also note that the recommended 380 x 570 background size assumes a basic content size of specifically 320 x 480, and is dependent on the range of screen shapes we are trying to target. If future devices are either more square or more skinny, this calculation may change, but for now it should be a relatively “future-proofed” size for your background assets. Therefore, you’ll find this recipe used fairly often in the sample code included with the current Corona SDK.

What if I don’t want my content centered?

A good question! There may be cases where you don’t want the added “bleed” areas (or, in “zoomEven” mode, the cropped areas) to be evenly distributed. For example, you might have an app with a menu bar that should always be aligned with the top of the device screen. In that case, you would want any added area to extend the bottom of the screen only, to maintain the top-aligned menu bar across multiple devices.

For cases like these, Corona provides optional alignment properties, both vertical and horizontal:

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "letterbox",
        xAlign = "left",
        yAlign = "bottom"
    },
}

While there are nine possible combinations of the above properties, the ones you are most likely to use are “center”/”center” (the default, which occurs if you don’t specify any alignment) and “top”/”center” (for apps where you have a top navigation bar that should remain flush with the top of the screen). In the latter case, your config.lua settings might look like this:

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "letterbox",
        xAlign = "center",
        yAlign = "top"
    },
}

(Read the follow up: Dynamic Image Resolution Made Easy)

Ready to get started?

Create amazing games and apps for iOS & Android

26 Comments

J. A. WhyeNovember 21st, 2010 at 3:00 am

Great timing, you read my mind! I was just thinking about this topic and was getting ready to dive into the docs to figure it out. You’ve made it very clear! (Although I reserve the right to come back later with questions. ;) )

Thanks!

Jay

JLunaNovember 21st, 2010 at 6:22 am

Excellent post.

I highly recommend linking to this blog post from the existing documentation. Though the existing documentation is technically accurate, it’s hard to get an understanding of the big picture. Since you’ll be linking to a blog post, it should be clear that it was posted on a certain date, so I don’t think it’s so bad if the resolution-specific info gets out of date.

GaretNovember 21st, 2010 at 11:46 am

The way I’d highly recommend setting up to support the small iphone and retina screen it is setting up your config like:

width = 320,
height = 480,
scale = “zoomEven”,

imageSuffix =
{
["@2x"] = 2,
},

Then create two versions of your graphics (for retina and not, so if you want a background make it 320×480 and another version that’s 640×960.) Name the small one “bg.png” and the large one “bg@2x.png”. Then to load it in your lua file use: display.newImageRect(“bg.png”,320,480). That way if it’s on the iPhone 4 it’ll automatically load the bg@2x.png.

This way your saving texture memory on the iPhone 3G yet using high resolution images on the iPhone 4.

The only downside would be that your .app file will be larger due to double graphics. So I’d recommend crushing your pngs as low as possible (without noticeable quality changes). Although since it’s being displayed on such a small screen it won’t be noticeable.

Is there some reason you wouldn’t encourage using this method?

EvanNovember 21st, 2010 at 12:25 pm

@Garet — correct, that method is great!

I was just avoiding discussion of dynamic assets in this post, because (1) it’s very long already, and (2) my experience in teaching this stuff is that people need to get comfortable with basic content scaling before introducing asset substitution. That will probably be the topic of a sequel post, which will also include details like the “1.5x” scaling for Android phones.

EvanNovember 21st, 2010 at 12:37 pm

@JLuna — good idea, done!

http://developer.anscamobile.com/content/configuring-projects#Dynamic_Content_Scaling

GaretNovember 21st, 2010 at 1:51 pm

Ah I see. I was just making sure that method wasn’t discouraged for any reason. As far as I know that’s the only real way of resolution independence.

Maybe that should be included in a release of corona? It’d save us a few extra steps. And it’d help beginners get resolution independence up and running from the get-go.

MagendaNovember 21st, 2010 at 4:34 pm

So, how we use spritesheets in this “dynamic image resolution” ?
Most of the games today are using spritesheets…

GaretNovember 21st, 2010 at 5:03 pm

The method I posted won’t work. I don’t think it’s possible at the moment. For animations I’m just using my custom version of the movieclip library to do dynamic resolution.

GurbsNovember 22nd, 2010 at 3:54 am

Hey, awesome post!

I have some questions:

1- Is the use of sprisheets along with Garet’s method scheduled for a near release?
This is kind of an issue for me, but I think I can handle the animations with movieclips. :-)

2- I started my project targeting the iPhone4 as a base resolution, is it possible to keep the project as it is and still use dynamic scaling and Garet’s method without running into trouble later, or should I refit everything to 320×480 standards?

Thanks for everything!
I’m still on my 30 day trial, but the project is coming along fine, and I’ll probably buy CGE by the end of the trial!

GaretNovember 24th, 2010 at 5:41 pm

Thinking about it (Imma see if it works as a POC and post it in the code section if it does) but I believe sprite sheets using my method could work. The way would be using percentages rather than using pixel values to determine the tiling.

So if you have a 100×100 and it tiles 10 times of the x axis instead of declaring that each frame is 10×100 you’d declare that it’s height is equal to the sheet’s height and the width is the sheets width / 10.

I believe that should work.

AntheorDecember 22nd, 2010 at 11:33 am

Thx for the tips.
You say that content size provide the coordinate system for your Corona code.
Considering :

width = 320,
height = 480,
in a landscape locked app.

Shouldn’t be
width = 480,
height = 320, ?

If not, it becomes confusing :
If I place someting at far right of my screen I should “translate” to far bottom … and what about trying to make things move …

What do you, corona veterans, do ?:)

[...] yourself and share it with the rest of the Corona community (whether as a video or as a simple post with coding annotations), let us know and we’ll help you out with anything you need! [...]

[...] at the edges on some devices. This topic deserves its own article, and happily, we have one: see this discussion of content scaling and multi-screen deployment with [...]

MadelineJanuary 20th, 2011 at 11:04 am

Can somebody give me a code that allows me to change the size of an image??

[...] (1) Content scaling automatically expands (or shrinks) the entire Corona stage so that your content fills different screens. This topic won’t be covered here, but you can read a full discussion of content scaling in this earlier post. [...]

[...] SDK can do some amazing things with dynamic content scaling and dynamic image resolution to make sure your graphics look sharp on hi-res screens. What [...]

duneunitApril 10th, 2011 at 10:04 am

I am banging my brains all over the place trying to understand something that may just be remarkably simple. Please help me to stop smashing my head against a desk…

I am building with the config file set for 480×320 pixels in landscape mode.
I load in a background image that is 768×512.
It fills the screen perfectly scaling itself to the 480×320 spec, as I hoped.
Now if I create a 480×320 pixel rectangle, or load in a 480×320 semi-transparent png image, I would expect it to resize itself to the contentWidth and contentHeight which is 480×320… thus also expanding to fill the entire screen. But it does not.

I see people saying that the code doesn’t need to be rewritten for various screen resolutions. So if I have a 200 x 100 pixel screen device (just an example) and I set the config file to assume a 100×50 pixel resolution, then moving a sprite or movie clip to coordinate 10, 5 actually moves it on the native resolution to 20, 10, right? And drawing a vector graphic of a rectangle would do the same thing, right?

If so, this is NOT what is happening in the windows Corona simulator. And if NOT, then what are you talking about when you say code doesn’t need to be rewritten for different screen resolutions? Could you please give an example– I am going crazy trying to figure out what I am missing! Thanks!

duneunitApril 10th, 2011 at 9:14 pm

I figured out my own problem. THIS IS ESSENTIAL:

The build.settings file orientation was set for “landscapeRight” mode.
Thus, in the config.lua file, I had set the width to 480, and the height to 320. I figured width is the long side in landscape mode. But no. This is not the case. At least not in the config.lua file. Even in landscape mode, the config file width should be 320 and height 480. I am now assuming that in the config.lua file the width x height values should always assume the values are seen from a “portrait perspective.”

This is VERY confusing, in my opinion. Why? Because in the main.lua file contentWidth and contentHeight are the REVERSE of what “width” and “height” represent in the config.lua file when in landscape mode. There may be some reasoning behind this which I haven’t thought out yet, but there you have it.

Regardless, I’m posting this to help anyone who might run into the same glitch. Now that I have switched my width to 320 and height to 480 in the config file, all of my content appears to be scaling properly. I assume that movement will scale properly according to the display.contentWidth and display.contentHeight as well.

TomApril 11th, 2011 at 8:20 am

@duneunit
The Width and Height in the config.lua file describe the device and not your application. An iPhone for example is always described as a 320×480 or 640×960 device. Most of our sample programs use scaling so they work on all device sizes.

We will look at making this point clearer in our documentation.

Thanks.

bubuMay 22nd, 2011 at 1:17 am

Sorry guys but in my personal opinion this topic is a mess. Talking about coordinates, especially the screen ones, needs precision. At 1st you displayed the image of iPad being the same size of iPhone ? Furthermore, as an developer i’m always afraid of statements when something is “made easy”. Often that means that the most important things will be hidden from me, and that’s dangerous …

Jerome82July 25th, 2011 at 7:03 am

I’m just very grateful that this conversation is out there being discussed…. I’m hitting the wall with this very idea – and am grateful it’s talked about. Onward, to continue my reading and understanding…. cause I just had my app display on the iPhone4 as though it was an iPhone3 and it’s messin’ with my head! Especially when the Simulator shows it perfect on the iPhone4.

Jerome82July 25th, 2011 at 10:24 am

I’m just very grateful that this conversation is out there being discussed…. I’m hitting the wall with this very idea – and am grateful it’s talked about. Onward, to continue my reading and understanding…. cause I just had my app display on the iPhone4 as though it was an iPhone3 and it’s messin’ with my head! Especially when the Simulator shows it perfect on the iPhone4.

OK, I implemented the “magic recipe” to great success! However, my iPhone4 DEVICE behaves like the iPhone3 simulator… yet the iPhone4 simulator – looks perfect. Why the discrepancy between Sim and Device..?!????

NickAugust 3rd, 2011 at 7:51 am

I just want to be clear. In order to have it work on both iPad and iPhone, I only write one line of code in the confit file and rebuild it as a universal device? I’m going from iPad to iPhone.

ErnestDecember 18th, 2011 at 5:44 am

Hello,
I’m ussing this content scaling on my project. Everything looks perfect, but the tap/touch events associated to display objects are not scaled.
I mean, I touch on the display object and no event occurs. I touch next to it, where it would be without scaling and it works?
Any idea about how can I solve this?
Thanks!

Simon StrangeDecember 26th, 2011 at 8:04 pm

I was very frustrated with this method – until I realized that there is a math error regarding the “magic recipie.” The proper “magic numbers” ar 570 x 360 NOT 380!

My calculation is thus: iPad has the widest screen – at 768 x 1024. Scaling up a 320 x 480 section to fill the screen top-to-bottom means increasing dimensions by (32/15), or 2.1333. This creates a 682.666 x 1024 screen space, so we have 85.333 pixels of deadspace, split between the left and right evenly. If we invert the scaling, (85.3333 / (32/15) ) = 40. So we need our original image to have 40 pixels of buffer along x. 320 + 40 == 360 NOT 380.

This is not simply theoretical – I have it implemented in my cross-platform project, and tested on device. When I try to do smoothly tiled scrolling backgrounds, the 570×380 size created very visible seams.

It just goes to show – always check the math!

RussJanuary 23rd, 2012 at 2:57 pm

If I select a larger virtual stage, such as the iPad, and with Dynamic Image Resolution always provide lower res versions for everything to scale down, does this also address the texture memory limitation?

Leave a comment

Your comment