Understanding Letterbox Scalling

Hi all,

I’ve replied to a post recently about how letterbox scalling works and since then I’ve bumped into a lot of people having trouble with the subject to whom I’ve been linking that old post.
As I came to the realization that it is quite an important subject and one that people don’t understand too well, I’m creating a thread of its own, so here it goes:

<< Start of repost >>

Let’s assume you’re coding on portrait orientation.

You set your config.lua for a screen of 320x480 pixels, letterbox scalling, and position centered both vertical and horizontal.

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

Now, when you create a background for the game, and you want to target iOS devices, so resolutions 320x480, 640x960 and 768x1024, you should do it like:

local bg = display.newImageRect("bg.png",360,480)  
bg.x = display.contentWidth/2  
bg.y = display.contentHeight/2  

The resolution of the image files should be:

bg.png - 360x480
bg@2.png - 720x960, or even 768x1024 if you want it to look really good on iPad.

Why this 360 pixel width?

Well, the thing is that with letter box, the images on higher resolutions than defined on config.lua will be scaled to fit the screen, but they won’t lose their aspect ratio, and that 320x480 rectangle you define on config.lua never goes out off screen.

Let’s see some examples. Start by assuming we did things the “normal” way. You call background images as 320x480, what would happen is:

iPhone - No scalling, config.lua already defines a rectangle of the size of the screen, so it would look fine.

iPhone4 - Scaling by a factor of 2, so the ratio is the same. Would look fine too, it would use double resolution image (640x960)

iPad - the 320x480 rect would grow by a factor of 2.1333…, ending up at a size of 682x1024. Since the iPad has a 768x1024 screen, you would notice some black bars on the sides.

Now lets assume you did as I said and called background images as 360x480.

iPhone - No scalling since config.lua defines a rectangle of screen size. The background though is a bit bigger for the screen and has some extra width compared to screen size. Since the background is centered, you would actualy not see some pixels to the sides. More exactly, 360-320 = 40, so 20 pixels to each side.

iPhone4 - Exactly the same case but on higher resolution, scalling by a factor of 2, usage of high res images.

iPad - Once again it will grow by a factor of 2.133, so a 360x480 images would grow to 768x1024, completely filling the iPad screen. No black bars. Usage of high resolution images.

Basically, using this you create the backgrounds with some extra areas that will only be shown when the screen has a different ratio. The screen will always be filled. You have to keep in mind though that anything out of the 320x480 center rectangle of an image (considering low res images), will not show on some devices, so don’t base you game in that areas, use it just for filling.

On Android the case is a bit different since the screen is actually taller (on portrait). So instead of having to increase width of background images from 320 to 360, you have to increase their height, from 480 to…
Well, considering all available android devices that number is 570.

In the case you want to completely support all resolutions of all phones/tablets available today, you should:

config.lua:

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

Instantiation of images for backgrounds:

local bg = display.newImageRect("bg.png",360,570)  
bg.x = display.contentWidth/2  
bg.y = display.contentHeight/2  

Low res images: 360x570 pixels
High res images: 720x1120 pixels
Remember that when you’re coding though, you’re coding for the rect defined on config.lua, so you’re coding for a screen of 320x480. If for example on iPad you want to refer to the left of the screen it will not be 0, as 0 is a bit distant from the border, as iPad is wider and this 320x480 rect preserves aspect ratio. So you should actualy rely on some corona defined values to help you. For any device you can define the top, left, right and bottom points as:

Top:

local topY = display.screenOriginY

Right:

local rightX = display.contentWidth - display.screenOriginX

Bottom:

local bottomY = display.contentHeight - display.screenOriginY

Left:

local rightX = display.screenOriginX

Hope this helps everyone to understand letterbox a bit better!

<< End of repost >>


Manuel [import]uid: 61899 topic_id: 23200 reply_id: 323200[/import]

Clap, clap, clap…

I thank you for such explanation @CluelessIdeas.
Cheers,
Rodrigo. [import]uid: 89165 topic_id: 23200 reply_id: 92834[/import]

Fantastic share! I imagine this will help a lot of developers :slight_smile: [import]uid: 52491 topic_id: 23200 reply_id: 92965[/import]

So far, this seems to be a brilliant solution. Is there a way to make this more prominent in the documentation? It seems to me that it would be an ideal framework for people to work with [import]uid: 87911 topic_id: 23200 reply_id: 94731[/import]

