Optimizations of custom ImageView that frequently refreshes by calling onDraw - android

I have created a custom ImageView and in its onDraw method I need to draw some bitmaps based on user interaction like touch. Everything is working fine however slowly as I start adding more and more bitmap the application really slows down.
This is what I do in my onDraw of the Custom ImageView
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e(TAG, "onDraw called");
for (int i=0; i < bitmapList.size(); i++){
drawBitmap(canvas, bitmapList.get(i));
}
}
As you can see, I am redrawing all the bitmaps in the List everytime onDraw is called naturally when the number of bitmap exceeds say 4-5 the operation becomes very expensive and slows the application down.
Any solution to this proble as to how can this be optimized?
Can calling drawBitmap in a different thread make the operation less expensive?
Is there a way to keep a copy of the previous canvas and then simply restore it in onDraw rather than drawing all the bitmaps again?
The question essentially is refreshing the View with lots of dynamic images on it and its optimization.

You should create a Bitmap which size must be equal to the ImageView's image size and draw all the bitmaps from the bitmapList on this bitmap only once. On every onDraw() call you should draw only this bitmap. When the bitmapList changes, this additional bitmap must be recreated.

You could use a backbuffer image to draw all your images into whenever they change, and then draw only the backbuffer image to the screen in onDraw()
// create backbuffer and draw it as seldom as possible
final Bitmap bufferBitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888);
final Canvas bufferCanvas = new Canvas(bufferBitmap);
for (int i=0; i < bitmapList.size(); i++){
drawBitmap(bufferCanvas, bitmapList.get(i));
}
// draw the buffered image as often as needed
canvas.drawBitmap(bufferBitmap ...);

Related

Recycle bitmaps made with createScaledBitmap

I wrote an example code to generically show my issue.
Basically, inside the onDraw of a View, I want to create a scaled up bitmap, draw it on canvas, recyle that bitmap and free its memory, and do it again and again, in a loop.
The problem is that memory grows and grows at each step of the loop, and remains full even after that onDraw has finished, even after waiting some time.
As far as my logic goes, canvas already contains a bitmap sized like the screen, so it should not grow in memory size whatever I draw into it, and I suppose each of the scaled up bitmaps remains in memory even though i called recycle on them and even though their reference is destroyed.
Where am I wrong?
Thanks!
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
int i=0;
while(i<10) {
matrix = new Matrix();
bitmapToDraw = Bitmap.createScaledBitmap(bitmap, canvas.getWidth(), canvas.getHeight(), false);
canvas.drawBitmap(bitmapToDraw, matrix, imagePaintF);
bitmapToDraw.recycle();
bitmapToDraw = null;
i++;
}
super.onDraw(canvas);
}
Just by repeating the loop 10 times, memory grows to -and always remains at- almost 200MB.
If I do it more and more, it goes outOfMemory. While by recycling bitmaps, I should be able to do it without a limit.

Android: drawing on large bitmap, 2 layers, best way?

