Bessel Curve of Android Custom View(9)_Path

Posted by VagabondKites on Sat, 11 May 2019 20:34:14 +0200

I. Table of Path's Common Methods

In order to be compatible (lazy), the method added above API21 (Android Version 5.0) is removed from this table. Unable to help Tucao, why does it seem that some overloaded method can be written only when API21 is added. Baby's heart is crumbling at the moment.

Effect correlation method Remarks
Mobile starting point moveTo Move the starting point of the next operation
Set end point setLastPoint Reset the last point in the current path. If called before drawing, the effect is the same as moveTo.
Connecting straight line lineTo Add a line from the previous point to the current point to Path
Closed path close Connect the first point to the last point to form a closed area
Add content addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo Add (rectangle, rounded rectangle, ellipse, circle, path, arc) to the current Path (note the difference between addArc and arcTo)
Is it empty? isEmpty Judging whether Path is empty
Is it a rectangle? isRect Determine whether path is a rectangle?
Replacement path set Replace all contents of the current path with a new path
Offset path offset Offset the operation before the current path (without affecting the subsequent operation)
bezier quadTo, cubicTo The Method of Quadratic and Triple Bessel Curves
rXxx method rMoveTo, rLineTo, rQuadTo, rCubicTo The method without r is based on the origin coordinate system (offset) and the rXxx method is based on the current point coordinate system (offset).
Fill mode setFillType, getFillType, isInverseFillType, toggleInverseFillType Setting, Getting, Judging and Switching Filling Mode
Prompting method incReserve Hint how many points Path has to wait to join (this method seems to allow Path to optimize the storage structure)
Boolean operation (API19) op Boolean operations on two Path s (i.e. taking intersection, union, etc.)
Computation boundary computeBounds Computing Path's Boundary
Reset path reset, rewind Clear up content in Path
reset does not retain the internal data structure, but it retains FillType.
rewind retains the internal data structure, but does not retain FillType
Matrix operation transform Matrix transformation

II. Path Details

Last time, except for some common functions, the explanation is basically straight lines. This time, we need to understand the curve part. When we talk about curves, we have to nominate the famous Bessel curve. It was invented by the following man (Pierre Bzier, French mathematician).

What can Bessel Curve do?

Bessel curve is widely used. It can be said that Bessel curve lays the foundation of computer graphics (because it can describe any complex graphics in precise mathematical language). It has been used inadvertently.

If you use Photoshop, you may notice that there is a pen tool in it. The core of the pen tool is the Bessel curve.

You said you couldn't PS? It doesn't matter. If you have read the previous articles or used 2D drawing, you must have drawn circles, arcs, rounded rectangles and so on. All of the arc parts are the use of Bessel curves.

Bessel Curve has a very wide range of functions. Let's simply list a few chestnuts: chestnut, chestnut, chestnut, chestnut, chestnut, chestnut, chestnut, chestnut, chestnut and chestnut.

  • Dragging effect of QQ small red dot
  • Some cool drop-down refresh controls
  • The effect of reading software on turning over books
  • Making Some Smooth Fold Diagrams
  • Many cool animation effects

How to get into Bessel Curve easily?

Although Bessel Curve is widely used, there seems to be no suitable Chinese tutorial at present. Android's Chinese articles about Bessel Curve can be basically divided into the following categories:
* Popular science (just to make people understand Bessel, not substantive content)
* Pretending (putting out a bunch of formulas, quoting a bunch of English texts)
* Basic type (just explaining the use of two functions of Bessel curve)
* Practical Type (Explaining the Application of Bessel Curve Based on Examples)

The more useful of the above types is the basic type and the actual combat type, but both have their own shortcomings. This paper will synthesize the two contents and learn the Bessel curve from scratch.

Step 1. Understanding the Principle of Bessel Curve

Understanding the Bessel curve here is not a process of learning to deduce formulas (not a process of pushing over, but a process of understanding how the Bessel curve is generated).  
Bessel curves use a series of points to control the state of the curve. I simply divide these points into two categories:

type Effect
data point Determine the starting and ending position of the curve
control point Determine the degree of curve bending

For the time being, I'll just explain the concept, and then I'll explain its detailed meaning.

First order curve principle:

The first order curve has no control points, only two data points (A and B), and the final effect is a line segment.

The above figure shows a certain stage in the process of first-order curve generation. The dynamic process can refer to the following figure. (The dynamic demonstration pictures of Bessel curve in this paper are from Wikipedia.)

