preface

When coordinate transformation is carried out, the Matrix class cannot be wrapped around. Then, what the Matrix principle is and how to use it will be explained in detail in the following. Through this article, you will learn:

1. Basic methods of Matrix class 2. How to understand pre/ Post 3

Matrix knowledge







Take a simple multiplication example:


You can see that when you switch places, the multiplication is different. In fact: matrix multiplication does not satisfy the commutative law. The identity matrix: a square matrix with 1’s on the main diagonal and 0’s on the rest is called the identity matrix

Matrix principle

Some basic knowledge of Matrix is described above, and then the principle of Matrix is analyzed. Most of the coordinate systems we contact are plane coordinates (two-dimensional), and the transformation of the coordinate system includes translation, scaling, rotation, miscutting, etc. When the Android Canvas is drawn, the default starting point is (0,0). How to change the drawing position of the Canvas? Android provides the Matrix class to transform Canvas coordinates. A Matrix is a Matrix. The Android Matrix class operates on a 3 by 3 (3 rows /3 columns) Matrix. So what are the values in the Matrix Matrix?










The Matrix class transforms the coordinates of a point


Matrix common methods

Since Matrix class achieves the purpose of coordinate transformation by operating on Matrix, it needs to expose the method of operating Matrix externally. Let’s search for the method in this class:

SetTranslate (float dx, float dy)// PreTranslate (float dx, float dy) postTranslate(float dx, Rotate: rotate setRotate(float degrees)//degrees Rotate Angle setRotate(float degrees, float px, //px py preRotate(float degrees, float px, PostRotate (float degrees) postRotate(float degrees, float px, float py) scale: zoom setScale(float sx, SetScale (float sx, float sy, float px, float py) preScale(float sx, float sy, float px, float py) float sy) preScale(float sx, float sy, float px, float py) postScale(float sx, float sy) postScale(float sx, float sy, Skew: error skew (float kx, float KY) setSkew(float kx, float KY, float px, float py) preSkew(float kx, float ky) preSkew(float kx, float ky, float px, float py) postSkew(float kx, float ky) postSkew(float kx, float ky, float px, Float py) // SRC The source rectangle to be scaled // DST the target rectangle to be filled after scaling // STF how to fill setRectToRect(RectF SRC, RectF DST, ScaleToFit STF) // Matrix a, Matrix BCopy the code

How do you understand these methods and how do you use them? Here’s an example:

