Android, fastest way to draw a bitmap to canvas - android

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();
}

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.

I got same problem as yours, please tell me when you found some thing new.
This is what I founded so far:
For android version > 3 better not to use double buffer, because you are getting hardware acceleration(need to set true in manifest)
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.

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?

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.

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

Related

Android: Low FPS drawing many bitmaps in a surfaceview

I'm trying to create a bullethell game and Ive run into a bit of a trouble. I can't get more than 17 fps after about 500 bullets. The update logic code takes around 1-4ms for all of them while the render code takes around 40ms
For now my code is
private void drawEntities(Canvas canvas) {
for (HashMap<UUID, Spatial> h: spatialList) {
for (Spatial spatial: h.values()) {
spatial.render(canvas);
if(spatial.life > 0)
spatial.life--;
else if (spatial.life == 0)
engine.deleteEntity(spatial.owner);
}
}
}
spatialList is an arrayList where each index is a zLevel
The spatial which displays the actual bullet is
public void render(Canvas canvas) {
float angle = (float) (vel.getAngle() * (180 / Math.PI));
matrix.reset();
matrix.setTranslate(pos.x - bullet.getWidth() / 2, pos.y - bullet.getHeight() / 2);
matrix.postRotate(angle + 90, pos.x, pos.y);
canvas.drawBitmap(bullet, matrix, paint);
canvas.drawCircle(pos.x, pos.y, col.getRadius(), paint);
}
I can provide more code but these seem to be the main issue. I've tried everything I can think of and can't find much else online. The only thing I can think of to fix this is to switch from a surfaceview to a GLSurfaceview but I really think there is a better way and I'm just using bad code.
Edit: I noticed my timer was off and removed the drawcircle and after running it again I get 40ms~ around 500 which is still a bit too low for reasonable performance.
TLDR; 500 entities = 17 fps.
You may be limited by pixel fill rate. How large (in pixels) is the display on your test device?
One simple thing to play with is to use setFixedSize() to reduce the size of the SurfaceView's Surface. That will reduce the number of pixels you're touching. Example here, video here, blog post here.
It's generally a good idea to do this, as newer devices seem to be racing toward absurd pixel counts. A full-screen game that performs all rendering in software is going to struggle on a 2560x1440 display, and flail badly at 4K. "Limiting" the game to 1080p and letting the display scaler do the heavy lifting should help. Depending on the nature of the game you could set the resolution even lower with no apparent loss of quality.
Another thing to try is to eliminate the drawBitmap() call, check your timings, then restore it and eliminate the drawCircle() call, to see if one or the other is chewing up the bulk of the time.
You may find switching to OpenGL ES not so bad. The "hardware scaler exerciser" activity in Grafika (from the video linked above) shows some simple bitmap rendering. Replace drawCircle() with a scaled bitmap and you might be most of the way done. (Note it uses SurfaceView, not GLSurfaceView, for GLES in that activity.)
I had the same issue. The problem will be solved to a large extent if you do all the drawing on a bitmap framebuffer and then draw the framebuffer to the canvas. If you are directly drawing on the canvas then there are several overheads. I looked around and found this tutorial
"http://www.kilobolt.com/day-6-the-android-game-framework-part-ii.html"
Look at the implementation for AndroidGraphics and AndroidFastRenderView and see how he uses AndroidGraphics to do all the actual drawing in a buffer and paints that buffer to the canvas in AndroidFastRenderView.
Try the performance when you remove the rotate and/or drawCircle parts from your render() method. Both of them might be quite time consuming as they probably contain sin() / cos() calculations.
If that helps, you'll have to figure out how to replace them with something faster, if not, well...

How to efficiently display images in Android