PS: The first-order curve is actually the lineTo described earlier.

Second-order curve principle:

The second-order curve is described by two data points (A and C) and one control point (B). It is roughly as follows:

The part of the red curve in the picture above is the legendary second-order Bessel curve. How does this red curve come into being? Next, we will analyze one of the states:

Connect AB BC and take point D on AB and point E on BC to satisfy the conditions:

Connect DE, take point F, make:

The obtained point F is a point on the Bessel curve. The dynamic process is as follows:

PS: The method of second-order curve correspondence is quadTo

Third-order curve principle:

The third-order curve is described by two data points (A and D) and two control points (B and C), as follows:

The process of calculating the third-order curve is similar to that of the second-order curve.

PS: The third-order curve corresponds to cubicTo

Bessel Curve Quick Lookup Table

Strongly recommend click here Practicing Bessel curve can deepen the understanding of Bessel curve.

Step 2. Understanding the use of Bessel curve correlation function

First order curve:

The first order curve is a line segment. It's very simple. See the previous article. Basic Operation of Path I won't go into details here.

Second order curve:

Through the simple understanding of the second-order curve above, we know that the second-order curve is composed of two data points and a control point. Next, we use an example to demonstrate how the second-order curve is used.

Firstly, two data points control the position of Bessel curve beginning and ending, which is easy to understand, while the control point controls Bessel curve bending state, which is relatively difficult to understand. Therefore, this example focuses on understanding the relationship between Bessel curve bending state and control point.

In order to make it easier to see the relationship between the control points and the bending degree of the curve, the auxiliary points and the auxiliary lines are drawn in the figure above. From the dynamic diagram above, it can be seen that the Bessel curve has the same elastic effect as the rubber band in the process of dynamic change, so it is often used in making some elastic effects.

The main code is as follows:

public class Bezier extends View {

    private Paint mPaint;
    private int centerX, centerY;

    private PointF start, end, control;

    public Bessel1(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        start = new PointF(0,0);
        end = new PointF(0,0);
        control = new PointF(0,0);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w/2;
        centerY = h/2;

        // Initialize the location of data points and control points
        start.x = centerX-200;
        start.y = centerY;
        end.x = centerX+200;
        end.y = centerY;
        control.x = centerX;
        control.y = centerY-100;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Update control points according to touch position and prompt redrawing
        control.x = event.getX();
        control.y = event.getY();
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // Drawing data points and control points
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);
        canvas.drawPoint(start.x,start.y,mPaint);
        canvas.drawPoint(end.x,end.y,mPaint);
        canvas.drawPoint(control.x,control.y,mPaint);

        // Drawing auxiliary lines
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);
        canvas.drawLine(end.x,end.y,control.x,control.y,mPaint);

        // Drawing Bessel Curve
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);

        Path path = new Path();

        path.moveTo(start.x,start.y);
        path.quadTo(control.x,control.y,end.x,end.y);

        canvas.drawPath(path, mPaint);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

Third-order curve:

The third-order curve is controlled by two data points and two control points.

Code:

public class Bezier2 extends View {

    private Paint mPaint;
    private int centerX, centerY;

    private PointF start, end, control1, control2;
    private boolean mode = true;

    public Bezier2(Context context) {
        this(context, null);

    }

    public Bezier2(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        start = new PointF(0, 0);
        end = new PointF(0, 0);
        control1 = new PointF(0, 0);
        control2 = new PointF(0, 0);
    }

    public void setMode(boolean mode) {
        this.mode = mode;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;

        // Initialize the location of data points and control points
        start.x = centerX - 200;
        start.y = centerY;
        end.x = centerX + 200;
        end.y = centerY;
        control1.x = centerX;
        control1.y = centerY - 100;
        control2.x = centerX;
        control2.y = centerY - 100;

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Update control points according to touch position and prompt redrawing
        if (mode) {
            control1.x = event.getX();
            control1.y = event.getY();
        } else {
            control2.x = event.getX();
            control2.y = event.getY();
        }
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //drawCoordinateSystem(canvas);

        // Drawing data points and control points
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);
        canvas.drawPoint(start.x, start.y, mPaint);
        canvas.drawPoint(end.x, end.y, mPaint);
        canvas.drawPoint(control1.x, control1.y, mPaint);
        canvas.drawPoint(control2.x, control2.y, mPaint);

