Lesson 145. Drawing. Matrix. setRectToRect and setPolyToPoly

Lesson 145. Drawing. Matrix. setRectToRect and setPolyToPoly


In this lesson:

– deal with the methods setRectToRect and setPolyToPoly

The matrix not only allows us to specify the necessary transformations, but also knows how to calculate them ourselves if we provide it with the initial data and the result we would like to obtain.

Consider a couple of such methods.

setRectToRect

Let’s start with setRectToRect, which already helped us some time when we displayed the camera image in Lesson 132.

This method takes two rectangles and determines which transformations need to be made over the first rectangle to fit it completely into the second. These transformations are written to the matrix and we can use it.

As an example, let’s draw the largest snowman and try to place it in small rectangles.

Let’s create a project:

Project name: P1451_MatrixTransform2
Build Target: Android 2.3.3
Application name: MatrixTransform2
Package name: ru.startandroid.develop.p1451matrixtransform2
Create Activity: MainActivity

MainActivity.java:

package ru.startandroid.develop.p1451matrixtransform2;

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;
    Path path;
    Path pathDst;
    RectF rectfBounds;
    RectF rectfDst;
    Matrix matrix;

    public DrawView(Context context) {
      super(context);
      p = new Paint();
      p.setStrokeWidth(3);
      p.setStyle(Paint.Style.STROKE);

      rectfDst = new RectF();
      rectfBounds = new RectF();

      path = new Path();
      path.addCircle(200, 100, 50, Path.Direction.CW);
      path.addCircle(200, 225, 75, Path.Direction.CW);
      path.addCircle(200, 400, 100, Path.Direction.CW);
      
      pathDst = new Path();
      matrix = new Matrix();
    }

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

      rectfDst.set(500, 50, 800, 150);
      
      // снеговик
      p.setColor(Color.BLUE);
      canvas.drawPath(path, p);

      // граница снеговика
      path.computeBounds(rectfBounds, true);
      p.setColor(Color.GREEN);
      canvas.drawRect(rectfBounds, p);

      // START
      // рамка
      p.setColor(Color.BLACK);
      canvas.drawRect(rectfDst, p);
      // преобразование
      matrix.reset();
      matrix.setRectToRect(rectfBounds, rectfDst, Matrix.ScaleToFit.START);
      path.transform(matrix, pathDst);
      // снеговик
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);

      rectfDst.offset(0, 150);

      // CENTER
      // рамка
      p.setColor(Color.BLACK);
      canvas.drawRect(rectfDst, p);
      // преобразование
      matrix.reset();
      matrix.setRectToRect(rectfBounds, rectfDst,
          Matrix.ScaleToFit.CENTER);
      path.transform(matrix, pathDst);
      // снеговик
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);

      rectfDst.offset(0, 150);

      // END      
      // рамка
      p.setColor(Color.BLACK);
      canvas.drawRect(rectfDst, p);
      // преобразование
      matrix.reset();
      matrix.setRectToRect(rectfBounds, rectfDst, Matrix.ScaleToFit.END);
      path.transform(matrix, pathDst);
      // снеговик
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);

      rectfDst.offset(0, 150);

      // FILL
      // рамка
      p.setColor(Color.BLACK);
      canvas.drawRect(rectfDst, p);
      // преобразование
      matrix.reset();
      matrix.setRectToRect(rectfBounds, rectfDst, Matrix.ScaleToFit.FILL);
      path.transform(matrix, pathDst);
      // снеговик
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);

    }
  }

}

result:

Let’s parse the code.

First, we add three circles to the path and get a snowman. We draw it in blue.

Because the setRectToRect method works with rectangles, we need to get the snowman boundary as a rectangle. To do this, we use the computeBounds method and write the boundaries in rectfBounds. We draw these borders in green for clarity.

Next we draw the first black rectangle with rectfDst coordinates. With the setRectToRect method, we adjust the matrix so that rectfBounds fits in rectfDst. You must specify the ScaleToFit parameter. we indicate START. We will discuss later what he gives us. Our matrix is ​​ready and we use it to transform the snowman (path) and display the result on the screen.

We then repeat the same procedure three times, shifting the black rectangle coordinates (rectfDst) down by 150 and using different ScaleToFit modes.

Let’s discuss the value of ScaleToFit.

When we try to insert one rectangle into another, their aspect ratios may not coincide and the first will occupy only part inside the other. You can see it in the screenshot. If you reduce the snowman (keeping its aspect ratio), it only occupies a portion of the black rectangle. The ScaleToFit parameter lets you specify where to place the snowman.