im working on an app, that displays large(around 2000x2000px) bitmap in imageview. This image has to be that large since user can pinch to zoom it in order to see some details. App has to be able to draw circles on that image, and also to display image alone, without circles on it. I was using 2 layers but the problem is memory since 2k x 2k px is around 16mb of memory, and creating another bitmap(another 16mb), just to draw a few circles, is pointless in my opinion. Is there any way, that you can draw simple primitives on image, and also be able to display it without primitives(circles in my case)?
Maybe somehow to store only modified pixels or sth?
Thanks!
You don't need to make another 2000x2000 Bitmap to draw those circles on. Just 'prerender' a circle, and then choose where you draw it.
I'm working under the assumption that you're drawing your 'big' image on a Canvas, since you have zooming features etc.
If you're not, you'll need to override your SurfaceView's onDraw(Canvas canvas) method so that you can access the SurfaceView Canvas. I won't go into depth about that part since again I'm assuming you have it, but if not the implementation of that function would look like this:
//Overriding SurfaceView onDraw(Canvas canvas)
#Override
protected void onDraw(Canvas surfaceCanvas) {
if(canvas == null) return; //No Canvas? No point in drawing then.
surfaceCanvas.drawColor(Color.BLACK);
//Draw your 'big' image on the SurfaceView Canvas
insertYourBigImageDrawingFunctionHere(surfaceCanvas);
//Now draw your circles at their correct positions...
insertCircleDrawingFunctionHere(surfaceCanvas);
}
Now that you have access to the SurfaceView Canvas, you can choose precisely how things are drawn on it. Like circles for example...
I want to draw your attention to the multiple Canvas' being used below (surfaceCanvas vs. circleCanvas). I once thought that Canvas was a kind-of 'one Canvas for the whole app/activity' implementation, but it isn't. You are free to create Canvas' as you please. It is merely an instance of a tool to draw onto Bitmaps. This was a HUGE revelation for me, and gave me much more robust control over how Bitmaps are composed.
public void myCircleDrawingFunction(Canvas surfaceCanvas){
//Make a new Bitmap for your circle
Bitmap.Config conf = Bitmap.Config.ARGB_4444;
tinyCircleBMP = Bitmap.createBitmap(10,10, conf);
//Make a new canvas using that Bitmap as the source...
Canvas circleCanvas = new Canvas(cacheBmp);
//Now, perform your drawing on the `Canvas`...
Paint p = new Paint();
circleCanvas.drawCircle(5, 5, 5, p);
//Now the `Bitmap` has a circle on it, draw the `Bitmap` on the `SufaceView Canvas`
surfaceCanvas.drawBitmap(tinyCircleBMP, 10, 10, p);
//Replace the '10's in the above function with relevant coordinates.
}
Now obviously, your circles will zoom/pan differently to your 'big' image, since they are no longer being drawn at the same size/position of the 'big' image. You will need to consider how to translate the positions of each circle taking into account the current scale and position of the 'big' image.
For example, if your image is zoomed in to 200%, and a circle is supposed to appear 100px from the left of the big image, then you should multiply the pixel values to take into account the zoom, like this
(PsuedoCode):
drawCircleAtX = Bitmap.left * BitmapZoomFactor
If you are using the canvas API (if not I would suggest to)? if so you are just draw your image on the canvas and then the primitive shapes on top of the same canvas before display. This way you just keep a reference of the circles position in some basic data types and scale them as the user moves around and zooms, so you know where to draw them each frame.

Loading images to specific coordinates in SCanvasView