I'm making a simple 2d game in android. However, even with only a few images on screen, I'm running into problems with fps(its around 15-25 and quite stuttery).
Currently I use canvas, and simply set up an image each frame with everything I need to display on it.
Here's some of my render code:
public void render(Painter g) {
g.setColor(Color.rgb(208, 244, 247));
g.fillRect(0, 0, GameMainActivity.GAME_WIDTH, GameMainActivity.GAME_HEIGHT);
g.setColor(Color.rgb(139,69,19));
g.fillRect(0, 1400, GameMainActivity.GAME_WIDTH, GameMainActivity.GAME_HEIGHT - 1400);
g.drawImage(Assets.background3stone, currentBackground3X, 141);
g.drawImage(Assets.background3stone, currentBackground3X + 3459, 141);
g.drawImage(Assets.background2stone, currentBackground2X, 141);
g.drawImage(Assets.background2stone, currentBackground2X + 3459, 141);
g.drawImage(Assets.ground, currentBackground1X, 760);
g.drawImage(Assets.ground, currentBackground1X + 3000, 760);
renderNews(g);
renderButtons(g);
g.drawImage(Assets.leaderboard, 40, 180, 148, 148);
g.drawImage(Assets.achievements, 40, 354, 148, 148);
g.drawString("FPS: " + frameValue, 50, 200);
}
This code runs for every iteration of the game loop.
Does anyone know any ways to optimize my performance? Currently I redraw literally everything every frame, is there a way to not redraw static images? Would switching to openGl help a lot?
Thanks!
There's also a lot of good and efficient game engines available for Android (For example LibGDX). Game engines are usually well optimized for showing, moving and animating multiple images and they usually come with plenty other useful features.
However, if you prefer not to use game engines, I'd recommend using OpenGL to get some boost. You may also get some boost by optimizing your images. Try to reduce the sizes of images and don't use too large images. Also try to load images efficiently to the program.
Switching to openGL would always help a lot. But your problem is that you're drawing directly to the screen. Don't do that. Draw to a bitmap, then blit the bitmap to the screen in a single operation. The performance of that will be MUCH higher. It also would allow you to do your drawing on a 2nd thread and only draw to the screen on the main one.
Before switching to openGL you may want to try to not redraw the image itself, but just to move it. You just need to set new LayoutParams to your image. You may also want to look at TranslateAnimation in case you are moving stuff softly. Or you use a matrix transformation.

Creating/Importing many bitmaps to use for maptiles stack memory issues

I'm pretty new to Android, but I wanted to try and make a tile based game, where you would move around a map, square by square.
However I'm having problems with opening many resources, for now i'm just using the same image for each tile (but in future this will obviously change)
The problem is when creating a decently sized map this code:
mBitmap = BitmapFactory.decodeStream(new BufferedInputStream(res.openRawResource(R.drawable.tile)));
mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight());
canvas.drawBitmap(mBitmap, mX, mY, null);
is causing an error when emulated...
I understand that emulators/phones have limited memory, but I thought they would have enough to hold say 20x20 tiles?
-I was hoping to have much bigger maps, but the only workaround I could think of is destroying tiles once they are out of view and drawing new ones as they come into view (as you move)
I've tried to do that here:
if (tile.mX > 100 && tile.mX < 600){
tile.mBitmap = null;
} else {
//draw it with code above
}
So the code above would loop through each tile in a array (every frame) and only draw the ones within a certain X co-ordinates.
I was just wondering if this was the 'way' to do it? -opening and nulling bitmaps whenever you move? Or is there is simpler way? Have I missed a command which would destroy the bitmap better than null which would make my program more efficient?
The programs runs EXTREMELY slow on the emulator, is this a problem? -Or is it safe to assume loading many bitmaps each frame would be slow on an emulator? Will it still run slow on phone devices?
Any tips/help would be great, thanks!
I don't think you must load/free the bitmaps every time. I also wrote a program that's handling a lot of (large) bitmaps and we had lots of memory problems until I stumbled on the LruCache class provided by the Android API (the linked JavaDoc even has an example on how to use it for bitmaps). LRU stands for Least Recently Used, so basically it keeps elements used shortly and removes elements not used for a long time.
So what you should do is this: You'll paint your playfield using a SurfaceView - this means you'll have to paint it every time the draw method is called. So when drawing, you'll have to know which tiles are visible and you just paint those tiles. For every tile you look up the LruCache to see if you've loaded that bitmap already and if so, simply paint it on the correct position of the surface view. If the bitmap is not in the cache you load it, put it into the cache and paint it onto the surface.

Drawing rotated Bitmaps on Canvas is slow: any other options?