I’ve been playing with this code a bit, and wondered if there was any fault in the following globals (useful for positioning)
[lua]screenWidth = display.contentWidth - (display.screenOriginX*2)
screenHeight = display.contentHeight - (display.screenOriginY*2)
screenTop = display.screenOriginY + display.screenOriginY
screenRight = display.contentWidth - display.screenOriginX
screenBottom = display.contentHeight - display.screenOriginY
screenLeft = display.screenOriginX
screenCentreX = display.contentWidth/2
screenCentreY = display.contentHeight/2[/lua] [import]uid: 87911 topic_id: 23200 reply_id: 95707[/import]

There just one wrong:

screenTop = display.screenOriginY  

Everything else seems fine.

By the way, if for any reason you want to have screenWidth and screenHeight in real pixels, instead of the abstraction created by what you define in config.lua, you can do it like:

[code]
screenWidth = display.contentWidth - (display.screenOriginX*2)
screenRealWidth = screenWidth / display.contentScaleX

screenHeight = display.contentHeight - (display.screenOriginY*2)
screenRealHeight = screenHeight / display.contentScaleY
[/code] [import]uid: 61899 topic_id: 23200 reply_id: 95753[/import]

AH well spotted. I think I copy-pasted a bit too much.

Is there a reason why xAlign & yAlign are better set to “center” rather than “left” / “top”? Is this just to keep as much content on screen as possible?

Likewise, is letterbox used over zoomEven because of problems with physics (I read that zoomEven can mess up the physics object positions - perhaps not in the more recent builds of the SDK)

I’m trying to build up a basic project skeleton and want to ensure that it’s likely to work on as many devices as possible. [import]uid: 87911 topic_id: 23200 reply_id: 95757[/import]

That x and y alignment is for the box you define on config.lua, namely the 320x480 I defined in my examples. If you set them to left and top, for example on an iPad, instead of having an extra 20 (virtual) pixels on each side, you’ll have 40 extra pixels on the right.

If you do this, when creating a background you can’t set it’s position for center (or else you’ll have some bleeding areas going off screen to the left), you would have to set topleft reference and 0,0 position. This means that the bleeding area you create on your images for background would all have to be on the right (and bottom for androids). This would be extremely assymetrical. You would be positioning the focus of your game to the right, and to the top, instead of making people focus on the center of the device which I suppose makes more sense.

And imagine that you would want to set an element to the center of the screen. I don’t even know if that would be possible. You would have to set the X of the element to be in the middle of the 320x480 rect, and then sum some ammount to actualy center it. How much would you have to sum? In this scenario screenOriginX would be 0, so you don’t even know dynamicaly how large is the bleeding area to the right.

The problem with zoomEven is that on different ratios, some of the content from the 320x480 rect can go offscreen. What if some important content goes offscreen? The game becomes unplayable! With letterbox you guarantee that nothing inside that rect goes offscreen, so you have a region in which you can base your game, and you’re sure that no matter how strange the ratio may be, it can even be 1000:1, nothing important will ever be offscreen. [import]uid: 61899 topic_id: 23200 reply_id: 95858[/import]

Thanks! That post really helped me!
Can i use the same configuration for “landscape” scale? [import]uid: 76774 topic_id: 23200 reply_id: 98348[/import]

@danillo.vellozo Yes of course. You just have to swap the dimensions of the images when creating them on your game logic. E.g.: [lua]display.newImageRect(“bg.png”, 570, 360)[/lua] instead of what you see in the examples above. [import]uid: 61899 topic_id: 23200 reply_id: 98485[/import]

This is a most brilliant and well explained post!!!
[import]uid: 19626 topic_id: 23200 reply_id: 98501[/import]

First, thanks for taking the time to write this. Math makes my head hurt, so I like “magic recipes”. I’m doing almost what you recommend for my landscape app, except I’m using the 570x380 formula recommended here, since the actual size of my normal image is 570x380 pixels.

http://blog.anscamobile.com/2010/11/content-scaling-made-easy/

However, when I call

background = newImageRect("bg.png", 570, 380)  
background.x = display.contentWidth/2  
background.y = display.contentHeight/2  

The image is blown up all the way and bleeds off the edges of the screen, like I called display.newImage(“bg.png”, true). Is this a bug in build 773? I have to scale it manually:

background.xScale = display.contentWidth / background.contentWidth  
background.yScale = display.contentHeight / background.contentHeight  