@override protected void onDraw(Canvas Canvas) {Override protected void onDraw(Canvas Canvas) {Override protected void onDraw(Canvas Canvas) { Rect = new rect (0,0,200, 200); DrawRect (rect, paint); // construct matrix matrix = new matrix (); // Horizontally translate 300 matrix. SetTranslate (300, 0); //matrix.preTranslate(300, 0); //matrix.postTranslate(300, 0); // setMatrix canvas.setmatrix (matrix); SetColor (color.red); DrawRect (rect, paint); }Copy the code

Custom view drawing simple rectangle, after running:





SetTranslate (300, 0)-> Modify the TransX value of the newly constructed matrix, and the other values are the default values of the identity matrix

Expressed as a matrix:


Matrix = new matrix ()-> construct 3*3 identity matrix, we call it I preTranslate()-> call it “left multiply”, the translation matrix is called T, “left multiply” means that the existing matrix I “times” the translation matrix T, the result of the new matrix is: M=I * T

Expressed as a matrix:

The postTranslate method, as opposed to the preTranslate method, is called “right-multiply”, which means to “multiply” the translation matrix T by the existing matrix I. The result of the new matrix is: M=T * I

You may have noticed that setTranslate, preTranslate, and postTranslate in the above example yield the same results, which means that all three methods have the same function. Actually, it’s not. Remember what we said about matrices earlier, “Any matrix multiplied by the identity matrix is itself.” The original matrix is the identity matrix I, so preTranslate and postTranslate work the same in this case. Here’s another example:

@override protected void onDraw(Canvas Canvas) {Override protected void onDraw(Canvas Canvas) {Override protected void onDraw(Canvas Canvas) { Rect = new rect (0,0,200, 200); DrawRect (rect, paint); // construct matrix matrix = new matrix (); // Horizontally translate 300 matrix. SetTranslate (300, 0); //1 matrix.preTranslate(100, 0); //2 matrix.postTranslate(50, 0); //3 matrix.preTranslate(-50, 0); //4 matrix.preTranslate(10, 0); //5 matrix.postTranslate(20, 0); //6 // setMatrix canvas.setmatrix (matrix); SetColor (color.red); DrawRect (rect, paint); }Copy the code

Read some articles that say pre is executed first, post is executed after, according to this statement, the above order is: 124563. In fact, this statement is wrong, the same thread, except compilation optimization and other adjustments to the code order, code execution > according to the order of execution, that is, 123456.

It can be understood as follows:

1, setTranslate(300, 0)-> matrix (T1) 2, matrix. PreTranslate (100, 0)-> matrix (T1) T2=T1 * Ta(-50, 0); T2=T1 * Ta(-50, 0); T2=T1 * Ta(-50, 0); T2=T1 * Ta(-50, 0); T4 = T3 * * * (to transform) 5, matrix. PreTranslate (10, 0) – > matrix into: T5 = T4 * Td (to transform) 6, matrix. PostTranslate (20, 0) – > matrix into: T5 T6 = Te (to transform) * eventually: T6 (Tb) = (Te) * * (T1) * (Ta) * (Tc) * (Td)

After a series of changes, the Matrix Matrix will eventually become T6, which will be used by Canvas for coordinate transformation. It can be seen from the above that matrix multiplication does not satisfy the commutative law, and different combinations of pre/ POST methods will affect the final result.

Matrix composite transformation

The Translate transform is simple. The combination of other transformations, such as rotate, scale, and Skew, is called composite transformation.

    @Override
    protected void onDraw(Canvas canvas) {
        Matrix matrix = new Matrix();
        canvas.drawBitmap(bitmap, matrix, paint);
        matrix.setRotate(45);
        canvas.drawBitmap(bitmap, matrix, paint);
    }
Copy the code

Draw an image and rotate the image 45 degrees to see what it looks like:


Rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate rotate Let’s call it matrix -t and the final matrix M=I * T * R * (-t) = T * R * (-t);

Complete the above three steps using the method provided by Matrix

    @Override
    protected void onDraw(Canvas canvas) {
        Matrix matrix = new Matrix();
        canvas.drawBitmap(bitmap, matrix, paint);
        matrix.preTranslate(bitmap.getWidth() / 2, bitmap.getHeight() / 2);
        matrix.preRotate(45);
        matrix.preTranslate(-bitmap.getWidth() / 2, -bitmap.getHeight() / 2);
        canvas.drawBitmap(bitmap, matrix, paint);
    }
Copy the code











    @Override
    protected void onDraw(Canvas canvas) {
        Matrix matrix = new Matrix();
        canvas.drawBitmap(bitmap, matrix, paint);
        matrix.postTranslate(-bitmap.getWidth() / 2, -bitmap.getHeight() / 2);
        matrix.postRotate(45);
        matrix.postTranslate(bitmap.getWidth() / 2, bitmap.getHeight() / 2);
        canvas.drawBitmap(bitmap, matrix, paint);
    }
Copy the code

PreXX and postXX are combined

    @Override
    protected void onDraw(Canvas canvas) {
        Matrix matrix = new Matrix();
        canvas.drawBitmap(bitmap, matrix, paint);
        matrix.postRotate(45);
        matrix.postTranslate(bitmap.getWidth() / 2, bitmap.getHeight() / 2);
        matrix.preTranslate(-bitmap.getWidth() / 2, -bitmap.getHeight() / 2);
        canvas.drawBitmap(bitmap, matrix, paint);
    }
Copy the code

So when do you use preXX and when do you use postXX?

1. As can be seen from the above, first list the effect we want, and simply deduce the matrix multiplication to get the final matrix. 2. Next, consider using preXX, postXX alone or in combination, as long as the result of the derivation is the same as in step 1. PreXX and postXX are simply used to exchange the order of transformations.

Of course Matrix provides a way to rotate around a fulcrum: setRotate(float Degrees, float PX, float py). Take Translate and Rotate as examples to illustrate some understanding of the Matrix method. Scale and Skew methods are very similar to the previous two, so they are easy to be compared.

Matrix code implements Matrix operations

Matrix class provides several transformation methods, it is very convenient to set the desired transformation, so these methods behind how to achieve Matrix addition, multiplication? Let’s look at the underlying principle of Matrix.

The data structure

Let’s start with matrix.java in the Java layer

// ------------------ Critical JNI ------------------------ @CriticalNative private static native boolean nIsIdentity(long nObject); @CriticalNative private static native boolean nIsAffine(long nObject); @CriticalNative private static native boolean nRectStaysRect(long nObject); @CriticalNative private static native void nReset(long nObject); @CriticalNative private static native void nSet(long nObject, long nOther); @CriticalNative private static native void nSetTranslate(long nObject, float dx, float dy); @CriticalNative private static native void nSetScale(long nObject, float sx, float sy, float px, float py); / / to omitCopy the code

It is found that the jNI layer is called, and.cpp code is called through JNI.

Matrix. Java ultimately calls SkMatrix. CPP.

    SkScalar         fMat[9];
    mutable uint32_t fTypeMask;

    constexpr SkMatrix(SkScalar sx, SkScalar kx, SkScalar tx,
                       SkScalar ky, SkScalar sy, SkScalar ty,
                       SkScalar p0, SkScalar p1, SkScalar p2, uint32_t typeMask)
        : fMat{sx, kx, tx,
               ky, sy, ty,
               p0, p1, p2}
        , fTypeMask(typeMask) {}
Copy the code

The identity Matrix of Matrix 3*3 mentioned before has a total of 9 values, which are stored in a one-digit array, corresponding to SkScalar fMat[9] above;

SkScalar fMat[9] -> float fMat[9]

In matrix. Java setXX/preXX/postXX eventually changes the corresponding value in fMat[9]. The nine value array subscripts are represented by the following values

static constexpr int kMScaleX = 0; / /! < horizontal scale factor static constexpr int kMSkewX = 1; / /! < horizontal skew factor static constexpr int kMTransX = 2; / /! < horizontal translation static constexpr int kMSkewY = 3; / /! < vertical skew factor static constexpr int kMScaleY = 4; / /! < vertical scale factor static constexpr int kMTransY = 5; / /! < vertical translation static constexpr int kMPersp0 = 6; / /! < input x perspective factor static constexpr int kMPersp1 = 7; / /! < input y perspective factor static constexpr int kMPersp2 = 8; / /! < perspective biasCopy the code

From these values, you can see the meanings of the corresponding positions.

Example of the skmatrix. CPP function

Java: setTranslate(float dx, float dy) skmatrix.cpp:

SkMatrix& SkMatrix::setTranslate(SkScalar dx, SkScalar dy) { *this = SkMatrix(1, 0, dx, 0, 1, dy, 0, 0, 1, (dx ! = 0 || dy ! = 0)? kTranslate_Mask | kRectStaysRect_Mask : kIdentity_Mask | kRectStaysRect_Mask); return *this; }Copy the code

You can see that kMTransX and kMTransY are reset except for dx and dy, respectively. The same goes for setScale, setRotate, and setSkew. Therefore, other transformations are reset when the matrix.java setXX method is used.

Matrix. Java: setScale(float sx, float sy, float px, float py)

SkMatrix& SkMatrix::setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { if (1 == sx && 1 == sy) { this->reset(); } else { this->setScaleTranslate(sx, sy, px - sx * px, py - sy * py); } return *this; } void setScaleTranslate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty) { fMat[kMScaleX] = sx; fMat[kMSkewX] = 0; fMat[kMTransX] = tx; fMat[kMSkewY] = 0; fMat[kMScaleY] = sy; fMat[kMTransY] = ty; fMat[kMPersp0] = 0; fMat[kMPersp1] = 0; fMat[kMPersp2] = 1; unsigned mask = 0; if (sx ! = 1 || sy ! = 1) { mask |= kScale_Mask; } if (tx || ty) { mask |= kTranslate_Mask; } this->setTypeMask(mask | kRectStaysRect_Mask); }Copy the code

This function takes (px, py) as the fulcrum to zoom in and out.

setScaleTranslate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty)

This function sets the scale and translation distance directly

This ->setScaleTranslate(sx, sy, px-sx * px, py-sy * py) this->setScaleTranslate(sx, sy, px-sx * px, py-sy * py) Let’s review the matrix multiplication above. SetScale (float sx, float sy, float px, float py) can be divided into three steps:

Scale the fulcrum from (px, py) to (0,0); scale the fulcrum from (px, py) to (0,0)

In terms of matrices, T represents the shifted matrix, S represents the scaled matrix, -t represents the shifted back matrix, and M represents the final matrix. M = T * S * (-T)


Other functions are basically the same, limited to space, not expand to say. Ps: If there is any doubt, please comment on the message, will reply as soon as possible