I am creating an android application and am working with canvas bitmaps (specifically the Samsung SPen SDK). I currently have the functionality I want working working, but the way the loading is implemented makes the run-time too long.
What I have:
Saving: I am saving the canvas as a bitmap and splitting this bitmap into X smaller bitmaps of a specific size (saving only the sections with writing in them). There is no problem with the efficiency of this section.
Loading::
I then take the X number of smaller bitmaps and combine them to reform the original bitmap using canvas.drawBitmap(imgs[x][y], xCoordinate, yCoordinate, null) for all of the images that have writing in them(imgs[][] = array of bitmaps).
I then display this in the foreground of the canvas using the setclearImageBitmap(BitmapName) function in the SCanvasView class. This works fine besides the fact that loading the one large image into the foreground is taking ~90% of the run time.
private void loadCanvasImage(){
Bitmap bmOverlay = Bitmap.createBitmap(mSCanvas.getWidth(), mSCanvas.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmOverlay);
int yCo = 0;
for(int yCount=0; yCount
}
Question:
I am wondering if there is a more efficient way to do this. Potentially by implementing this idea:
By adding multiple bitmaps to the canvas view (preferably foreground) at specific x,y coordinates (This may make it faster since the smaller bitmaps without writing on them will not have to be displayed).
Another method I failed to implement was to set the SCanvasView to the canvas containing the one large bitmap that I recreated. I did this by trying to override the onDraw(Canvas) method, and passing the canvas result of canvas.drawBitmap(imgs[x][y], xCoordinate, yCoordinate, null)

Android: How to get a custom view to redraw partially?

I have a custom view that fills my entire screen. (A piano keyboard)
When a user touches the key, it causes invalidate() to be called and the whole keyboard gets redrawn to show the new state with a touched key.
Currently the view is very simple, but I plan to add a bit more nice graphics. Since the whole keyboard is dynamically rendered this would make redrawing the entire keyboard more expensive.
So I thought, let's look into partial redrawing. Now I call invalidate(Rect dirty) with the correct dirty region. I set my onDraw(Canvas canvas) method to only draw the keys in the dirty region if I do indeed want a partial redraw. This results in those keys being drawn, but the rest of the keyboard is totally black/not drawn at all.
Am I wrong in expecting that calling invalidate(Rect dirty) would "cache" the current canvas, and only "allows" drawing in the dirty region?
Is there any way I can achieve what I want? (A way to "cache" the canvas and only redraw the dirty area?"
Current nice workaround is to manually cache the full canvas to a bitmap:
private void onDraw(Canvas canvas)
{
if (!initialDrawingIsPerformed)
{
this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Config.ARGB_8888); //Change to lower bitmap config if possible.
Canvas cacheCanvas = new Canvas(this.cachedBitmap);
doInitialDrawing(cacheCanvas);
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
initialDrawingIsPerformed = true;
}
else
{
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
doPartialRedraws(canvas);
}
}
Ofcourse, you need to store the info about what to redraw yourself and preferably not use a new Paint everytime, but that are details.
Also note: Bitmaps are quite heavy on the memory usage of your app. I had crashes when I cached a View that was used with a scroller and that was like 5 times the height of the device, since it used > 10MB memory!
To complement Peterdk's answer, you could save your operations in a Picture instead of a Bitmap.
A Bitmap will save all pixels, like
he said it could take a lot of
memory.
A Picture will save the
calls, like drawRect, drawLine, etc.
It depends of what is really heavy in your application : a lot of draw operations, a few draw operations but controlled by heavy calculations, a lot of blank/unused space (prefer Picture) etc...

How to blit() in android?

I'm used to handle graphics with old-school libraries (allegro, GD, pygame), where if I want to copy a part of a bitmap into another... I just use blit.
I'm trying to figure out how to do that in android, and I got very confused.
So... we have these Canvas that are write-only, and Bitmaps that are read-only? It seems too stupid to be real, there must be something I'm missing, but I really can't figure it out.
edit: to be more precise... if bitmaps are read only, and canvas are write only, I can't blit A into B, and then B into C?
The code to copy one bitmap into another is like this:
Rect src = new Rect(0, 0, 50, 50);
Rect dst = new Rect(50, 50, 200, 200);
canvas.drawBitmap(originalBitmap, src, dst, null);
That specifies that you want to copy the top left corner (50x50) of a bitmap, and then stretch that into a 150x150 Bitmap and write it 50px offset from the top left corner of your canvas.
You can trigger drawing via invalidate() but I recommend using a SurfaceView if you're doing animation. The problem with invalidate is that it only draws once the thread goes idle, so you can't use it in a loop - it would only draw the last frame. Here are some links to other questions I've answered about graphics, they might be of use to explain what I mean.
How to draw a rectangle (empty or filled, and a few other options)
How to create a custom SurfaceView for animation
Links to the code for an app with randomly bouncing balls on the screen, also including touch control
Some more info about SurfaceView versus Invalidate()
Some difficulties with manually rotating things
In response to the comments, here is more information:
If you get the Canvas from a SurfaceHolder.lockCanvas() then I don't think you can copy the residual data that was in it into a Bitmap. But that's not what that control is for - you only use than when you've sorted everything out and you're ready to draw.
What you want to do is create a canvas that draws into a bitmap using
Canvas canvas = new Canvas(yourBitmap)
You can then do whatever transformations and drawing ops you want. yourBitmap will contain all the newest information. Then you use the surface holder like so:
Canvas someOtherCanvas = surfaceHolder.lockCanvas()
someOtherCanvas.drawBitmap(yourBitmap, ....)
That way you've always got yourBitmap which has whatever information in it you're trying to preserve.
In android you draw to the canvas, and when you want it to update you call invalidate which will the redraw this canvas to the screen. So I'm guessing you have overridden the onDraw method of your view so just add invalidate();
#Override
public void onDraw(Canvas canvas) {
// Draw a bitmap to the canvas at 0,0
canvas.drawBitmap(mBitmap, 0, 0, null);
// Add in your drawing functions here
super.onDraw(canvas);
// Call invalidate to draw to screen
invalidate();
}
The above code simply redraws the bitmap constantly, of course you want to add in extra thing to draw and consider using a timing function that calls invalidate so that it is not constantly running. I'd advice having a look at the lunarlander sources.

Categories

Resources