START – in the left (or top) side

CENTER – in the center

END – on the right (or bottom) side

FILL – Never keep the aspect ratio but stretch the first rectangle so that it completely fills the second

These are the 4 modes in the order we see in the screenshot.

setPolyToPoly

The setPolyToPoly method. Extremely non-trivial to explain and understand a thing. But I kind of found a way to make it all clear. There will be many letters and constant repetitions of the same in different words, for better learning.

This method, in addition to 4 ordinary operations (moving, resizing, tilting, rotating), allows you to set the fifth operation in the matrix – perspective. And we set them not explicitly, but indicating the starting and target points. Behind it, the matrix itself calculates which transformations to make.

We will set points through arrays of coordinates. One point is two coordinates. So if there are 6 coordinates in an array, that means there are 3 points in it. I will mention in the text both coordinates and points, watch carefully.

I will briefly describe the basic meaning of the method in simple words. We pass the method two coordinates arrays: source and target. The source array contains the coordinates of points before currency conversion. That is as it is now. And in the target array we specify the coordinates, as it should be after the transformation. And the task of the method is to adjust the matrix so that it can perform the following transformation: that is, get the target points from the source.

one point

A simple one-point example. To the setPolyToPoly method, we pass the original coordinate array {100,100} and the target coordinate array {150,120}. The setPolyToPoly method has to adjust the matrix so that after the transformation we have what is at the point (100,100) would appear at the point (150,120). That is, in this case, it is simply a shift of 50 to the right and 20 down.

Let’s look at an example.

rewrite the class DrawView:

 class DrawView extends View {

    Paint p;
    Path path;
    Path pathDst;
    RectF rectf;
    Matrix matrix;
    float[] src;
    float[] dst;

    public DrawView(Context context) {
      super(context);
      p = new Paint();
      p.setStrokeWidth(3);
      p.setStyle(Paint.Style.STROKE);

      path = new Path();
      pathDst = new Path();
      matrix = new Matrix();

      rectf = new RectF(100, 100, 200, 200);
      src = new float[] { 100, 100 };
      dst = new float[] { 150, 120 };
    }

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

      // зеленый квадрат
      path.reset();
      path.addRect(rectf, Path.Direction.CW);
      p.setColor(Color.GREEN);
      canvas.drawPath(path, p);

      // преобразование
      matrix.setPolyToPoly(src, 0, dst, 0, 1);
      path.transform(matrix, pathDst);
      
      // синий квадрат
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);
    }
  }

In path we add a square and draw it on the screen in green.

Next we use the setPolyToPoly method. It takes 5 parameters:

– array of initial coordinates
– position of the element in the array of initial coordinates from which we begin to form points
– array of target coordinates
– the position of the element in the array of target coordinates from which we begin to form points
– the number of points that the setPolyToPoly method takes from arrays and uses to adjust the matrix

As an array of output coordinates, we pass an array of two numbers 100 and 100. They are the coordinates of one output point (100,100).

As an array of target coordinates, we pass an array of two numbers 150 and 120. They are the coordinates of one target point (150,120).

The positions of elements in an array will always be 0, that is, we begin to form points from the first element.

We specify the number of points 1. Since two elements of an array are two coordinates, this is one point.

The matrix is ​​configured, we apply it to the path and draw the result in blue.

Now let’s find out what content the source and target points bear. When we use only one source and destination point, the matrix adjusts the transformation of the movement. In simple words, adjust the matrix so that the starting point (100,100) after the conversion is at the target point (150,120).

Let’s look at the result for a better understanding

In the green square, the upper left corner is the point (100,100), which we set in rectf. We adjusted the matrix so that what was at the point (100,100) after the transformation would appear at the point (150,120). So, because at point (100,100) we had the upper left corner of the square, then after converting this angle moved to the point (150,120), which reflects the blue square (pathDst). Of course, the transformation can be applied not only to the point, but to the whole figure: it has all shifted. But there is only one point to configure this conversion.

That is, in a non-trivial way, we simply performed the move. We did this simply by stating that after the transformation, what was at the starting point should be at the destination. In our case it was the upper left corner of the square. Moreover, it is not necessary to indicate the exact point of the square. With the same success, we could specify any other point of the square or even a point outside the square. The matrix would still adjust the move (if the output and destination points are different, of course) and move the square after converting the currency. Just by using the point of the square, it is clearer and clearer.

We have considered the setPolyToPoly method as an example of specifying one point (one source and one target). And they saw that one point specifies a move. And we can specify such points from one to four.

two points