Another question to anyone and everyone. My client is providing me with the graphics, and most screens have the buttons burned into the background image. I’m creating invisible rectangles with touch listeners to get those areas to respond to button clicks, but I’m hardcoding the top, left, width, and height. Is that bad practice and should I be using the predefined Corona constants * a certain factor? If I ever changed my 320x480 in the config.lua, I suppose I would get badly burned by these hardcoded numbers.

[import]uid: 58455 topic_id: 23200 reply_id: 98859[/import]

@davemikesell It should be bleeding by exactly 570 - 480 = 90px or 45px on each side, and 380 - 320 = 60px or 30px on the top and bottom, if you done everything correctly. You seem to be making the same mistake most people do when dealing with image scaling. You’re expecting to see the whole image all the time on all devices. That’s not what you want. It’s supposed to have bleed areas that aren’t visible on all devices (e.g. iPhone). The background image is supposed to be bigger than the display.contentWidth and Height. If you scale it down like you do then you’re loosing all the benefits of this approach and might as well just use newImageRect(“img.png”, 480, 320) because that’s what you end up with by doing it like you do.

Also, if you followed the guides correctly and have everything working as expected then there’s no need to use 380px tall images because the extra 20px will never be seen on any device. [import]uid: 61899 topic_id: 23200 reply_id: 98928[/import]

Actually, it is what I want in this case, as my client is providing me background images with buttons baked into them. I think I either need to get them to stop doing that and send a larger plain background and separate button images, or I’m stuck with this approach, or even using zoomStretch to make sure everything is visible.

Thanks again for this thread, though - I’m learning a lot. I do understand your explanation, I just have a special case with this client. [import]uid: 58455 topic_id: 23200 reply_id: 98972[/import]

One more thing I don’t quite understand. Are you saying that if you use newImageRect with an image larger than the resolution set up in config.lua, it won’t be scaled at all? That’s what you appear to be saying when describing the bleed areas above on the 570x360 image.

Other images on the screen - text, logos, buttons - are scaled depending on which device I render to in the simulator. You can see this for yourself in the DynamicImageResolution sample by getting rid of the suffixes. The 200x200 image is displayed on iPad Retina and other large devices scaled up.

Why would backgrounds be different? [import]uid: 58455 topic_id: 23200 reply_id: 99747[/import]

@davemikesell I managed to get these settings working. I found it easiest to draw out an image with pixel markers on it to see how they are displayed on different devices.

As @CluelessIdeas explained, the images are designed to display on iOS with some bleed (the edges of the background image are off the screen) and then on Android, you would see more of this background. The area defined in the settings is more of a “safe zone” that scales inside the device (keeping the aspect ratio). Rather than have black rectangles for any area outside of the safe zone, the bleed area on the background image will be visible instead. If you used a 320x480 background image, you would see the black rectangles. [import]uid: 140429 topic_id: 23200 reply_id: 99751[/import]

Sorry for the confusion. It will always be scaled if you use a scaling mode on your config.lua. I forgot to mention that the bleed areas I mentioned are in “Corona” units, i.e. in relation to your stage width and height as defined in config.lua.

BTW, you can use buttons baked in using the method mentioned here (instead of the zoom method you seem to be using) as long as the buttons are inside the “safe area”. What’s the safe area you ask? Take your 570x360 image, put a 480x320 rectangle centered in the middle of it and there you have your safe area. Using the method outlined in this post everything inside this area will always be visible at all times in all devices no matter what the resolution or aspect ratio are. [import]uid: 61899 topic_id: 23200 reply_id: 99752[/import]

@CluelessIdeas - that also helped me to see this safe area - made 2 red background images (360x570 and @2x 720x1120) and put a green"safe zone" rectangle right in the middle (320x480 and 640x960 respectively) [import]uid: 140429 topic_id: 23200 reply_id: 99753[/import]

Just a little bonus for people using this method:

Instead of using 360x570 (and 720x1140) images you can scale those assets to 360x512 (720x1024) after creating them in the proper resolution (360x570) and you won’t loose much detail. Remember that you only change the image assets, not the newImageRect values, because you want the Rect to be constant. This will save you a considerable amount of texture memory. [import]uid: 61899 topic_id: 23200 reply_id: 99754[/import]

Thanks for the clarification. If I had my client “bake in” the images in the background in the “safe area”, won’t the size of the borders look larger/smaller relative to the various device sizes?

Another reason I want to get away from this approach is because I don’t want director disposing/creating a full size background image on every screen change. [import]uid: 58455 topic_id: 23200 reply_id: 99764[/import]