Lesson 146. Drawing. Canvas conversion. Save and restore methods.

Lesson 146. Drawing. Canvas conversion. Save and restore methods.


In this lesson:

– we use the canvas matrix for transformations

Canvas has its own matrix that will work for any object you are about to draw. The methods for adjusting this matrix are already known to us from Lesson 144:

– translate (Moving)
– scale (Resize)
– rotate (Turn)
– skew (Incline)

In canvas, these methods are pre-methods. That is, they place the transform at the beginning of the matrix, keeping the others.

Consider this as an example.

transformation

Let’s create a project:

Project name: P1461_CanvasTransform
Build Target: Android 2.3.3
Application name: CanvasTransform
Package name: ru.startandroid.develop.p1461canvastransform
Create Activity: MainActivity

MainActivity.java:

package ru.startandroid.develop.p1461canvastransform;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new DrawView(this));
  }

  class DrawView extends View {

    Paint p;
    Matrix matrix;
    RectF rectf;
    Path path;

    public DrawView(Context context) {
      super(context);
      p = new Paint();
      p.setStrokeWidth(3);
      p.setStyle(Paint.Style.STROKE);
      rectf = new RectF(100, 100, 200, 200);
      matrix = new Matrix();
      path = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);
      
      // квадрат
      path.reset();
      path.addRect(rectf, Path.Direction.CW);
      p.setColor(Color.BLACK);
      canvas.drawPath(path, p);
      
      // преобразованный квадрат
      matrix.reset();
      matrix.preRotate(30);
      matrix.preTranslate(500, 0);
      path.transform(matrix);
      p.setColor(Color.BLUE);
      canvas.drawPath(path, p);
    }
  }
}

We draw a black color path with a rectangle. Then we adjust the matrix to rotate 30 degrees to the point (0,0) (because not specified otherwise) and to move 500 to the right. Because we use pre methods, there will be a move first, then a rotation. We will transform path and display in blue.

result:

Let’s try to do the same with the help of an outline.

rewrite onDraw:

    @Override
    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);
      
      // квадрат
      p.setColor(Color.BLACK);
      canvas.drawRect(rectf, p);
      
      // квадрат на канве с преобразованиями
      canvas.rotate(30);
      canvas.translate(500, 000);
      p.setColor(Color.GREEN);
      canvas.drawRect(rectf, p);
      
    }

First, we output the rectf rectangle in black. Then we adjust the canvas matrix. We set the same transformations in the same order. Since these methods are analogues of pre-methods, then the move is performed first, then the rotation.

The canvas matrix is ​​configured, now all the objects we draw on the canvas will be converted according to its matrix. Draw the same rectf rectangle in green.

Note that we will not convert rectf in any way. It stays with the same coordinates. Canvas itself turns it into a drawing.

result:

The green rectangle is where it was blue last time. That is, the transformations using a separate matrix are identical to the transformations of the canvas matrix.

Saving and restoring status

We can memorize the state of the canvas matrix and then restore it.

Consider this with examples. rewrite DrawView:

class DrawView extends View {
    
    Paint p;
    RectF rectf1;
    RectF rectf2;

    public DrawView(Context context) {
      super(context);
      p = new Paint();
      p.setStrokeWidth(3);
      p.setStyle(Paint.Style.STROKE);
      rectf1 = new RectF(50,50,100,100);
      rectf2 = new RectF(50,150,100,200);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);
      
      // зеленый квадрат
      p.setColor(Color.GREEN);    
      canvas.drawRect(rectf1, p);

      // синий квадрат
      p.setColor(Color.BLUE);
      canvas.drawRect(rectf2, p);
      
    }
  }

Draw a couple of squares, the first in green and the second in blue.

Without any transformation, they look like this:

rewrite onDraw:

    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);
      
      // зеленый квадрат
      p.setColor(Color.GREEN);    
      canvas.drawRect(rectf1, p);
      
      // преобразования канвы
      // и рисование зеленых квадратов
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // сброс канвы
      canvas.restore();
      
      // синий квадрат
      p.setColor(Color.BLUE);
      canvas.drawRect(rectf2, p);
      
    }

Multiple times adjust the movement 100 to the right and each time we draw the first square in green. Then reset the canvas matrix using the restore method to its original state. And we draw the second square in blue.

result:

We see that the first (green) square was drawn with the offset according to the canvas settings, and the restore method reset all these conversion settings and the second (blue) square was drawn without transformations.

You can configure the canvas so that the restore method will reset its setting not to its original state, but between the state saved by us.