Let’s see what gives us two points to use.

rewrite the class DrawView:

class DrawView extends View {
    
    Paint p;
    Paint pBlack;
    Path path;
    Path pathDst;
    RectF rectf;
    Matrix matrix;
    float[] src;
    float[] dst;
    float[] dst2;
    int points = 1;
    

    public DrawView(Context context) {
      super(context);
      p = new Paint();
      p.setStrokeWidth(3);
      p.setStyle(Paint.Style.STROKE);

      pBlack = new Paint();
      pBlack.setColor(Color.BLACK);
      pBlack.setStrokeWidth(3);      
      
      path = new Path();
      pathDst = new Path();
      matrix = new Matrix();

      rectf = new RectF(100,100,200,200);
      src = new float[]{100,100,200,200};
      dst = new float[]{50,300,250,500};
      dst2 = new float[]{400,200,500,200};
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);

      // зеленый квадрат
      path.reset();
      path.addRect(rectf, Path.Direction.CW);
      p.setColor(Color.GREEN);
      canvas.drawPath(path, p);
      canvas.drawLine(src[0], src[1], src[2], src[3], pBlack);
      
      // синий квадрат
      // преобразование
      matrix.setPolyToPoly(src, 0, dst, 0, points);
      path.transform(matrix, pathDst);
      // рисование
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);
      canvas.drawLine(dst[0], dst[1], dst[2], dst[3], pBlack);
      
      // красный квадрат
      // преобразование
      matrix.setPolyToPoly(src, 0, dst2, 0, points);
      path.transform(matrix, pathDst);
      // рисование
      p.setColor(Color.RED);
      canvas.drawPath(pathDst, p);
      canvas.drawLine(dst2[0], dst2[1], dst2[2], dst2[3], pBlack);
    }
  }

result:

We have three arrays: src – source coordinates, and two target coordinate arrays dst and dst2. There are 4 elements in each array. Since 4 coordinates = 2 points, we have a pair of starting points and two pairs of target points. Two pairs of targets are needed to show two examples of conversion. That is, we will configure two matrices.

We use all the same square rectf. Add it to the path and display it in green. Here in black we draw a line given by a pair of starting points. We need this for clarity. This line is irrelevant to the matrix. It will simply help you better see and understand the mechanism. We set the starting points so that this line is diagonal of a square. This is visible in the screenshot, in the green square.

Next, we set up the matrix using the setPolyToPoly method. Give the output array (src) and the first target (dst). Here note that the arrays contain 4 coordinates, and therefore 2 points. And the points variable we pass to setPolyToPoly is 1. That is, we tell the method that it uses only one point (the first two elements) from each array to adjust the matrix, and ignores the other points.

Thus, again, as in the previous case, we use a single point. So we get a simple move. In our case, this is the move from point (100,100) (first two elements of the src array) to point (50,300) (first two elements of the dst array).

After adjusting the matrix in blue, we display the result of the transformation. The screenshot shows that the left upper corner of the blue square is at (50,300) with dst, as we ordered. Here we draw a black line given by a pair of target points with dst. Later we will understand why it is necessary.

We then perform the conversion similarly using the same source array (src) and the second target (dst2). And output the result in red. The upper left corner is in (400,200) with dst2. The screenshot shows this. Again, we draw a black line given by a pair of destination points with dst2.

So, compared to the previous example, here we took arrays with two points (4 coordinates) rather than one point (2 coordinates). But in the method setPolyToPoly so far indicated that only one point should be used. That is, the matrix just tuned us the movement that we saw on the screen.

Why then are the second points needed? So far, we have only used them to draw black lines between the first and second points. We will talk about these lines now.

We remember the logic of one point. What was at the starting point should be at the destination after the transformation. Similarly, we formulate the logic for two points. What is on the line given by two starting points, after transformation, must be on the line given by two destination points. Accordingly, we give the setPolyToPoly method two of these lines: the output (the src array) and the target (the dst array), and it adjusts the matrix on them.

We look at the screen again

We have a green square at the start, and blue and red at the target.

In black, we drew the lines we needed. In the green square, this line is from the source array src: (100,100) – (200,200). Blue has the target dst (50,300) – (250,500). So after the transformation, what was on line (100,100) – (200,200) should appear on line (50,300) – (250,500). Therefore, on the line (100,100) – (200,200) we have the diagonal of the original square, so the diagonal of the target square should be the line (50,300) – (250,500). That is, the blue square should increase in size so that its diagonal coincides with its black line.

So far this has not happened because we instructed the setPolyToPoly method to use only one point. He does not take the second into account when calculating the transformations.

