Tutorial: Scaling Processing for Android Apps to Different Screens

In my previous article, I gave an introduction to the built-in features of the Android platform for supporting screens of various sizes and densities. In this article, I am going to expand on this and show you actual code for achieving screen-independence in an app. My example will be a Processing app (as that’s my own primary use case), but the ideas should apply equally well to any game or graphics-centric app.

"Chimpzilla Attacks!" mockup (no, I'm not serious)

Let’s use the game “Chimpzilla Attacks!” as an example. I made up this game specifically for the purpose of this tutorial—and already spent way too much time on the mockup. I have no idea what the game mechanics are, but judging from the cheesy graphics, it’s got to be some kind of “Punch The Monkey” knock-off. Anyway, back to the tutorial…

The skyline backdrop has a resolution of 800×600 pixels. In the following image, you can see its size and aspect ratio relative to various common screen sizes, from a QVGA screen to a 1024×600 screen.

Background image relative to various screen sizes and aspect ratios

The goal here is to scale the game screen so that its width fits the display. In the following image, you can see what the scaled image will look like on various screens.

Background image relative to various aspect ratios

On a 320×240 screen, all of the backdrop is visible (at 40% of the size, of course). This is because both have an aspect ratio of 4:3 (or 1.33). Most of the common screen sizes are wider, though. For example, a 1024×600 screen is 5:3 (or 1.66). The wider the screen, the higher the aspect ratio. On any screen with an aspect ratio higher than 1.33, we’ll have to cut off some of the backdrop’s height, but those parts are not used for the gameplay anyway.

Having to cut off some of the backdrop on wide screens sounds like the game isn’t designed for wide screens. It’s quite the opposite, though. The part of the game screen that’s used for actual gameplay is 800×400, with an aspect ratio of 2 (which is not quite Cinemascope, but close). With such a wide picture, we’d have to display black bars above and below the picture on all screens. Instead of black bars, I can just as well show more of the photo as a filler.

There are still aspect ratios where we have to pad the screen with black bars, namely ratios smaller than 1.33 and higher than 2, as shown in the following image.

Image relative to extremely narrow and extremely wide screens

The 800×640 screen has an aspect ratio of 1.25. There, we’ll display black bars above and below the picture. The second screen is a hypothetical 800×340 Cinemascope (2.35:1) screen. While it is wide enough to show the entire game screen, it is not high enough. We’ll have to scale the game screen down to 680×340 so that it fits the height of the display. At that scale, we’ll have to display black bars (or whatever) to the left and to the right.

Okay, let’s create a “Hello, Chimpzilla!” app that demonstrates all of this.

(I will explain the steps based on the Eclipse Android plug-in. If you haven’t set it up yet, see my first-steps article. If you prefer to work without Eclipse, there’s also an article for that.)

New Android Project wizard

1. In Eclipse, click “File” → “New” → “Project…” and select “Android Project”. Specify the project name, location, package name, etc. As the Build Target, select at least API Level 7 (Android 2.1), and specify 7 as the Min SDK Version. Click Finish.

2. Place the file “processing-core.jar” in the libs/ sub-folder of the project folder. Press F5 in the Package Explorer. The file should now be visible in your project tree.

3. Add a reference to “processing-core.jar” to the project: Click “Project” → “Properties”, select “Java Build Path” and go to the “Libraries” tab. Click the “Add JARs…” button and select the file “libs/processing-core.jar” from your project.

4. Double-click the file AndroidManifest.xml. On the Manifest tab, set the “Target SDK version” of the “Uses Sdk” element to 7 or higher. On the Application tab, set “Debuggable” to “true” (needed to debug the app on a physical device).

When you edit the file AndroidManifest.xml directly, it should look something like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="org.realmike.chimpzilla"
 android:versionCode="1"
 android:versionName="1.0">
 <application android:icon="@drawable/icon" android:label="@string/app_name" 
     android:debuggable="true">
   <activity android:name=".HelloChimpzilla" android:label="@string/app_name">
     <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
   </activity>
 </application>
 <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="7"/>
</manifest>

5. Place the file “chimpzilla.png” in the assets/ sub-folder.

6. Replace the code in HelloChimpzilla.java with this:

package org.realmike.chimpzilla;

import processing.core.*;

public class HelloChimpzilla extends PApplet
{
    PImage backdropImage;

    public void setup()
    {
    	orientation(LANDSCAPE);
    	background(39, 70, 134);
        backdropImage = loadImage("chimpzilla.png");
    }

    public void draw()
    {
        image(backdropImage, 0, 0);
    }

    public static void main(String[] args)
    {
        PApplet.main(new String[] { "HelloChimpzilla" });
    }
}

7. To debug the app, click the “Debug As…” button and select “Android Application” as the target. If this doesn’t appear, go to “Run” → “Debug Configurations…” instead and create a new launch configuration below “Android Application”. Click Debug.

Note: If the debugger stops because of a NullPointerException, the app probably can’t load the image file. Note that Eclipse does not rebuild the app package automatically when you change the files in the assets/ folder. If you placed the file chimpzilla.png in there after you edited the source code, the file might not be in the package yet. Make another source code change and save the file, and Eclipse should rebuild the app package the next time you start debugging.

8. Let’s add the code to scale the image:

    public void draw()
    {
        int newWidth = screenWidth;
        float scaleFactor = (float)screenWidth / (float)backdropImage.width;
        int newHeight = (int)(backdropImage.height * scaleFactor);
        int destX = (width - newWidth) / 2;
        int destY = (height - newHeight) / 2;
        image(backdropImage, destX, destY, newWidth, newHeight);
    }

Basically, we always scale the image to fill the width of the screen and draw it centered. This also works on aspect ratios smaller than 1.33 (where the background color will be visible above and below the scaled image). But we still have to add an “if” for extremely wide screens. On those screens, we will scale the image so that the height of the playfield (400 pixels) fits the height of the screen.

    public void draw()
    {
        float scaleFactor;
        float screenAspect = (float)screenWidth / (float)screenHeight;
        if (screenAspect <= 2)
        {
            scaleFactor = (float)screenWidth / (float)backdropImage.width;
        }
        else
        {
            final int PLAYFIELD_HEIGHT = 400;
            scaleFactor = (float)screenHeight / (float)PLAYFIELD_HEIGHT;
        }
        int newWidth = (int)(backdropImage.width * scaleFactor);
        int newHeight = (int)(backdropImage.height * scaleFactor);
        int destX = (width - newWidth) / 2;
        int destY = (height - newHeight) / 2;
        image(backdropImage, destX, destY, newWidth, newHeight);
    }

That’s it.

You might have noticed that we didn’t take Android’s screen classification (“small”, “normal”, etc.) or the screen density (“ldpi”, “mdpi”, etc.) into account when scaling the graphics. It simply wasn’t necessary to use different resources or a different formula based on this information. Once we add UI elements to the screen, we’ll have to take this into account. For example, when loading graphics for touch buttons, we might want to scale them according to the density, so that they end up in a touch-friendly size on the screen. (See my previous article for more about size classifications and densities.)

You can download a ZIP file containing the source code and the graphics.

Photo of the Boston Skyline is © 2007 Rene Schwietzke, licensed under a Creative Commons Attribution 2.0 Generic license. Boat and Chimpzilla graphics are from openclipart.org and have been dedicated to the public domain.

5 comments to Tutorial: Scaling Processing for Android Apps to Different Screens

Leave a Reply

  

  

  

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Ads