        // Drawing auxiliary lines
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint);
        canvas.drawLine(control1.x, control1.y,control2.x, control2.y, mPaint);
        canvas.drawLine(control2.x, control2.y,end.x, end.y, mPaint);

        // Drawing Bessel Curve
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);

        Path path = new Path();

        path.moveTo(start.x, start.y);
        path.cubicTo(control1.x, control1.y, control2.x,control2.y, end.x, end.y);

        canvas.drawPath(path, mPaint);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

Compared with the second-order curve, the third-order curve can produce more complex shapes, but for the higher-order curve, the combination of lower-order curves can achieve the same effect, that is, the legendary order reduction. Therefore, we usually encapsulate Bessel curves up to third-order curves.

Reduction and Upgrading

type Interpretation change
reduced order When the shape and direction of the curve remain unchanged, the number of control points is reduced, that is, the order of the curve is reduced. The method becomes simple, the number of data points increases, the number of control points decreases, and the flexibility decreases.
ascend the stairs When the shape and direction of the curve remain unchanged, the number of control points is increased, that is, the order of the curve is increased. The method is more complex, the data points remain unchanged, the control points increase, and the flexibility becomes stronger.

Step 3. Examples of Bessel Curve

Before making this example, first of all, we need to clarify one thing: under what circumstances do we need to use the Bessel curve?

When irregular graphics need to be drawn? Of course not! At present, I think the use of Bessel curve mainly has the following aspects (personal humble opinion, there may be errors, welcome to correct)

Serial number content use case
1 Do not know the state of curve beforehand and need real-time calculation Smooth Breakdown Map of Temperature Change in Weather Forecast
2 When the display status changes according to the user's operation QQ Little Red Spot, Simulate the Return of Books
3 Some of the more complex states of motion (used with PathMeasure) Animation effect of complex motion state

As for the case that only one static curve graph is needed, it would be better to use pictures, and a lot of calculation would be very inexpensive.

If the SVG vector graph is displayed, there are already relevant analytical tools (the Bessel curve is still used internally), and no manual calculation is needed.

The main advantage of Bessel curve is that it can control the state of curve in real time and make the curve change smoothly in real time by changing the state of control point.

Next, let's use a simple example to make a circle gradually become a heart shape:

Design sketch:

Idea analysis:

The final effect we need is to transform a circle into a heart. Through analysis, we can see that a circle can be composed of four third-order Bessel curves, as follows:

The heart shape can also be composed of four third-order Bessel curves, as follows:

The difference between them is only that the positions of data points and control points are different, so it is only necessary to adjust the positions of data points and control points to change the circle into a heart shape.

Core difficulties:

1. How to get the location of data points and control points?

The use of data points and control points to draw a circle has been calculated in detail for a long time, and you can refer to an answer from stack overflow. How to create circle with Bézier curves? The data only need to be used.

For the heart-shaped data points and control points, the circular part of the data points and control points can be translated, and the specific parameters can be slowly adjusted to a satisfactory effect.

2. How to achieve gradual effect?

Gradient is to move data points and control points a little at a time, then redraw the interface, adjust data points and control points several times in a short time, so that they gradually approach the target value, and achieve a gradient effect by constantly redrawing the interface. The process can refer to the following dynamic effects:

Code:

public class Bezier3 extends View {
    private static final float C = 0.551915024494f;     // A constant used to calculate the position of control points in drawing circular Bessel curves

    private Paint mPaint;
    private int mCenterX, mCenterY;

    private PointF mCenter = new PointF(0,0);
    private float mCircleRadius = 200;                  // Radius of circle
    private float mDifference = mCircleRadius*C;        // The difference between the control point and the data point of a circle

    private float[] mData = new float[8];               // Clockwise Recording of Four Data Points Drawing a Circle
    private float[] mCtrl = new float[16];              // Clockwise Recording of Eight Control Points for Drawing a Circle

    private float mDuration = 1000;                     // Total change duration
    private float mCurrent = 0;                         // Currently in progress
    private float mCount = 100;                         // How many copies of the total length will be divided
    private float mPiece = mDuration/mCount;            // The length of each share


    public Bezier3(Context context) {
        this(context, null);

    }

    public Bezier3(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);


        // Initialize data points

        mData[0] = 0;
        mData[1] = mCircleRadius;

        mData[2] = mCircleRadius;
        mData[3] = 0;

        mData[4] = 0;
        mData[5] = -mCircleRadius;

        mData[6] = -mCircleRadius;
        mData[7] = 0;

