Save canvas then restore, why is that? - android

I often see the following code
canvas.save().
canvas translate or rotate
some drawing
canvas.restore
I don't understand why we save and then restore. What's the point of undoing what we just did! I am sure I am missing something here
Thanks

I understand this question is a bit dated, but for anyone still looking for an answer I can ELI5;
Imagine the canvas is a piece of paper, and you're tasked with drawing a picture of a robot right side up, at the bottom, and another robot upside down, slightly moved to the right, and about 40% smaller at the top. Something like this;
How would you start? What's easier to do first?
You would probably draw the bigger robot at the bottom first since it's right-side up and it's a lot easier to draw in the direction that feels more natural. So you've got the first one done, now how do you approach the second upside down robot?
You could attempt to draw it as is, but that would be a bit difficult since you're upside down.
or
You could rotate your paper 180°, move your starting point a bit, and start drawing at a smaller scale, and after you're all done you'd just rotate the paper back.
This is what canvas.save() and canvas.restore() do, they allow you to modify your canvas in any way that makes it easier for you to draw what you need. You don't need to use these methods, but they sure do simplify a lot of the process. The above would look something like
drawRobot()
canvas.save()
canvas.rotate(180)
canvas.translate(100, 0)
canvas.scale(40,40)
drawRobot()
canvas.restore()
If we look at the restore() documentation it says
is used to remove all modifications to the matrix/clip state since the last save call
and to see what those modifications are we take a look at save() it says
translate, scale, rotate, skew, concat or clipRect, clipPath
Well look at that, we did in fact use translate rotate and scale but we also did call drawRobot() so wouldn't calling restore erase our drawing? No, because it doesn't affect the drawing, only the modifications. So when we call restore it will return our canvas to the state that it was in before we started the second drawing.

What's the point of undoing what we just did!
You're not, though. If you're just going off the words, it does sound like that's what might happen, but it actually isn't.
Think of it like this:
You have a series of really complex translations and rotations you want to apply in the same onDraw(Canvas) call. Now, since every translation/rotation you apply to the Canvas happens in order, you would have to undo your last adjustments to the Canvas, or somehow calculate your new adjustments based off the previous one before drawing whatever it is you want to draw. That would get very messy, very quickly.
Using canvas.save() and canvas.restore() is a ridiculously easy way to simplify that process.
By doing adjustments that apply to the Canvas within a save/restore block, you're effectively isolating said adjustments so that whatever you want to draw next won't be affected by what you're drawing now.
Now, a little better explanation of the names:
canvas.save() is saying that I want to save the state of the current Canvas's adjustments so that I can go back to it later.
canvas.restore() is saying that I want to revert my Canvas's adjustments back to the last time I called cavas.save()
The beauty of this is in its simplicity. If you already drew whatever it is you wanted to draw during the save/restore block and you no longer need those adjustment's for your next drawing, using this let's you throw away those unnecessary adjustments and return to the state you want to start your next drawing from.
Hopefully that helps explain it!

Working with the canvas involves all manner of translate,scale,rotate,skew procedures on the canvas. The save() method preserves a state before any of the aforementioned augmentation in place, restore() rewinds to a state in time where no augmentation is injected. In other words, you can save a pre state before any transformation of the canvas, do your rotations and whatever else you want to during the process, but when your finished rewind to the state before any augmentation.

I guess the simplest way to put it is:
It removes the change in settings, but not the drawing itself.
Settings can include scaling the canvas etc., and it restores the scaling to the initial state when you called canvas.save().

When you have a background composed of multiple objects, a great way is to save this "static" background and only redraw objects that changed. This saves (processor) time.

Related

incremental drawing

