14

Just wondering what the fastest way is to draw a bitmap to canvas?

Currently I have a bitmap (and canvas for drawing) which i use to double buffer drawing calls, and then when i draw to canvas have a scrolling effect by applying a 1px canvas translation. This alone will reduce the framerate from 60+ FPS to ~40, quite a hit. Im not using surfaceView (or GLSurfaceView) at the moment but just wondering if im missing anything that would improve the speed. onDraw() code below

@Override
    public void onDraw(Canvas canvas)
    {
        //update fps text
        mFpsTracker.frameTouch();

        if(mBufferedBitmap == null)
        {
            mBufferedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_4444);
            mBufferedCanvas = new Canvas(mBufferedBitmap);
        }

        paint.setColor(Color.BLUE);
        mBufferedCanvas.drawLine(0, getHeight(), getWidth(), getHeight(), paint);
        mBufferedCanvas.translate(0, -1);


    canvas.drawBitmap(mBufferedBitmap, 0, 0, null); 

    //draw fps
    mTextPaint.setColor(Color.WHITE);
    canvas.drawText(mFpsTracker.getFPSString(), 40, 40, mTextPaint);


    invalidate();       
}
Jonas
  • 121,568
  • 97
  • 310
  • 388
Dori
  • 18,283
  • 17
  • 74
  • 116
  • 2
    in case anyone is intrested using SurfaceView brings this code up by approx 10-15FPS – Dori Aug 01 '11 at 14:13
  • I am puzzled, what exactly is it about SurfaceView that can magically improve performance? Or maybe you are using SurfaceView differently -- for example I'd expect RGB_565 to draw faster on most devices. – Qwertie Apr 18 '12 at 18:24
  • 1
    drawing is done off the UI thread in a SurfaceView http://developer.android.com/reference/android/view/SurfaceView.html – Dori Apr 19 '12 at 10:00
  • Correction, it is *allowed* to be done on a different thread. SurfaceView doesn't require one to start a new thread. Even if it did, introducing new threads doesn't, automatically, guarantee better performance for a given task. – Qwertie Jun 06 '12 at 19:19
  • You're also calling `invalidate()` inside `onDraw()` – TheRealChx101 Oct 16 '15 at 21:00
  • @Qwertie - not saying it guarantees better performance - just reporting observed performance differences. @chx101 - this was many years ago but afaik not using `invalidate()` here meant that `onDraw()` was not called on the next frame. – Dori Oct 17 '15 at 08:19

5 Answers5

6

please see this blog post by Romain Guy.

A video version is available here.

Don't use ARGB_4444 anymore. It is deprecated. Each pixel is only allocated 4 bits per channel (hence the name). ARBG_8888 offers 16,777,216 colors instead of ARBG_4444's 4,096, but uses 4 bytes per pixel instead of 2.

In Gingerbread, Android made ARGB_8888 the standard format for Surface and increased memory allotment per process because of it.

It is more efficient to set your Window's and (assuming you are using streamlined SurfaceView) SurfaceHolder's format to RGBA_8888. This avoids format changes which are noticeably slower.

Other tips include:

  • Limit alpha compositing, as this requires comparatively expensive blending from Skia.
  • Request Bitmap Options that prefer the ARGB_8888 Config and disable dithering.
  • Remove the Window background if possible.
  • Enable hardware acceleration, but beware of unsupported operations.

On a 2.1 device, I am able to draw at least 300 bitmaps on-screen at 50 fps.

Mark
  • 164
  • 2
  • 9
  • +1 thanks. Are your bitmaps the size of the entire canvas as mine was above? – Dori Feb 05 '13 at 13:44
  • No, I broke mine up into resusable parts, but it shouldn't matter too much in terms of speed. Generally the rule of thumb is that you should be able to draw 2.5 times the number of pixels on screen at 60 fps. – Mark Feb 06 '13 at 02:59
  • Sure, i guess without knowing the size or device its hard to quantify the difference in performance - i appreciate the post and tips though. Where is the rule of thumb you mention from? – Dori Feb 06 '13 at 10:07
  • [see here](http://developer.android.com/guide/topics/graphics/hardware-accel.html#tips) Though the 60fps claim is actually from [here](https://plus.google.com/105051985738280261832/posts/2FXDCz8x93s), its case is not strongly substantiated on account of the factual errors and poor writing in the article. – Mark Feb 09 '13 at 13:03
2

I got same problem as yours, please tell me when you found some thing new.

This is what I founded so far:

  1. For android version > 3 better not to use double buffer, because you are getting hardware acceleration(need to set true in manifest)
  2. set paint.setDither(true) it will work better on any device with different color then ARGB_4444, witch are most of the devices. Check this out for more info.
Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216
  • 1
    Hey, you sure setting dithering to true is faster? Docs say "No dithering is generally faster" – Dori May 01 '12 at 10:51
  • I am sure, test it on more then 7 devices. It's no a big different but it helps a bit, and the picture looks better. Check the link in my answer that I added – Ilya Gazman May 01 '12 at 11:31
1

In onSizeChange you can resize or create bitmaps according to the canvas size, then the frame drawing will be much faster about 60fps, however using custom view in an endless loop slows down on some and becomes jumpy on some android devices hence I do not recommend it. Instead, it is better to use SurfaceView.

Check this example: How can I use the animation framework inside the canvas?

Community
  • 1
  • 1
Lumis
  • 21,517
  • 8
  • 63
  • 67
  • +1 thanks for the link. Surely the drawing will only be faster after onSizeChanged called if I was using the wrong size bitmap to start with? – Dori Jul 28 '11 at 13:15
  • this is only because of so many different sizes of screens on android devices, plus honeycomb 3.x is not using full screen as it has a panel on the bottom, so onSizeChanged tells the real size of canvas and one can rezise other images accordingly, but not much to do with the speed you are right. – Lumis Jul 28 '11 at 17:37
  • In your case, I am not sure how big is the bitmap, but a fast way is not to translate the bitmap's canvas but just to draw the bitmap on different X, Y coordinates, or use canvas.drawBitmap(bitmap, fromRect, toRect, paint) and shift rectangles as in the second example. – Lumis Jul 28 '11 at 17:40
0

I think you can get good performance out of a canvas.. but it takes a lot of work..

if you start off with a good performance graphics library, then even if you get a lot of things wrong, you probably will still end up with good performance :) lol

there is a competition running for the fastest drawing library... libgdx is currently winning...

http://code.google.com/p/libgdx/wiki/SimpleApp#Project_Setup

hamish
  • 1,141
  • 1
  • 12
  • 21
0

U need create your bitmap somewhere ( example onCreate or other place (constructor will be good)) because when u do scrolling effect you again and again created new bitmap. So just need create in constructor and than using this bitmap.

it's good solution for me when i have similar problems. try created this mBufferedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_4444); in other place where no invalidate . hope it's help you.

Peter
  • 2,480
  • 6
  • 39
  • 60
  • I am lazily loading my bitmap inside the ondraw() call, so its only created once and then reused...i should have a look in translate to see if anything weird going on in there, or maybe run under ddms to see if any hidden object creation – Dori Jul 28 '11 at 10:18