Original article, reprint please contact the author

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

The Demo implementation effect, reference is windwOS drawing application. First let’s look at the effect of the drawing board:

  • 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

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; [] randomPoint = getRandomPoint(x, y, mPenW, true); float[] 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 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 circleWe 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 shown above


It is obvious that in the X-axis direction, the left and right ends are much denser than the center of the circle


Random values have regularity under a large number of data, which can be understood as 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:

It is obvious that the density at the center is higher than that at the edge. 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.


The smaller the r, the smaller the area occupied, the more dense the particles appear.

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:

The visual effect was finally achieved
uniformThe effect of this algorithm is to take advantage of the properties of a root function, as shown below:
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, which is calculated from the width and height of the attached View, or the w and H that the Pen gets when it initializes. 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.

// Float stepDis = mPenR * 1.6f; Int v = (int) (getLastDis()/stepDis); float gapX = getPoints().get(getPoints().size() - 1).x - getPoints().get(getPoints().size() - 2).x; float gapY = getPoints().get(getPoints().size() - 1).y - getPoints().get(getPoints().size() - 2).y; For (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()); } private static float calculate(int index, int min) calculate(x- (min+ Max) /2)^2/ (min- (min+ Max) /2)^2 Int Max) {float maxProbability = 0.6f; Float minProbability = 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 { return ratio; }}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 mathematics left tears of regret…). The address of the brush project is here, and the comments in the code are a little clearer, so if you like it, give it a thumbs up

The original is not easy, we pass by to see the happy, can be appropriate to give a MAO or two MAO chat

References:


0

Markdown is supported

Be the first guy leaving a comment!