        // Initialization control point

        mCtrl[0]  = mData[0]+mDifference;
        mCtrl[1]  = mData[1];

        mCtrl[2]  = mData[2];
        mCtrl[3]  = mData[3]+mDifference;

        mCtrl[4]  = mData[2];
        mCtrl[5]  = mData[3]-mDifference;

        mCtrl[6]  = mData[4]+mDifference;
        mCtrl[7]  = mData[5];

        mCtrl[8]  = mData[4]-mDifference;
        mCtrl[9]  = mData[5];

        mCtrl[10] = mData[6];
        mCtrl[11] = mData[7]-mDifference;

        mCtrl[12] = mData[6];
        mCtrl[13] = mData[7]+mDifference;

        mCtrl[14] = mData[0]-mDifference;
        mCtrl[15] = mData[1];
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w / 2;
        mCenterY = h / 2;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
         drawCoordinateSystem(canvas);       // Drawing coordinate system

        canvas.translate(mCenterX, mCenterY); // Move the coordinate system to the center of the canvas
        canvas.scale(1,-1);                 // Flip Y axis

        drawAuxiliaryLine(canvas);


        // Drawing Bessel Curve
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);

        Path path = new Path();
        path.moveTo(mData[0],mData[1]);

        path.cubicTo(mCtrl[0],  mCtrl[1],  mCtrl[2],  mCtrl[3],     mData[2], mData[3]);
        path.cubicTo(mCtrl[4],  mCtrl[5],  mCtrl[6],  mCtrl[7],     mData[4], mData[5]);
        path.cubicTo(mCtrl[8],  mCtrl[9],  mCtrl[10], mCtrl[11],    mData[6], mData[7]);
        path.cubicTo(mCtrl[12], mCtrl[13], mCtrl[14], mCtrl[15],    mData[0], mData[1]);

        canvas.drawPath(path, mPaint);

        mCurrent += mPiece;
        if (mCurrent < mDuration){

            mData[1] -= 120/mCount;
            mCtrl[7] += 80/mCount;
            mCtrl[9] += 80/mCount;

            mCtrl[4] -= 20/mCount;
            mCtrl[10] += 20/mCount;

            postInvalidateDelayed((long) mPiece);
        }
    }

    // Drawing auxiliary lines
    private void drawAuxiliaryLine(Canvas canvas) {
        // Drawing data points and control points
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);

        for (int i=0; i<8; i+=2){
            canvas.drawPoint(mData[i],mData[i+1], mPaint);
        }

        for (int i=0; i<16; i+=2){
            canvas.drawPoint(mCtrl[i], mCtrl[i+1], mPaint);
        }


        // Drawing auxiliary lines
        mPaint.setStrokeWidth(4);

        for (int i=2, j=2; i<8; i+=2, j+=4){
            canvas.drawLine(mData[i],mData[i+1],mCtrl[j],mCtrl[j+1],mPaint);
            canvas.drawLine(mData[i],mData[i+1],mCtrl[j+2],mCtrl[j+3],mPaint);
        }
        canvas.drawLine(mData[0],mData[1],mCtrl[0],mCtrl[1],mPaint);
        canvas.drawLine(mData[0],mData[1],mCtrl[14],mCtrl[15],mPaint);
    }

    // Drawing coordinate system
    private void drawCoordinateSystem(Canvas canvas) {
        canvas.save();                      // Drawing coordinate system

        canvas.translate(mCenterX, mCenterY); // Move the coordinate system to the center of the canvas
        canvas.scale(1,-1);                 // Flip Y axis

        Paint fuzhuPaint = new Paint();
        fuzhuPaint.setColor(Color.RED);
        fuzhuPaint.setStrokeWidth(5);
        fuzhuPaint.setStyle(Paint.Style.STROKE);

        canvas.drawLine(0, -2000, 0, 2000, fuzhuPaint);
        canvas.drawLine(-2000, 0, 2000, 0, fuzhuPaint);

        canvas.restore();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168

Three. Conclusion

In fact, the most important thing about Bessel curve is to understand the way of Bessel curve generation. Only by understanding the way of Bessel curve generation can we make better use of Bessel curve. At the end of the last article, I said that this article would involve a little bit of self-intersection graphics rendering. Unfortunately, this article is missing. Please look forward to the next one (possibly o()) o in the next one), and the next one is still about Path, which will teach you something more interesting.

Unlock the new realm [Draw an elastic circle]:

Topics: Android Mobile