Does "oversizing" images give better image quality?

Hi,

In my game, I have an image of a bomb that is displayed at 50 x 50 px. I have created a 50 x 50 and a 400 x 400 image of the bomb for testing purposes. I can see that when I use the 400 x 400 image, the quality is significantly better than when using the 50 x 50 image, although both are set to be displayed at 50 x 50 in the code.

Why is that?

I assumed that Corona downscaled the bigger image to 50 x 50 before displaying it, which should make the two images roughly equal from a quality point of view. Have I misunderstood this? Or could it be that it is displayed in 50 x 50 in “virtual Corona pixels” in both cases (sorry about the terminology) but that the bigger image is using more “real pixels” in the display and thus gives a better quality? I’m using the “utlimate config.lua” file:

local aspectRatio = display.pixelHeight / display.pixelWidth application = { content = { width = aspectRatio \> 1.5 and 800 or math.floor( 1200 / aspectRatio ), height = aspectRatio \< 1.5 and 1200 or math.floor( 800 \* aspectRatio ), scale = "letterBox", imageSuffix = { ["@2x"] = 1.3, }, }, }
  1. This will vary from image to image.

  2. Generally, if the source image is very near the true display resolution of the image it will look better.

  3. You can’t rely on what you see in the simulator.  You must test on device for the final verdict.  There are many things that can’t be simulated: Difference in display tech from monitory to mobile device, pixel-density, OpenGL and ‘driver’ variances, …

  4. If you are scaling a single source image to create the versions (with Photoshop of other software), this will also affect your final results.  In this case, you are always better off starting large and using Photoshop of other decent software to downscale.

  5. An old rule of thumb I used to follow was:

a. Plan my app and determine my ideal target device/resolution.  i.e. True screen resolution.

b. Determine the true pixel size my assets will display at on this device.

c. Design my assets at twice that resolution. (This is the @2X image).

d. Use normal and @2x images in my product( if the app size is not too bloated ).

– OR –

e. If the 2X images are simply too fat, scale the source images to 1/2 size when I go to production.  i.e. Keep backups of the large images, but shrink them all by 1/2 and use no imageSuffix scaling rules in config.lua.

(This leaves me with high resolution images to use in future updates when the common device is at a higher resolution.)

Today, scaling is even more difficult to decide on because of the introduction of mega-resolution devices like the iPad Pro and the iPhone 6+.

  1. I personally hate ‘calculated config.lua files’ because they introduce ambiguity in the design.  Others love them.  However, I strongly believe that using them before you truly grok scaling will only hurt you.

Finally, at the end of the day.  The image that has been least scaled (whose original resolution is nearest to the physics resolution it is being displayed at) will generally look the best.

PS - You said “I assumed that Corona downscaled the bigger…” DO NOT ASSUME.  Measure and determine for sure.

a. Find out if  your app is displaying the 1x or 2x image.

b. Calculate the true resolution the image is displaying at.  i.e. How many physical pixels are being used.

Use this information to make an informed design decision.  

(This is where the calculated config.lua messes you up.  You can’t guarantee parity on your choices between devices because the config.lua values are always different.)

One more note.  I do give a nod to “Sergey’s” calculated config.lua because he tries to solve a very big problem.  Sub-pixel rounding errors.  

http://spiralcodestudio.com/corona-sdk-pro-tip-of-the-day-36/

He calls this pixel perfect, but in short he mostly avoids the likelyhood that an object designed to be 50x50 will end up being 49.49 x 49.49 instead and thus display as 49x49.  

As well, he reduces the likelihood of scaling skew where the scaling is not a perfect pixel multiple thus causing blurry images.

To understand the last, consider this scenario:

  • Images:
    • Original 100 x 100
    • A scaled to 50 x 50
    • B scaled to 51 x 51
    • C scaled to 200 x 200
    • D scaled to 199 x 199

A will probably look better that B even though B is higher resolution.  Why? Sub-pixel blending errors.

C will probably look better that D, even though D is closer to the original resolution.  Why? Same reason.

what @roaminggamer said, adding only that calculated config files CAN be controlled IF you only calc one axis.

(where “ultimate config” muddies things is that EITHER axis may be calculated)

say you have a portrait app, then set your width explicitly and calc height to device aspect ratio

@2x logic would then function same across devices as “normal” content sizing

