Based on Java code implementation, and with the corresponding Kotlin version of the original article, please contact the author for reprint

Soft grass flat sha rain new, light sand road dust-free. When do you pack up your crops?

First, the effect picture:

The address of the brush project is here. If you like it, please give it a thumbs up

Effect of parsing

Because the final goal is to achieve the windwOS paintbrush palette, so first of all to do a more detailed effect analysis. Considering the point of use of brushes in general, the effect details of points and lines will be analyzed.

  • Draw some


From left to right is the same coordinate click 2 times, click 8 times, click 16 times effect display; When the number tends to be larger, the density of the points does not have a significant bias, and it can be basically determined to evenly distribute in the circle

  • Line drawing

As shown in the figure, lines are formed by points when sliding at uniform speed and slowly

The specific implementation

The general framework of the project consists of View and BasePen, two large modules. View belongs to THE UI level and BasePen belongs to the business logic level. Next, the specific functions and details of these two modules will be introduced one by one.

View

The host View of this project is PenView, which does not undertake business logic, but acts as a container. The only function in PenView is to trigger the invalidate () method.

private BasePen mBasePen;

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if(w ! = 0 && h ! = 0) {if (mBasePen == null) {
                mBasePen = new SprayPen(w, h);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        MotionEvent event1 = MotionEvent.obtain(event);
        mBasePen.onTouchEvent(event1);
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mBasePen.onDraw(canvas);
    }
Copy the code

BasePen and its subclasses implement the business logic of drawing, calculating data, moving touch points, and so on. Low coupling, which represents more freedom, has less impact on existing project code (if applied to a project). In terms of performance, if the View doesn’t meet the requirements, it can be ported to a better performance SurfaceView at a lower cost.

The business logic

In terms of business, BasePen, as the base class, undertakes some basic data calculation, drawing and other functions, while the specific brush effect is implemented by the subclass. Here’s what BasePen does:

  • draw
private List<Point> mPoints;
public void onDraw(Canvas canvas) {
        if(mPoints ! = null && ! mPoints.isEmpty()) { canvas.drawBitmap(mBitmap, 0, 0, null); drawDetail(canvas); }}Copy the code

First draw the brush onto a Bitmap, and then hand the Bitmap to PenView to draw. Point is a class that records only x and y coordinates. DrawDetail (Canvas) is an abstract class that uses subclasses to do concrete drawing.

  • Slide trackBasePentheonTouchEvent(MotionEvent event1)In the method. In every timeDOWNEvent for start, recordMOVEAll coordinate information in. Taking into account the spray paint effect, there is no need to deal with itEffect of pen, ignore the record for the time beingUPInfo (this will be optimized if other brushes are implemented later).
public void onTouchEvent(MotionEvent event1) {
        switch (event1.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                clearPoints();
                handlePoints(event1);
                break;
            case MotionEvent.ACTION_MOVE:
                handlePoints(event1);
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
    }

    private void handlePoints(MotionEvent event1) {
        float x = event1.getX();
        float y = event1.getY();
        if (x > 0 && y > 0) {
            mPoints.add(new Point(x, y));
        }
    }
    
    private void clearPoints() {
        if (mPoints == null) {
            return;
        }
        mPoints.clear();
    }
Copy the code
  • Spray paint to achieve
protected void drawDetail(Canvas canvas) {
        if (getPoints().isEmpty()) {
            return; } mTotalNum = drawSpray(x, y, mTotalNum); } private void drawSpray(float x, float y, int totalNum) {
        for(int i = 0; i < totalNum; I++) {// the algorithm computes random points in a circlefloat[] randomPoint = getRandomPoint(x, y, mPenW, true); mCanvas.drawCircle(randomPoint[0], randomPoint[1], mCricleR, mPaint); }}Copy the code

The above is part of the pseudocode. SprayPen defines a particle density that changes the number of particles in real time according to the width of the brush. The radius of each particle is calculated from the width provided by the externally dependent component. In drawDetail (…). In the method, each MOVE and DOWN event will draw a certain number of random points in the circle at the corresponding coordinates. When they are connected in series, the spray paint effect is formed. Of course, this is just preliminary work, there are still some algorithms to perfect. The pseudo-code is not fully expressed, please refer to SprayPen, there are relatively perfect annotations in the code.

Next, we will talk about some problems related to the spray painting algorithm.

Several problems of spray painting algorithm

There are two issues worth documenting in implementing the functionality. One is the distribution of uniform random points in a circle. Second, when sliding speed is fast, stroke connection processing problem.

How to uniformly generate random points in a circle

In order to solve this problem, three main methods have been tried:

X takes a random value in the range minus R,R, given by the circle

We solve for y. And then I take a random value of y in negative y,y, and that’s the point inside the circle. And similarly, we can figure out x from y.

The Java code is as follows:

float x = mRandom.nextInt(r);
float y = (float) Math.sqrt(Math.pow(r, 2) - Math.pow(x, 2)); y = mRandom.nextInt((int) y); X = randomly take positive or negative (x) of the values; Y = take positive or negative (y) randomly;Copy the code

The final rendering looks like this:

When the number of samples reaches 2000, the shape is as shown above. It can be clearly seen that in the X-axis direction, the density of the left and right ends is significantly higher than that of the random values at the center of the circle. The random values have regularity under a large number of data, which can be interpreted as that when there are many data, the value of x is roughly evenly distributed at (-r,r), and the value of y is also. When you’re on the left and the right, you get a smaller range of y, and it’s a little bit more compact. Of course, it would be more convincing if the probability theory mathematical statistics formula were used to verify, but unfortunately not… (shrug)

Random Angle, the Angle is randomly obtained within [0,360), and then randomly evaluated within [0,r], then usedsinandcosTo solve for x and y.

The Java code is as follows:

float[] ints = new float[2];
int degree = mRandom.nextInt(360);
double curR = mRandom.nextInt(r)+1;
float x = (float) (curR * Math.cos(Math.toRadians(degree)));
float y = (float) (curR * Math.sin(Math.toRadians(degree))); X = randomly take positive or negative (x) of the values; Y = take positive or negative (y) randomly;Copy the code

The final rendering looks like this:

In fact, when the Angle is fixed, r takes a random value within the range of [0, r). When the number is larger, the coordinate points are evenly distributed. When r is smaller, the area occupied is smaller, and the particles appear to be very dense.

Random angles, take random angles within [0,360], take the random square root of [0,1] and multiply it by R, then usesinandcosTo solve for x and y.

The Java code is as follows:

int degree = mRandom.nextInt(360);
double curR = Math.sqrt(mRandom.nextDouble()) * r;
float x = (float) (curR * Math.cos(Math.toRadians(degree)));
float y = (float) (curR * Math.sin(Math.toRadians(degree))); X = randomly take positive or negative (x) of the values; Y = take positive or negative (y) randomly;Copy the code

The final rendering looks like this:

This time, the visual effect is uniform. This algorithm makes use of the characteristics of a root function, as shown in the following figure:

Red is the root function, blue is the linear function. So the root function is going to be a little bit bigger, and correspondingly, there are going to be a little bit more points near the edges, so that the distribution of the particles is going to be a little bit more even.

Deal with the situation of “writing hard”

When sliding at a relatively slow speed, the strokes are smooth without obvious faults. When moving too fast, MOVE leaves fewer points and more space between them. The phenomenon of brush fault occurs, which requires some special treatment methods. The code sets a standard value, D, calculated from the values w and H held by BasePen, which are generally expected to be the width and height of the attached View. It was also initially considered to use the diameter of the brush for calculation, but considering that the brush diameter can be dynamically changed externally. It is better to keep some independence of the standard value, the more stable the data it depends on, otherwise it will affect the balance. Then, when moving, when the relative distance between the current point and the next point is greater than the standard value D, it will be judged to be in a fast moving state. The greater the distance, the faster the moving speed, so the spray painting effect will be weakened accordingly [intuitively speaking, the particle concentration will be low]. When moving fast, the code simulates handwriting points between the current point and the previous point. Accordingly, the particle density of these handwriting points is lower, and the calculation function is an anti-hump change state. That is, the middle of the continuous handwriting point particle is the most sparse, the two sides are the most dense.

// When the hand speed is too fastfloatStepDis = mPenR * 1.6f; Int v = (int) (getLastDis()/stepDis);float gapX = getPoints().get(getPoints().size() - 1).x - getPoints().get(getPoints().size() - 2).x;
floatgapY = getPoints().get(getPoints().size() - 1).y - getPoints().get(getPoints().size() - 2).y; // Draw handwriting pointsfor (int i = 1; i <= v; i++) {
 	float x = (float) (getPoints().get(getPoints().size() - 2).x + (gapX * i * stepDis / getLastDis()));
    float y = (float) (getPoints().get(getPoints().size() - 2).y + (gapY * i * stepDis / getLastDis())); drawSpray(x, y, (int) (mTotalNum * calculate(i, 1, v)), mRandom.nextBoolean()); } /** * use (x- (min+ Max) /2)^2/ (min- (min+ Max) /2)^2 as particle density ratio function */ private staticfloat calculate(int index, int min, int max) {
        floatMaxProbability = 0.6 f;floatMinProbability = 0.15 f;if (max - min + 1 <= 4) {
            return maxProbability;
        }
        int mid = (max + min) / 2;
        int maxValue = (int) Math.pow(mid - min, 2);
        float ratio = (float) (Math.pow(index - mid, 2) / maxValue);
        if (ratio >= maxProbability) {
            return maxProbability;
        } else if (ratio <= minProbability) {
            return minProbability;
        } else {
            returnratio; }}Copy the code

Kotlin

While writing this project, I also wrote a version of Kotlin’s. Note that this is not done using the code that comes with AS. So the Kotlin version will have a lot of unnecessary test experience code, so don’t worry about the details. Kotlin version here, here, if you like it, please give it a thumbs up

conclusion

Above is the idea of this Demo, as well as some algorithm analysis. The beauty of mathematics is intoxicating * (The students who failed in math left tears of regret…) The address of the brush project is here, and the comments in the code will be clearer. If you like it, please give it a thumbs up

Welcome to follow my public account, technology and life

References:

  • Uniformly generate random points in circles and triangles