Android Graphics Serving Our Drawing Needs

The Graphics interface we designed in Chapter 3 is also pretty lean and mean. It will draw pixels, lines, rectangles, and Pixmaps to the framebuffer. As discussed, we'll use a Bitmap as our framebuffer and direct all drawing calls to it via a Canvas. It is also responsible for creating Pixmap instances from asset files. We'll thus also need an AssetManager again. Listing 5-13 shows the code for our implementation of that interface, AndroidGraphics.

Listing 5-12. AndroidGraphics.java; Implementing the Graphics Interface package com.badlogic.androidgames.framework.impl;

import java.io.IOException; import java.io.InputStream;

import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.Canvas;

import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect;

import com.badlogic.androidgames.framework.Graphics; import com.badlogic.androidgames.framework.Pixmap;

public class AndroidGraphics implements Graphics { AssetManager assets; Bitmap frameBuffer; Canvas canvas; Paint paint;

Rect srcRect = new Rect(); Rect dstRect = new Rect();

The class implements the Graphics interface. It has an AssetManager member that we'll use to load Bitmap instances, a Bitmap member that represents our artificial framebuffer, a Canvas member that we'll use to draw to the artificial framebuffer, a Paint we need for drawing, and two Rect members we'll need for implementing the

AndroidGraphics.drawPixmap() methods. These last three members are there so that we don't have to create new instances of these classes on every draw call. That would make the garbage collector run wild.

public AndroidGraphics(AssetManager assets, Bitmap frameBuffer) { this.assets = assets; this.frameBuffer = frameBuffer; this.canvas = new Canvas(frameBuffer); this.paint = new Paint();

In the constructor we get an AssetManager and Bitmap representing our artificial framebuffer from the outside. We store these in the respective members and additionally create the Canvas instance that will draw to the artificial framebuffer, as well as the Paint, which we'll use for some of the drawing methods.

@Override public Pixmap newPixmap(String fileName, PixmapFormat format) { Config config = null; if (format == PixmapFormat.RGB565)

config = Config.RGB_565; else if (format == PixmapFormat.ARGB4444) config = Config.ARGB_4444;

else config = Config.ARGB_8888;

Options options = new Options(); options.inPreferredConfig = config;

InputStream in = null; Bitmap bitmap = null; try {

in = assets.open(fileName);

bitmap = BitmapFactory.decodeStream(in);

throw new RuntimeException("Couldn't load bitmap from asset '"

throw new RuntimeException("Couldn't load bitmap from asset '" + fileName + );

if (bitmap.getConfig() == Config.RGB_565)

format = PixmapFormat.RGB565; else if (bitmap.getConfig() == Config.ARGB_4444) format = PixmapFormat.ARGB4444;

else format = PixmapFormat.ARGB8888; return new AndroidPixmap(bitmap, format);

The newPixmap() method tries to load a Bitmap from an asset file, using the PixmapFormat specified. We start off by translating the PixmapFormat into one of the constants of the Android Config class we used in Chapter 4. Next we create a new Options instance and set our preferred color format. We then try to load the Bitmap from the asset via the BitmapFactory. We throw a RuntimeException if something goes wrong. Otherwise we check what format the BitmapFactory decided to load the Bitmap with and translate that into a PixmapFormat enumeration value. Remember that the BitmapFactory might decide to ignore our desired color format, so we have to check afterward what it decoded the image to. Finally we construct a new AndroidBitmap instance based on the Bitmap we loaded and its PixmapFormat, and return it to the caller.

@Override public void clear(int color) {

canvas.drawRGB((color & 0xff0000) >> 16, (color & 0xff00) >> 8, (color & 0xff));

The clear() method simply extracts the red, green, and blue components of the specified 32-bit ARGB color parameter and calls the Canvas.drawRGB() method, which will clear our artificial framebuffer with that color. This method ignores any alpha value of the specified color, so we don't have to extract it.

@Override public void drawPixel(int x, int y, int color) { paint.setColor(color); canvas.drawPoint(x, y, paint);

The drawPixel() method draws a pixel to our artificial framebuffer via the Canvas.drawPoint() method. We first set the color of our paint member variable and pass that to the drawing method in addition to the x- and y-coordinates of the pixel.

^Override public void drawLine(int x, int y, int x2, int y2, int color) { paint.setColor(color); canvas.drawLine(x, y, x2, y2, paint);

The drawLine() method draws the given line to the artificial framebuffer, again using the paint member to specify the color when calling the Canvas.drawLine() method.

^Override public void drawRect(int x, int y, int width, int height, int color) { paint.setColor(color); paint.setStyle(Style.FILL);

canvas.drawRect(x, y, x + width - 1, y + width - 1, paint);

The drawRect() method first sets the Paint member's color and style attributes so that we can draw a filled, colored rectangle. In the actual Canvas.drawRect() call, we then have to transform the x, y, width, and height parameters to the coordinates of the top-left and bottom-right corners of the rectangle. For the top-left corner we simply use the x and y parameters. For the bottom-right-corner coordinates, we add the width and height to x and y and subtract 1. For example, imagine if we were to render a rectangle with an x and y of (10,10) and a width and height of 2 and 2. If we don't subtract 1, the resulting rectangle on the screen would be 3! 3 pixels in size.

^Override public void drawPixmap(Pixmap pixmap, int x, int y, int srcX, int srcY, int srcWidth, int srcHeight) { srcRect.left = srcX; srcRect.top = srcY; srcRect.right = srcX + srcWidth - 1; srcRect.bottom = srcY + srcHeight - 1;

dstRect.right = x + srcWidth - 1; dstRect.bottom = y + srcHeight - 1;

canvas.drawBitmap(((AndroidPixmap) pixmap).bitmap, srcRect, dstRect, null);

The drawPixmap() method, which allows drawing a portion of a Pixmap, first sets up the source and destination Rect members that get used in the actual drawing call. As with drawing a rectangle, we have to translate the x- and y-coordinates together with the width and height to the top-left and bottom-right corners. We again have to subtract 1, or else we'll overshoot by 1 pixel. Next we perform the actual drawing via the Canvas.drawBitmap() method, which will automatically do blending as well if the Pixmap we draw has a PixmapFormat.ARGB4444 or PixmapFormat.ARGB8888 color depth. Note that we have to cast the Pixmap parameter to an AndroidPixmap in order to be able to fetch the bitmap member for drawing with the Canvas. That's a little bit nasty, but we can be sure that the Pixmap instance passed in is actually an AndroidPixmap.

@Override public void drawPixmap(Pixmap pixmap, int x, int y) {

canvas.drawBitmap(((AndroidPixmap)pixmap).bitmap, x, y, null);

The second drawPixmap() method just draws the complete Pixmap to the artificial framebuffer at the given coordinates. We again do some casting to get to the Bitmap member of the AndroidPixmap.

@Override public int getWidth() {

return frameBuffer.getWidth();

@Override public int getHeight() {

return frameBuffer.getHeight();

Finally we have the methods getWidth() and getHeight(), which simply return the size of the artificial framebuffer the AndroidGraphics instance stores and renders to internally.

There's one more class we need to implement related to graphics: AndroidFastRenderView.

0 0

Post a comment