(in fact it can even be MORE deterministic than plain letterboxing, because your content width will always be fit-to-device-width, and the @2x logic is based off width ratio, not height ratio)

this assumes that your app is internally capable of handling any height derived from aspect ratio, using 800w…

fe 4:3 at 800x1067

or 3:2 at 800x1200

or 16:9 at 800x1422

(or even galaxy s8 18:9 at 800x1600)

but at least your content width is constant (thus so is your @2x logic)

Note: “Re my use of word hate”.  I don’t actually hate the article or the idea.  I am bothered by the fact that folks jump on the use of it w/o understanding the ramifications.  

I am strongly of the opinion, that when learning something new, you should ratchet down the variables (things that can change), then tweak one feature at a time to understand the effect of that change.

When newer folks use a calculated config.lua as their foundation and then switch device resolutions (simulated and actual) while also making arbitrary and semi-informed design choices, the end result is often error and confusion.

It is worth noting, this site has great docs and guides and IF folks spent a lot of time reading those guides before jumping into game making, this stuff wouldn’t be mysterious.   The guides thoroughly cover the concepts and issues inherent in scaling.

However, I think it is natural to simply want to jump in feet first. :slight_smile:

I now realize that I did not explain my question properly, sorry about that.

The only reason I included my config.lua was for clarity but my question is not about content scaling. The tests with both images were done on the same device, which uses the “1x” version. In other words, both the 50 x 50 and the 400 x 400 image are used as the “1x” image, I don’t even have created a “2x” version yet.

The question was more about how Corona handles images that are larger than the size specified in the code. When I said “assume” I meant that I do know for a fact that Corona somehow scales the image since it takes up 50 x 50 pixels on the screen and not 400 x 400 pixels.

So, what I wanted to know was HOW this scaling is done since the 400 x 400 version was displayed in better quality than the 50 x 50 version (both having the same size on the screen). Maybe I should also mention that both images were exported in the specified sizes from a vector image in AI.

Just one reflection about the “ultimate config.lua”…

In the tutorial (and the later, updated version of it) the calculation based method was hailed as an amazingly simple way of achieving a dynamic configuration for the app. Now, I have seen that not only roaminggamer but others as well think that it might not be such a good solution.

Was it never a good solution to begin with, after all. Or has something changed since then? Just trying to understand…

Corona is essentially a “billboard sprite” system built on top of OpenGL.  All of your images are just quads with a texture.

newImage() sets the content dimensions of that rect to the pixel dimensions of the image.

newImageRect() set the content dimensions as specified, regardless of image dimensions.

if you feed newImageRect a higher resolution image than the content dimensions given, then it will be at an apparently higher pixel density.  but which *actual pixels* can be rendered further depends on the ratio of your content dimensions to the device dimensions…

imagine:

you have content dimensions of 320x480

you load a 640x960 image via newImageRect() at 320x480 (so it has @2x density pixels)

on a 320x480 *device* those extra pixels are “wasted” (can’t be rendered)

but on a 640x960 device that same 320x480 *content rect* becomes a 640x960 *device rect* and CAN render those additional pixels (and will look much better)

you don’t HAVE to do this via the dynamic resolution selector, you can do it “manually” as above, but the effect is the same - higher density pixels in a given amount of content space.

This explains it quite well - https://www.paintcodeapp.com/news/iphone-6-screens-demystified

they’re good ;in isolation, covering either dynamic content sizing OR dynamic image resolution, but few address their interaction

every few months there’s a forum post along the lines of

  i’m using 480x800 content dimensions

  i’ve set @2x images to kick in at 1.5

  why doesn’t an ipad at 768w use the @2x images?!!

  and they’re convinced that 768 is 1.6 * 480 and 1.6 > 1.5 – and most math teachers would agree, but it’s not that simple with dynamic screen stuff involved.

i’m sure  you  know (i’m not intending to “preach to the choir”), but…

it’s because those taller dimensions (16:10 aspect) on a squarer device (like 4:3 ipad) will cause the letterbox bars to be on the sides , not top/bottom.  so their actualContentWidth (which is what the dynamic image selection ratio is based on) will be 600, not 480.

so it’s not 768/480ths = 1.6, instead it’s 768/600ths = 1.28, so 1.5 threshold is not met, so @1x images are still used.

THAT sort of nuance of interaction is not well-documented imo