rewrite onDraw:

    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);

      // зеленый квадрат
      p.setColor(Color.GREEN);
      canvas.drawRect(rectf1, p);

      // преобразования канвы
      // и рисование зеленых квадратов
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // сохраняем настройки матрицы канвы
      canvas.save();

      // преобразования канвы
      // и рисование красных квадратов
      p.setColor(Color.RED);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // возврат канвы к предыдущему сохранению 
      canvas.restore();

      // синий квадрат
      p.setColor(Color.BLUE);
      canvas.drawRect(rectf2, p);

    }

Again, several times we adjust the movement 100 to the right and each time we draw the first square in green. Then for the canvas we perform the save method. It will remember the current settings of the matrix. Next, a couple of times we move to the right 100 and draw the first square, but already in red, to visually distinguish the output before and after saving the canvas.

Then we perform the restore method, which will reset the canvas to a state that was saved by the save method. And we draw the second square in blue.

result:

We see that the second (blue) square is drawn with the state of the canvas that was saved by the save method. The restore method brought us back to its current state.

Save using the save method does not erase the previously saved state. That is, you can call save several times and all of these states will be stored in some stack. And by the method of restore to extract from this stack.

rewrite onDraw:

    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);

      // зеленый квадрат
      p.setColor(Color.GREEN);
      canvas.drawRect(rectf1, p);

      // преобразования канвы
      // и рисование зеленых квадратов
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // сохраняем настройки матрицы канвы
      canvas.save();

      // преобразования канвы
      // и рисование желтых квадратов
      p.setColor(Color.YELLOW);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // сохраняем настройки матрицы канвы
      canvas.save();

      // преобразования канвы
      // и рисование красных квадратов
      p.setColor(Color.RED);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // возврат канвы к предыдущему сохранению
      canvas.restore();

      // синий квадрат
      p.setColor(Color.BLUE);
      canvas.drawRect(rectf2, p);

      // возврат канвы к предыдущему сохранению
      canvas.restore();

      // черный квадрат
      p.setColor(Color.BLACK);
      canvas.drawRect(rectf2, p);

      // возврат канвы в изначальное состояние
      canvas.restore();

      // пурпурный квадрат
      p.setColor(Color.MAGENTA);
      canvas.drawRect(rectf2, p);
    }

We repeatedly apply a displacement of 100 to draw the first square. At the same time we periodically save the state of the matrix by the save method, while changing the color for a more convenient visual perception.

Next, we call the restore method several times, which returns the canvas to the previously saved states and draws a second square. As a result, we come to the initial state of the outline.

result:

The save method returns us int values. We can pass this value to the restoreToCount method and the matrix will return to the specified state bypassing the others.

rewrite onDraw:

protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);

      // сохраняем настройки матрицы канвы
      // в initSave получаем значение для восстановления этого состояния
      int initSave = canvas.save();

      // зеленый квадрат
      p.setColor(Color.GREEN);
      canvas.drawRect(rectf1, p);

      // преобразования канвы
      // и рисование зеленых квадратов
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // сохраняем настройки матрицы канвы
      canvas.save();

      // преобразования канвы
      // и рисование желтых квадратов
      p.setColor(Color.YELLOW);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // сохраняем настройки матрицы канвы
      // в needSave получаем значение для восстановления этого состояния
      int needSave = canvas.save();

      // преобразования канвы
      // и рисование красных квадратов
      p.setColor(Color.RED);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // сохраняем настройки матрицы канвы
      canvas.save();

      // преобразования канвы
      // и рисование синих квадратов
      p.setColor(Color.BLUE);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);
      canvas.translate(100, 0);
      canvas.drawRect(rectf1, p);

      // возврат канвы к указанному сохранению
      canvas.restoreToCount(needSave);

      // черный квадрат
      p.setColor(Color.BLACK);
      canvas.drawRect(rectf2, p);

      // возврат канвы к указанному сохранению
      canvas.restoreToCount(initSave);

      // пурпурный квадрат
      p.setColor(Color.MAGENTA);
      canvas.drawRect(rectf2, p);

    }

We store the state at the beginning and store the value of the save method in the initSave variable. Then we move, save the canvas several times, and write the save value once in needSave.

We then return to the saved states using the restoreToCount method and the variables initSave and needSave.

In the last example, we used the restore method to sort through all the saved states. And here the restoreToCount method allowed us to return to the required state immediately.

result:

And finally, three more canvas methods

setMatrix (Matrix matrix) – changes the canvas matrix to the specified matrix

getMatrix () – allows you to get a copy of the canvas matrix

concat (Matrix matrix) – add to the beginning of the current transformation matrix of the specified matrix

In the next lesson:

– we use Region




Discuss in the forum [4 replies]

Leave a Comment