I'm trying to incrementally draw a waveform on a canvas in a separate thread, i.e. in each loop of the thread draw only the portion recently acquired by the hardware without redrawing the whole curve.
I am not sure if I can rely on the canvas' contents retained between SurfaceHolder.unlockCanvasAndPost and SurfaceHolder.lockCanvas.
The documentation seems to be a bit ambiguous on that and my experiments give surprising results: the "old" contents is different for the canvas returned in two subsequent loops of the drawing thread. It looks as if two canvases were used alternately (but I checked - it is the same canvas but with different contents).
The simplest way which I found to reproduce this behaviour:
Import the LunarLander sample applicaton.
In the LunarView.run method insert sleep(500) instruction in the loop.
In the LunarView.doDraw method comment out the line:
canvas.drawBitmap(mBackgroundImage, 0, 0, null);, which erases the old contents.
Run the game and let the ship crash. You will see the intermediate positions of the ship but they will blink: odd and even positions will be shown alternately.
So, my question is: can we draw pictures adding changes only to the contents which has been drawn before and if so - is there any documented way of dealing with the strange behaviour described above?
The lunar landing does not cache.
It draws the background image each time (refreshing) and then draws the spaceship.
private void doDraw(Canvas canvas) {
// Draw the background image. Operations on the Canvas accumulate
// so this is like clearing the screen.
canvas.drawBitmap(mBackgroundImage, 0, 0, null);
Check your If clauses closesly. Maybe one is alternating true and false. This would cause things to draw and then not draw, in rapid succession.
Use Log if need be to output your booleans, to see if one is changing.
Failing this, this the following gaming loop instead.
http://blorb.tumblr.com/post/236799414/simple-java-android-game-loop

Drawing gets slow

I am develop my writing application on my phone.
I need to draw the stroke on the canvas. I need to save all the points in order to redraw.
I found that as the number of points grow. It gets slow.
How ever i need all the points to redraw. Is there a way to save the previous drawing and restore the background for canvas?
Your canvas you are drawing in should be part of a view. Then you should be able to use getDrawingCache.
You need to enable caching too... check this.
Hope that helps.

android canvas - can i have multiple draw methods?

I have made my own canvas class which extends an imageView. My onDraw() method draws out the users gps position and I keep calling this onDraw method every time the user moves. My problem is I also want to draw out a gps trail which only needs to be drawn once (doesnt need to be updated when a user moves). I am wondering is it possible to have more than 1 onDraw method or is there any way of separating 1) the user location and 2) the gps trail??
My reason is I do not want to waste memory by redrawing the gps route everytime the users gps position changes. It is a waste.
Have you seen performance take a hit? If not, don't worry about it. I would think that this would be wasting CPU cycles if anything... not memory. So if the app seems fast enough already, don't worry about optimizing it.
If your app is a bit laggy, and you've found that the trail is the bottleneck... I would suggest caching it into a bitmap. This way, you will still have to draw the trail, but you will not have to calculate the coordinates of the trail on each frame.
I have had to solve a somewhat similar problem recently and I'll explain briefly what I did in case it's of any help.
What you can do is use multiple overlapping Views, where one may contain the background graphics that you don't want to redraw often, and a foreground View that contains the graphics that are frequently updated. Then, to gain performance, you can design the background View's onDraw() so that it is backed by a Bitmap that you then retain as a class variable. In the very first onDraw() of your background graphics, you do the relatively slow drawing to Canvas. In subsequent calls to onDraw(), you simply draw that Bitmap to Canvas.
I've just done this myself. Basically what my application does is display a number of graphical gauges. Those gauges have lots of graphics that are drawn just once (gauge face, number legends), and the pointer graphic that needs to be redrawn over and over as the model data changes. First of all, I split the background graphics and moving foreground graphics into separate overlapping Views. Now, invalidating and redrawing the foreground pointer graphic of course causes anything it overlaps to be invalidated too, so the onDraw() method for the background graphics View is being called each time the pointer View is redrawn. The background View only needs to draw the background graphics once, but retains the Bitmap that backs the canvas, and in subsequent onDraw() calls it draws this bitmap back to Canvas (which is a lot faster than initially creating the graphics using Path() objects).
Bitmap bm;
....
protected void onDraw(Canvas canvas){
if(null==bm){
bm=Bitmap.createBitmap(getMeasuredWidth(),getMeasuredHeight(),Bitmap.Config.ARGB_8888);
// do your slow vector graphics drawing to Canvas here
}
Paint drawPaint = new Paint();
drawPaint.setAntiAlias(false);
drawPaint.setFilterBitmap(false);
drawPaint.setDither(false);
canvas.drawBitmap(bm, 0, 0, drawPaint);
}
Well, there can't be more than 1 onDraw method, assuming that I understood your question correctly. You will need to think about alternate approaches about how to handle this.
#DeeV suggested, that can be a solution for you.

OpenGL ES/Android -- Is there a built-in function to reset the scale, translation, rotation of a object?

My program draws an object, then translates, rotates, and scales it, then redraws it, etc etc.
To set the translation I would do:
gl.glTranslatef(2,4,666);
then to clear it gl.glTranslatef(-2,-4,-666);
I'm wondering if there's a built in function to do so?
glPushMatrix() and glPopMatrix() are the normal ways to do this. Push before applying glTranslate, pop when done and it will revert the stack. You have to remember, OpenGL is a state based system that uses a stack. When you apply glTranslatef, you are adding the translate function to the stack, so anything drawn after it is placed on the stack will have that translation done to it. Calling
gl.glTranslatef(2,4,666);
and then
gl.glTranslatef(-2,-4,-666);
if I understand it correctly, will cause the scene to first move your object to (-2,-4,-666), then back (2,4,666). Because it is a stack, the last transformation you apply gets applied first, and the first is last. It helps to remember that little fact while you're setting up your scene. Just put a push before gl.glTranslatef(2,4,666);, and a pop after and you should be good.
glPushMatrix();
gl.glTranslatef(2,4,666);
//draw code here
glPopMatrix();
Just remember the whole stack thing and you should be able to think through any problem areas.
Just reset your matrix by using glLoadIdentity().
Another alternative (if you're having a lot of objects that needs their own matrix) is to make your object's drawings on a matrix copy. In order to accomplish that you need to use glPushMatrix() and glPopMatrix().
glLoadIdentity()

How to animate in Android Canvas?

I want to draw something at about 30 frames per seconds on Android Canvas or other convenient object for this purpose. In my application different graphic objects are drawn and if any of the graphic object is touched, the graphic object changes its shape. I looked at the
onDraw(Canvas canvas) callback of View subclass but calling invalidate() does not help here: first I cannot control the frame rate and second if the objects are moving too fast, the motion appears jerky.
I personally dislike Android's built-in Animation classes, so I tend to do all animations with Canvas by hand. I have found the most luck with creating a list of the images you want to use in your animation and then an int variable to store the current "frame" you are on. To advance the frame, I create a thread that sleeps for, say, 30 ms and then update the frame variable accordingly. Then in whatever update handler you are using, you can just create a switch statement or something of the like, and draw the respective frame.
It may seem like a lot of work, but it really isn't. Shove it all into a class and you will love yourself for many animations to come.

Categories

Resources