Downscaling is almost always better than upscaling.

i.e. If you use a 50x50 and 400x400 image to create an object that ends up being displayed with 200x200 real-pixels, the later will look much  better.

50 --> 200 = 200% upscale.  This will be blurry and ugly.

400 --> 200 == 50% downscale.  This should still be pretty sharp, with a little bit of aliasing, depending on the characteristics of the image.

I use exactly that config but in landscape and iPad does use @2x

It would be great if Corona scaled based on orientation as it would save some headaches!

i.e. if an app is landscape then @2x using 1.5 should kick in at 1200px or if an app is portrait then @2x using 1.5 should kick in at 720px

Maybe config.lua needs to be set as width=800, height=480 for landscape apps as this makes more sense to me.

yeh, i didn’t even toss in the orientation issue – and you’re right, it’s really THREE topics that need to be considered simultaneously

trying to close the loop and tie this back to my earlier assertion that calculated content resolutions can be MORE deterministic than letterbox wrt dynamic image scaling…

back to portrait orientation for the moment, if width is set to a fixed 480 and height were calc’d as 480*deviceAspectRatio, then there’d be no letterbox bars anywhere, and the 1.6 @2x images would have been used on that portrait iPad (as my ficticious OP imagined) – the resulting width-scale-ratio seems more “intuitive” with the calculated dimensions than with the letterbox extended-width.

personally I find zoomEven works best for my app.  iPad gets more vertical and iPhone gets more horizontal but then again my playing area is MANY times larger that the screen can display.  the only real pain is locking UI top and bottom.

to address the OP, this is a trick commonly used in websites… on non-retina displays (1 point = 1 pixel) the browser downscales the large image to the smaller image dimensions. but on retina displays (where 1 point is actually 4 pixels) then the higher resolution raw image is used instead.

so a 100x100 image fit into a non-retina display will be downscaled to 50x50.  On a retina display the 100x100 will be used but it will only take up 50x50 points on the display - if that makes sense

The ultimate config.lua and the following “Modernizing” was done to solve a problem, that was simply to make 0, 0 the top, left corner, and display.contentHeight, display.contentWidth the bottom right corner. This gives the app a content area that is guaranteed in letterbox mode to fill the screen exactly but it has a bad drawback in that is the distance between objects cannot be guaranteed. It requires things that should be a fixed distance apart harder to accomplish.  Considering these two scenarios:

A Card game with 7 stacks of cards

Angry Birds

In the card game scenario, you are likely going to have the 7 card stacks evenly spread across the screen. If your on an landscape iPad, you only have 480 content area points to draw the 7 stacks. On an HDTV shared phone, you have 570 pixels to show things, so it’s a bit easier to position the cards since the number of points between cards in irrelevant. They can spread out, they can compress together as long as they don’t over lap.  The variable config.lua can work for this scenario.

But this would totally stink for Angry Birds because the distance between the slingshot and the pigs would be different on every device and the game would be constantly be playing differently for each different screen shape.

The standard fixed config.lua guarantees that your 0-319, 0-479 will represent the same distance between points regardless of the device. However 0, 0 isn’t necessarily the top, right and display.contentWidth and display.contentHeight is the right, bottom of your content area. Because screens are a different shape, you pretty much have to use display.actualContentHeight and display.actualContentWidth to get the real size of the screen and then add display.screenOriginX, display.screenOriginY to anything that needs to be edge aligned.  

Some UI elements can be moved around without impacting the game. Things like the score, a lifebar, a fire button etc. need to be near edges. The distance between the score and the lifebar don’t have to be constant. You want your buttons in the corners for easy reach.

If you look at an iPhone 5 with a 320x480 content area (I’m going to do this example totally as a landscape example, but you still have to consider config.lua in portrait orientation). The actualContentWidth of the iPhone 5 with a fixed 320x480 config.lua will be 568px. The content area is typically centered on the screen.  568 - 480 = 88 content points that are now evenly divided on either side of the content area.  This means the left edge is actually at a .x of -44.  The right edge is either display.contentWidth + 44 or display.actualContentWidth - 44. They work out to be the same.

There are different strategies at tackling this, but they all involve using display.contentOriginX, display.contentOriginY. In this example .contentOriginX will be -44, .contentOriginY will be 0.

To position the health bar at the top-left corner 50 points from each edge you would do:

