This guide outlines virtual pixels in Corona and explains how they can be made to behave like native iOS/Android virtual pixels using adaptive scaling.
Corona offers its own scheme for virtual pixels known as content scaling. This allows developers to specify a common set of screen coordinates while Corona automatically scales text, vector objects, and images to different resolutions depending on the device.
There are actually multiple ways to approach content scaling. The easiest way is to just choose a single
Define a content area width and height in virtual pixels
Specify a scale mode such as "letterbox"
or "zoomEven"
— this determines how Corona fills the screen.
If using "letterbox"
mode, design for the empty letterbox regions which occur when the aspect ratio of the virtual screen and the actual device differs — these are like the black bars on a TV when the show is letterboxed, except that in Corona, these regions can (and should) have visual content drawn into them. Alternatively, if using "zoomEven"
mode, understand that some visual content may bleed off the screen when the aspect ratio differs.
In practice, a simple config.lua
may include values like these:
application = { content = { width = 320, height = 480, scale = "letterbox", } }
In this guide, this approach will be referenced as simple content scaling, so named because a
For more details on content scaling and how to configure it, please see the Project Configuration guide.
Although simple content scaling works well in most cases, it's not a panacea. In certain apps, particularly
If the app design is locked into a particular aspect ratio, managing the letterbox regions or allowing for bleed isn't ideal. In this instance, it would be better if the entire virtual screen had the same aspect ratio as the device screen.
Interface elements like buttons and other widgets scale proportionally larger on tablets compared to phones — in other words, UI elements that look reasonable (size) on phones typically appear abnormally large on tablet screens.
Given the various drawbacks of simple content scaling, especially in
To set adaptive content scaling, simply set the scale
key within config.lua
to "adaptive"
:
application = { content = { scale = "adaptive", } }
Because adaptive scaling calculates the content area size as outlined in the next section, it is not necessary to define width
and height
values when using "adaptive"
scale mode.
For adaptive scaling, Corona uses heuristics to determine an appropriate content width and height. There are three primary goals:
The aspect ratio of the virtual screen should match the actual device screen, meaning that there are no letterbox regions.
The preferred pixel scales should be whole numbers (or simple fractions) whenever possible.
The virtual pixel density should be roughly the same across devices.
For iOS, this is achieved by setting the content width and height to match the width and height of the device in iOS points. This works out nicely because iOS points are roughly the same physical size.
For Android, the content width and height is set to match the dp hdpi
, xhdpi
, etc.)
Whenever the virtual screen size is allowed to change, there's a tradeoff and the complexity increases slightly. Specifically, the code should be aware that the content width and height can change depending on the device. Thus, rather than using static width and height values for certain interface elements, those values should be defined dynamically via display.contentWidth
, display.contentHeight
, and various calculations around these properties, for instance display.contentWidth * 0.5
display.contentHeight
to position its vertical center point at the bottom of the screen.
The difference between simple content scaling and adaptive content scaling is essentially this:
In a simple scaling scenario, the visual size of content is scaled. Thus, on larger screens, objects actually get larger in ruler length (for example inches or centimeters).
In an adaptive scaling scenario, the visual size of content is approximately equal across different screen sizes, meaning that there's more available screen space on larger devices.
Another way to understand the difference is to inspect what happens to pixel scales. Using simple content scaling, the pixel scales differ across various devices, but they stay the same in adaptive scaling. Consider this comparison:
In a simple scaling scenario, assume that the content width is set to 320
. On an iPhone 7 which has a screen resolution width of 750, the pixel scale is about 750/320
)1242/320
)
Using adaptive content scaling, the pixel scale is 2 on both devices because Apple designed the point widths and heights for each device to make the math work:
Adaptive scaling also affects the choice of scaling thresholds for Retina/HD image selection
In a simple scaling scenario, it may be necessary to go as high as @4x
-suffixed@2x
and @3x
-suffixed
In terms of choosing actual threshold values for the imageSuffix
table of config.lua
, it may seem logical that the scaling thresholds for @2x
and @3x
-suffixed2.0
and 3.0
respectively, as in:
application = { content = { scale = "adaptive", imageSuffix = { ["@2x"] = 2.0, ["@3x"] = 3.0, }, } }
However, there are two specific reasons why the thresholds should be lowered:
On Android, hdpi
devices have a fractional scale of 1.5×. In this case, the @2x
images should be chosen by lowering the associated threshold to 1.5
.
The iPhone 7 Plus is one device where the pixel scale is a little weird. Here, @3x
images should probably be used, otherwise the @2x
images will be chosen and scaled up which might not appear optimal. This can be achieved by lowering the associated threshold to 2.5
.
With these considerations in mind, an adaptive config.lua
should include threshold values like these:
application = { content = { scale = "adaptive", imageSuffix = { ["@2x"] = 1.5, ["@3x"] = 2.5, }, } }