With the red square everything is the same. Its diagonal should be where its black line runs. It turns out that to do this, he will have to turn back slightly against the clock and decrease in size.

Let’s include in our code the use of the second points and look at the result of transformations. Give the variable points a value of 2 instead of 1.

int points = 2;

result:

All as we asked. The diagonals of the target squares completely occupied the black lines. That is, we, in the matrix, set not only the displacement, but also the resizing and rotation, using two points.

In summary.

one a point (an array of two coordinates) allowed us to put moving.

two points (an array of four coordinates) allowed us to put moving, turn and resize.

three points

We see an example with three dots.

rewrite the class DrawView:

class DrawView extends View {
    
    Paint p;
    Paint pBlack;
    Paint pGray;
    Path path;
    Path pathDst;
    RectF rectf;
    Matrix matrix;
    float[] src;
    float[] dst;
    float[] dst2;
    int points = 2;
    

    public DrawView(Context context) {
      super(context);
      p = new Paint();
      p.setStrokeWidth(3);
      p.setStyle(Paint.Style.STROKE);
      
      pGray = new Paint();
      pGray.setColor(Color.GRAY);
      pGray.setStrokeWidth(3);
      
      pBlack = new Paint();
      pBlack.setColor(Color.BLACK);
      pBlack.setStrokeWidth(3);      
      
      path = new Path();
      pathDst = new Path();
      matrix = new Matrix();

      rectf = new RectF(100,100,200,200);
      src = new float[]{100,100,200,200,200,100};
      dst = new float[]{50,300,250,500,230,350};
      dst2 = new float[]{400,200,500,200,440,100};
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);
      
      // зеленый квадрат      
      path.reset();
      path.addRect(rectf, Path.Direction.CW);
      p.setColor(Color.GREEN);
      canvas.drawPath(path, p);
      canvas.drawLine(src[0], src[1], src[2], src[3], pBlack);
      canvas.drawLine(src[0], src[1], src[4], src[5], pGray);

      // синий квадрат
      // преобразование
      matrix.setPolyToPoly(src, 0, dst, 0, points);
      path.transform(matrix, pathDst);
      // рисование      
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);
      canvas.drawLine(dst[0], dst[1], dst[2], dst[3], pBlack);
      canvas.drawLine(dst[0], dst[1], dst[4], dst[5], pGray);
      
      // красный квадрат
      // преобразование      
      matrix.setPolyToPoly(src, 0, dst2, 0, points);
      path.transform(matrix, pathDst);
      // рисование
      p.setColor(Color.RED);
      canvas.drawPath(pathDst, p);
      canvas.drawLine(dst2[0], dst2[1], dst2[2], dst2[3], pBlack);
      canvas.drawLine(dst2[0], dst2[1], dst2[4], dst2[5], pGray);
    }
  }

The code is similar to the previous example. But now we have added one more point to the arrays (two coordinates). Now the arrays consist of 6 coordinate elements, so you can add three points to them.

What the first two do, we already know. Let’s see what the third will give us.

To do this, we again draw the lines given by the first and third points of the array. Draw them in gray (pGray). The points parameter for the setPolyToPoly method is set to two. That is, the method is yet to calculate the matrix based on two points, and the third ignore.

let’s see the result

Almost the same picture as in the previous case. Using the first two points of the arrays, the matrix set the transformation of the green square to blue and red.

But now the figure shows gray lines. Recall that this line is given by the first and third (new) point of the array. The meaning here is the same: what is on the baseline must be on the target. But in the case of the third point, it allows us to set the skew of the square.

In the green (original) square, the gray line is its upper bound. So the blue and red arrays should be drawn so that their upper boundaries lie on their gray lines. And so we use the third point for this, it should be done by tilting, not moving or turning (we set them by the second point).

Let’s turn on the use of the setPolyToPoly method of all three points by changing the value of the points parameter to 3 in the code:

int points = 3;

result

The squares tilted so that their upper boundaries coincided with the gray lines. That is the way we set the slope.

We summarize again.

one a point (an array of two coordinates) allowed us to put moving.

two points (an array of four coordinates) allowed us to put moving, turn and resize.

Three points (an array of six coordinates) allowed us to put moving, turn, resize and incline.

I have great hope that you are gradually discovering the content of this whole mechanism)

four points

The 4th point remains.

rewrite the class DrawView:

class DrawView extends View {
    