I'm developing a live wallpaper app which will draw an analog clock. So far I reached the point where I draw a clock using three bitmaps: dial, hour hand, minute hand.
When I draw hours and minutes I do it like this (pseudo code):
// without this it looks weird after rotate
Paint smoothPaint = new Paint(FILTER_BITMAP_FLAG);
canvas.drawBitmap(dialBitmap, 0, 0, null);
canvas.rotate(ANGLE_FOR_HOUR_HAND, w/2, h/2);
canvas.drawBitmap(hourBitmap, 0, 0, smoothPaint);
canvas.rotate(ANGLE_FOR_MINUTE_HAND, w/2, h/2);
canvas.drawBitmap(minuteBitmap, 0, 0, smoothPaint);
This works okay except that I'm getting a really low frame rate: about 13 fps.
If I turn off bitmap filtering, it increases to about 24 fps, but still low for me.
I know that 2d drawing on Canvas is not accelerated in any way, but still want to ask are there any other options for me to speed up this code? Drawing to bitmap natively somehow? Something else maybe?
Using OpenGL is an option, but its not that simple since LiveWallpapers do not support GL drawing, so I need to use 3rd party hacks (which I know exist) for that which I would rather not do...
"Any other options?" you ask. You have two:
Don't use bitmaps. The second Cube SDK sample, for example, pops out a rotating 20-line dodecahedron at 25 fps.
As you've mentioned, you can sometimes use a "hack" for special-purpose graphics within live wallpaper. I suggest you at least take a look at the following three...you may find them useful one day: AndEngine (has a live wallpaper extension), LibGDX (allows you to prototype in a desktop environment; live wallpaper facilities still under development, methinks), and GLWallpaperService (designed specifically to bring GLSurfaceView to live wallpaper).
In your post you say that 24 fps is "still low" for you. It is my understanding that 24 fps is approximately the limit of human perception (it's what Flash uses by default, anyway). I suggest you go no higher, even if you can, in order to preserve battery life.
Edit: One other "hack" to consider: renderscript. That's how some of the sophisticated wallpapers (Galaxy, Grass, Water) that come with many phones work. See renderscript enabling java files here: https://android.googlesource.com/platform/packages/wallpapers/Basic/+/master/src/com/android/wallpaper
How about rendering your hand configuration to an intermediate bitmap each time the minute-hand changes, and then rendering that static bitmap to your canvas on each frame?
Found few things that helped to increase frame rate, will post here as an answer to myself :)
First is to use Canvas.drawBitmap(Bitmap, Matrix, Paint) overload to supply the matrix, instead of using canvas.save() + canvas.rotate() + canvas.restore() - more efficient.
Second: reducing the size of bitmaps. Earlier they were padded to near screen-size of hdpi (480pix) with transparent pixels added around them. After I cropped these transparent pixels away and modified the rendering code to account for that, rotating became much more efficient (as expected).

Get Pixel Values from Android Canvas

I'm currently writing an Android game using surfaceView. I've optimized the game as much as possible and it runs quite smoothly. However, I have collision detection incorporated which is a bit messy. I would like to do collision detection by reading pixels directly from the canvas. Is this possible to do? The closest to this that I have found was to attach a new bitmap to the canvas using setBitmap. Then when I drew to the canvas, the bitmap would be updated. Would this be the way to go? Thanks.
Although you should manage the collision detection in a different way, you can get the pixel color on a given position with the next line of code:
mView.mBitmap.getPixel(j, i)
mView is the View that contains your canvas and mBitmap is the Bitmap with which you created your canvas:
Canvas mCanvas = new Canvas(mBitmap);
From the Canvas API:
The Canvas class holds the "draw"
calls. To draw something, you need 4
basic components: A Bitmap to hold the
pixels, a Canvas to host the draw
calls (writing into the bitmap), a
drawing primitive (e.g. Rect, Path,
text, Bitmap), and a paint (to
describe the colors and styles for the
drawing).
So you wouldn't ever ask a canvas for pixel data, because the canvas itself doesn't really "own" pixel data. The canvas which you use to make draw calls is always attached to a bitmap (the one you're drawing on), so that bitmap is where you should get your pixel data from.
Collision detection is usually costly, but going to a bitmap-based process could make it even worse, depending on what you're trying to do. Just a heads up.
I agree with Josh. If precision is that important in your app, you may want to incorporate screen size/resolution data into some kind of physics engine. Trying to build a physics engine based entirely on visual processing is probably unnecessarily costly.

Categories

Resources