healthBar.x = 50 + display.contentOriginX

healthBar.y = 50 + display.contentOriginY

Corona will have calculated the value for contentOrigin*  and you know for certain the edge is those pixels away from your content area.

The trick on the other side is that since 0, 0 is already 44 pixels away from the leftEdge, if you want to position your fireButton at the bottom, right, if you use display.actualContentWidth, that part of your app will already be 44 pixels off screen, so you still have to add the contentOrigin* contents  to adjust things:

fireButton.x = display.actualContentWidth  - 50 + display.screenOriginX   – position the button 50 points from the right edge of the screen.

Of course you could take the math.abs(display.contentOriginX) and add that to display.contentWidth to get the same place on the screen.

For your core game elements, keep them within the 0-480, 0-320 range and call it a day. At that point you’re not trying to edge position but content area position.

Let’s switch to an iPad which gives you an effective 360x480 screen. Now display.screenOriginY will hold the value of -20 (360-320=40/2=20) and as long as you are always adding these screenOrigin* constants to edge positioned objects, they will move to the right place on your iPad, and your game elements stay in their 320x480 box.

We recommend that you use a fixed config.lua and write the extra code to add the screenOrigin* constants to your edge positioned items. It’s extra typing but unless you have a game that benefits from variable distance from game objects, you’re better off with the fixed config.lua.

Rob

@Rob:

Thanks for your thorough explanation.

If I understand you correctly, doing it the way you describe it above would in fact also guarantee that the screen would be filled with “content” and that there would be no “black edges” neither on top or to the sides. Is that correct?

Now that the “ultimate config.lua” seems to have fallen out of grace, what would you recommend that a good config.lua looked like (given that I do not want to optimize for any single device)?

Good config.lua?  

That depends on your app really.  If it is a simple match 3 style game at 320x480 with @1x, @2x and @4x assets will be just fine.  If you want something with more depth and more touch accuracy I would suggest 480x800 with @1x and @2x assets.

If you are only targeting high end devices you could go for 800x1200 but Corona will be downsizing on devices like iPhone 5.

IMHO, 480x800 covers most devices and scales well.  iPad might cause a few headaches as it is almost square but you could just over size your backgrounds and keep the main game content in the centre of the screen.

640x960 is also a “convenient” resolution these days, as often a single set of images can suffice.  (because it’s “@1x” is effectively @2x, so you’d only add @2x this THIS config only if you’d have added @4x to a 320x480 config)  caveat: if still need iPhone3 support with lots of images / memory concerns.

it can help to think about if your app has a “natural” single axis that it extends best along.  if so, then it’s often “convenient” to fix the other axis.  fe, if making a horizontal runner then fix the vertical axis, and let the horizontal axis be whatever the device aspect dictates - your “camera” should handle everything else.  (and vice versa for a vertical jumper/shooter)

if you free-scroll on both axes then it really doesn’t matter if either is fixed, your “camera” should handle everything.

if your app doesn’t “naturally” scroll in EITHER axis, fe if it’s mostly ui elements with a fairly rigid structure, then you can just letterbox, occupy the content area as designed, and maybe just fill the bars with background.  if you could compensate your ui a bit to better use those bars, instead of just background-filling them, then that’s pretty much the “standard config” way of doing things.

The config.lua that we recommend is:

application = { content = { width = 320, height = 480, scale = "letterbox", fps = 60, imageSuffix = { ["@2x"] = 1.5, ["@4x"] = 3.0, }, }, }

Make your backgrounds 360x570 for the 1x version. Don’t put anything important in the background image that’s in the 45 pixels near the long edge of the image and in the 20 pixels along the short side of the image.

Position game objects with the understanding that the only area guaranteed to be on screen is 0, 0 - 320, 480. Anything that needs positioned near an edge and can move add display.contentOrignX to the .x of the object and add display.contentOriginY to the .y. 

Rob

I’m surprised the whole mess of config.lua hasn’t been revisited as I’ve *always* found these extended configs to be more trouble than they are worth.

Personally I think it is time to move on from the width and height of 320x480 (way outdated) and use a better default resolution.
My own configs use something like:

width and height of 960x480 (all modern devices support at least this), letterbox scaling and alignX and alignY of left and top, so you will always get a *minimum* of 960 x 480, but with extra width or height as needed to fill the device screen.