    Paint p;
    Paint pBlack;
    Paint pGray;
    Paint pWhite;
    Path path;
    Path pathDst;
    RectF rectf;
    Matrix matrix;
    float[] src;
    float[] dst;
    float[] dst2;
    int points = 3;
    

    public DrawView(Context context) {
      super(context);
      p = new Paint();
      p.setStrokeWidth(3);
      p.setStyle(Paint.Style.STROKE);
      
      pGray = new Paint();
      pGray.setColor(Color.GRAY);
      pGray.setStrokeWidth(3);
      
      pBlack = new Paint();
      pBlack.setColor(Color.BLACK);
      pBlack.setStrokeWidth(3);
      
      pWhite = new Paint();
      pWhite.setColor(Color.WHITE);
      pWhite.setStrokeWidth(3);      
      
      path = new Path();
      pathDst = new Path();
      matrix = new Matrix();

      rectf = new RectF(100,100,200,200);
      src = new float[]{100,100,200,200,200,100,100,200};
      dst = new float[]{50,300,250,500,230,350,40,550};
      dst2 = new float[]{400,200,500,200,440,100,440,230};
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
      canvas.drawARGB(80, 102, 204, 255);

      // зеленый квадрат
      path.reset();
      path.addRect(rectf, Path.Direction.CW);
      p.setColor(Color.GREEN);
      canvas.drawPath(path, p);
      canvas.drawLine(src[0], src[1], src[2], src[3], pBlack);
      canvas.drawLine(src[0], src[1], src[4], src[5], pGray);
      canvas.drawLine(src[0], src[1], src[6], src[7], pWhite);
      
      // синий квадрат
      // преобразование 
      matrix.setPolyToPoly(src, 0, dst, 0, points);
      path.transform(matrix, pathDst);
      // рисование
      p.setColor(Color.BLUE);
      canvas.drawPath(pathDst, p);
      canvas.drawLine(dst[0], dst[1], dst[2], dst[3], pBlack);
      canvas.drawLine(dst[0], dst[1], dst[4], dst[5], pGray);
      canvas.drawLine(dst[0], dst[1], dst[6], dst[7], pWhite);

      // красный квадрат
      // преобразование       
      matrix.setPolyToPoly(src, 0, dst2, 0, points);
      path.transform(matrix, pathDst);
      // рисование
      p.setColor(Color.RED);
      canvas.drawPath(pathDst, p);
      canvas.drawLine(dst2[0], dst2[1], dst2[2], dst2[3], pBlack);
      canvas.drawLine(dst2[0], dst2[1], dst2[4], dst2[5], pGray);
      canvas.drawLine(dst2[0], dst2[1], dst2[6], dst2[7], pWhite);
    }
  }

The code is similar to the previous example with three dots. Now we’ve added a couple more coordinates to the arrays to get fourth points. And for each square the white lines given by the first and fourth points were drawn.

The points parameter for the setPolyToPoly method will be set to 3. That is, the method will still calculate the matrix based on three points and the fourth will ignore.

result:

Almost the same picture as last time. The three-point matrix calculates displacement, rotation, resizing, and tilt, and we see the results of the transformation: blue and red squares.

The white lines of the squares are given by the first and fourth points. The meaning again is the same: what is on the baseline should be on the target. In the case of the fourth point, it allows us to set something like a perspective for a square.

The green (original) square of the white line is its left side. So the blue and red arrays should be drawn so that their left borders lie on their white lines. And so we use the fourth point for this, then it should be done with perspective, not tilt (third point) or move and rotate (we set them the second point).

Let’s include the use of the setPolyToPoly method on all four points, changing the code of the points parameter to 4:

int points = 4;

result:

The squares are deformed so that their left boundaries coincide with the white lines. The result looks like a prospect.

In summary.

one a point (an array of two coordinates) allowed us to put moving.

two points (an array of four coordinates) allowed us to put moving, turn and resize.

Three points (an array of six coordinates) allowed us to put moving, turn, resize and incline.

Four points (an array of eight coordinates) allowed us to put moving, turn, resize, incline and perspective.

In general, he explained how he could. Of course, I will definitely not be able to) I advise you to put a little point in the arrays and watch the results. You can use the last example immediately. And with points you can adjust the number of points used: from 1 to 4.

The setPolyToPoly method returns us in addition to the matrix setup boolean value. By this he reports: he had to adjust the matrix or the requirements were contradictory and the adjustment was impossible.

In the process of creating the lesson, the book Flotland was mentioned. If you have not read it, then I recommend a very interesting piece.

In the next lesson:

– we use the canvas matrix for transformations




Discuss in the forum [6 replies]

Leave a Comment