Friday, August 23, 2013

Canvas.save, Canvas.restore; Camera.save, Camera.restore



Android graphics package contains android.graphics.Canvas andandroid.graphics.Camera classes, both of which allow you to manipulate the “Modelview” transformation matrix, with the latter manipulating your drawing in “3D”.

Basically the ability to manipulate complex, hierarchical transformation is achieved by an(?) internal matrix stack with the top matrix being the current one in effect and the one that you are manipulating. All the transformations you are making on the current matrix are cumulative; so if you were just to manipulate the current matrix, it’s going to be a big pain to draw something like a car or a human figure which conceptually is assembled by different parts. Canvas.save()/Camera.save() is to push a matrix into the stack; the result is the transformation you’ve made is saved into the stack. Canvas.restore()/Camera.restore() is to discard the current transformation and go back to the last saved one and continue on it. So if you want to continue with some transformation you made a while ago, you should first save/push it into the stack first so that you can go back to it by restoring/popping it out from the stack. See the following pseudo code for how we go about drawing a drag race car with huge wheels:

//setup/clean up stuff
canvas.save(); // remember where we are since we’d like to go back to this original transformation later;
// move the front wheel to front left
// make it twice as big as the normal wheel
// draw front-left wheel in normal size
canvas.restore(); // go back to the identity matrix;
// move the front wheel to front right
// make it twice as big as the normal wheel
// draw front-right wheel in normal size
canvas.restore();
//… draw body
canvas.restore();
//… and other wheels…

The pattern is
  • If you want to go back to the current state in terms of transformation, you should remember where you are by calling Canvas.save() or Camera.save());
  • Transform the matrix (via Canvas or Camera methods such as Canvas.translate() and Camera.rotateY();
  • Do drawing;
  • Go back to the transformation state you remember in step 1 by calling Canvas.restore()/Camera.restore() if you want to continue from the transformation you left in step 1;
  • In theory, if you only have one matrix transformation saved in the stack, you can simply push it out by calling Canvas.restore()/Camera.restore() again and again. But in the real world (at least applying for cupcake and donut),  I’ve found I have to keep “saving” before each new transformation and “restoring” after it, even if I was “saving”/”restoring” an identity matrix. So this means you would normally call Canvas.save()/Camera.save() each time before a new transformation;
  • Transform the matrix;
  • Do drawing;
  • Canvas.restore()/Camera.restore();
To summarize (applying to android.graphics.Camera as well), you repeatedly do the following cycles:
  1. canvas.save();
  2. transform matrix;
  3. draw;
  4. canvas.restore();


Note that in android, #2 transformation should be coded before #3 drawing to make the drawing stick to the transformation. If you reverse 2/3, your drawing won’t be affected by the transformations that come after it. This is kinda “counter-intuitive” at least for me since you have always need to set your canvas in position before you paint but not the other way around.

Check out the “Red book” for “Manipulating the Matrix Stacks” to get a better understanding. And here is a sample code showing you how I draw the awesome Batmobile (Spiderman’s car) crawling on a skyscraper (use your imagination for the wall and other details as I am still polishing my drawing skill. Please hold on for the next episode!):
package com.mh.android.test;
import android.app.Activity;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
public class HelloAndroidAgain extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyTestView(this));
}
private static class MyTestView extends View {
public MyTestView(Context context) {
super(context);
setMinimumWidth(200);
setMinimumHeight(200);
//camera = new Camera();
}
private Camera camera = new Camera();
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
camera.save();
camera.rotateY(60f);
camera.applyToCanvas(canvas);
canvas.drawColor(Color.DKGRAY);
Paint paint = new Paint();
paint.setTextSize(24);
//front left wheel
canvas.save();
canvas.translate(50f, 50f);//xCtr, yCtr
paint.setColor(Color.GREEN);
canvas.drawCircle(0, 0, 30, paint);
paint.setColor(Color.WHITE);
canvas.drawText(“FL”, -10, 10, paint);//string
canvas.restore();
//front right wheel
canvas.save();
canvas.translate(150f, 50f);
paint.setColor(Color.GREEN);
canvas.drawCircle(0, 0, 30, paint);
paint.setColor(Color.WHITE);
canvas.drawText(“FR”, -10, 10, paint);
canvas.restore();
//rear left wheel
canvas.save();
canvas.translate(50f, 150f);
paint.setColor(Color.GREEN);
canvas.drawCircle(0, 0, 30, paint);
paint.setColor(Color.WHITE);
canvas.drawText(“BL”, -10, 10, paint);
canvas.restore();
//rear right wheel
canvas.save();
canvas.translate(150f, 150f);
paint.setColor(Color.GREEN);
canvas.drawCircle(0, 0, 30, paint);
paint.setColor(Color.WHITE);
canvas.drawText(“BR”, -10, 10, paint);
canvas.restore();
//body
canvas.save();
canvas.translate(100f, 100f);
paint.setColor(Color.RED);
canvas.drawRect(-50, -50, 50, 50, paint);
canvas.restore();
camera.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight());
}
}
}

source: http://maohao.wordpress.com/2009/09/30/canvas-save-canvas-restore/