GV_FiQst GV_FiQst - 4 months ago 21
Java Question

Draw large bitmaps in Android

I don't know whether or not this question was answered. At least I didn't find an answer.

So here is a thing: I'm making some space-themed 2D game on android, and I'm testing it on emulator with screen size = 2560x1600. In this game there is a field where space ship is flying. And of course it (a field) must have a beautiful background with high resolution. My background image's resolution is 4500x4500. I want to make my image move in opposite direction relative to camera movement, so thats why I can't use small static image. At the time only a part of this image is visible:

Example 1

When I tried to draw it I got fps = 1-2 (of course it is low because of the image size):

canvas.drawBitmap(getBigImage(), -x, -y, null);
/* getBigImage() method does nothing but returning
a Bitmap object (no calculation or decoding is performing in there) */


I tried to cut out the needed image from the big one but fps was still low:

Bitmap b = Bitmap.createBitmap(getBigImage(), x, y, sw, sh);
canvas.drawBitmap(b, 0, 0, null);


How can I draw this big bitmap with high fps?

Answer

I was thinking a lot and came up with an idea to divide input bitmap to small chunks and save them to an array, and when I would need to draw that bitmap all I would have to do is to draw visible chunks.

Picture:

enter image description here

Big black rectangle means input bitmap, green rectangle means viewport, red rectangle means visible chunks that are drawn

I've wrote an object that does that all (didn't checked for bugs yet :/). I've tested it and it draws 3000x3000 bitmap with ~45 fps. I'm considering this way as very effective. The object itself may need to be developed more but I think this functionality is enough for my needs. Hope it'll help someone :)

P.S. http://stackoverflow.com/a/25953122/6121671 - used this for inspiration :)

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;

public final class DividedBitmap {
    private final Bitmap[][] mArray;    // array where chunks is stored

    private final int mWidth;           // original (full) width of source image
    private final int mHeight;          // original (full) height of source image
    private final int mChunkWidth;      // default width of a chunk
    private final int mChunkHeight;     // default height of a chunk

    /* Init */

    public DividedBitmap(Bitmap src) {
        this(new Options(src, 100, 100));
    }

    public DividedBitmap(Options options) {
        mArray = divideBitmap(options);

        mWidth = options.source.getWidth();
        mHeight = options.source.getHeight();
        mChunkWidth = options.chunkWidth;
        mChunkHeight = options.chunkHeight;
    }

    /* Getters */

    public int getWidth() {
        return mWidth;
    }

    public int getHeight() {
        return mHeight;
    }

    public Bitmap getChunk(int x, int y) {
        if (mArray.length < x && x > 0 && mArray[x].length < y && y > 0) {
            return mArray[x][y];
        }

        return null;
    }

    /* Methods */

    /**
     *  x, y are viewport coords on the image itself;
     *  w, h are viewport's width and height.
     */
    public void draw(Canvas canvas, int x, int y, int w, int h, Paint paint) {
        if (x >= getWidth() || y >= getHeight() || x + w <= 0 || y + h <= 0)
            return;

        int i1 = x / mChunkWidth;           // i1 and j1 are indices of visible chunk that is
        int j1 = y / mChunkHeight;          // on the top-left corner of the screen
        int i2 = (x + w) / mChunkWidth;     // i2 and j2 are indices of visible chunk that is
        int j2 = (y + h) / mChunkHeight;    // on the right-bottom corner of the screen

        i2 = i2 >= mArray.length ? mArray.length - 1 : i2;
        j2 = j2 >= mArray[i2].length ? mArray[i2].length - 1 : j2;

        int offsetX = x - i1 * mChunkWidth;
        int offsetY = y - j1 * mChunkHeight;

        for (int i = i1; i <= i2; i++) {
            for (int j = j1; j <= j2; j++) {
                canvas.drawBitmap(
                        mArray[i][j],
                        (i - i1) * mChunkWidth - offsetX,
                        (j - j1) * mChunkHeight - offsetY,
                        paint
                );
            }
        }
    }

    /* Static */

    public static Bitmap[][] divideBitmap(Bitmap bitmap) {
        return divideBitmap(new Options(bitmap, 100, 100));
    }

    public static Bitmap[][] divideBitmap(Options options) {
        Bitmap[][] arr = new Bitmap[options.xCount][options.yCount];

        for (int x = 0; x < options.xCount; ++x) {
            for (int y = 0; y < options.yCount; ++y) {
                int w = Math.min(options.chunkWidth, options.source.getWidth() - (x * options.chunkWidth));
                int h = Math.min(options.chunkHeight, options.source.getHeight() - (y * options.chunkHeight));
                arr[x][y] = Bitmap.createBitmap(options.source, x * options.chunkWidth, y * options.chunkHeight, w, h);
            }
        }

        return arr;
    }

    public static final class Options {
        final int chunkWidth;
        final int chunkHeight;
        final int xCount;
        final int yCount;
        final Bitmap source;

        public Options(Bitmap src, int chunkW, int chunkH) {
            chunkWidth = chunkW;
            chunkHeight = chunkH;

            xCount = ((src.getWidth() - 1) / chunkW) + 1;
            yCount = ((src.getHeight() - 1) / chunkH) + 1;

            source = src;
        }

        public Options(int xc, int yc, Bitmap src) {
            xCount = xc;
            yCount = yc;

            chunkWidth = src.getWidth() / xCount;
            chunkHeight = src.getHeight() / yCount;

            source = src;
        }
    }
}