4

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?

GV_FiQst
  • 1,487
  • 14
  • 30

2 Answers2

3

Try

drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)

which takes a rectangle of pixels from the source image to display in a rectangle on the Canvas. I found this to be faster back when I did a scrolling game.

Craig C.
  • 166
  • 1
  • 1
  • 7
  • Does it really cuts out the src rect from the input image or just resizes it? And what is happenning when src.x + src.width > bitmap.width? No exceptions are thrown in that case? – GV_FiQst Jul 29 '16 at 06:26
  • It simply draws the image in the source rectangle to the canvas rectangle. It doesn't delete the pixels in the source bitmap. If the source rectangle is larger than the source bitmap then yes I'm sure there would be an exception. Give it a quick try to see if it can improve your FPS at all. – Craig C. Jul 29 '16 at 08:55
  • I tested it like this: `Rect rect = new Rect(0, 0, sw, sh); Bitmap b = Data.Images.getImage(R.drawable.neb_1); canvas.drawBitmap(b, rect, rect, null);` Fps was still low (1-2). I don't know maybe there is some tricks or something to draw very large images. – GV_FiQst Jul 29 '16 at 13:05
  • Try using a background image that is the same as the screen size, 2560 x 1600, and see what frame rate you get then. If it is still low then maybe the emulator is underperforming. – Craig C. Jul 29 '16 at 17:05
  • Using Renderscript you could get a multiple of that speed. See http://stackoverflow.com/a/35877894/5148048 for inspiration. – Settembrini Jul 31 '16 at 19:43
  • That is very interesting. My only other suggestion would be to try OpenGL to do your graphics, but it is way more complex and I can't guarantee an increase in performance as I haven't tried it myself. – Craig C. Aug 01 '16 at 20:46
  • I was thinking a lot and came up with idea to write an object that would divide a big bitmap to a small chunks when decoded and then save them to an array. So when I want to draw it I just have to take a visible chunks from that array and draw them. I hope it helps. The only one thing that I'm not sure about is a memory needed to store all of the chunks. Is it a good way? – GV_FiQst Aug 02 '16 at 12:49
2

I was thinking a lot and came up with an idea to divide input bitmap to small chunks and save them to an array. So now to draw that bitmap all I 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 (I didn't check it 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. https://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;
        }
    }
}
Community
  • 1
  • 1
GV_FiQst
  • 1,487
  • 14
  • 30