Canvas.drawArc() artefacts

2019-03-13 12:18发布

问题:

I draw an arc on canvas in a custom view as shown below. Paint and rectangle are defined outside of onDraw() and added in there for simplicity purpose.

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

    RectF rectangle = new RectF(60f, 60f, 480f, 480f);

    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(0x40000000);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(120);

    canvas.drawArc(rectangle, 225f, 315f, false, paint);
}

When I run this code on Galaxy Nexus with 4.3, there are following artefacts.

There is no such artefacts when running on Nexus 5 with 4.4.4 though.

I observed such artefacts by angles like (225f, 315f) and some other angles only. Most of the time the arc has a correct shape.

Is there a way to avoid those artefacts?

Update: I tried to use software, hardware and none layers using setLayerType(). Artefacts changed their form, but were still present.

回答1:

I'm a StackOverflow newbie, wanted to add a comment but could not (having insufficient points) so had to put my comment in an answer !

One strange thing is that the arc overshoots the specified end position with a straight vertical line at the outer side. The inner end point seems to be ok. Of course this and the other mess of lines does not tell what causes the problem.

It seems to appear when the end angle is at exactly a multiple of 90 degrees. This looks like a numeric calculation bug, float round-off issue etc. @kcoppock remarked that 314.98f already circumvents the bug. Probably any value other than exactly 315.0f may do the trick.

If code optimization is involved (attempts to draw the arc in as little line segments as possible) another trick that may work is to simplify the arc by cutting it into pieces -> use multiple drawArc calls that each span a certain maximum angle. Candidates are 30, 45, 90 and 180 degrees. Remains to be seen if the joints will be invisible...

It's a bit of a long shot, hope any of these suggestions might work.



回答2:

I've found the solution! :)

Instead of this:

RectF rectangle = new RectF(60f, 60f, 480f, 480f);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0x40000000);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(120);

canvas.drawArc(rectangle, 225f, 315f, false, paint);

Use this:

RectF rectangle = new RectF(60f, 60f, 480f, 480f);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0x40000000);
paint.setStyle(Paint.Style.FILL);

float strokeWidth = 120;
float startAngle = 225f;
float sweepAngle = 315f;

RectF pathRect = new RectF();
Path path = new Path();
float s = strokeWidth / 2;
pathRect.left = rectangle.left - s;
pathRect.right = rectangle.right + s;
pathRect.top = rectangle.top - s;
pathRect.bottom = rectangle.bottom + s;
path.arcTo(pathRect, startAngle, sweepAngle);
pathRect.left = rectangle.left + s;
pathRect.right = rectangle.right - s;
pathRect.top = rectangle.top + s;
pathRect.bottom = rectangle.bottom - s;
path.arcTo(pathRect, startAngle + sweepAngle, -sweepAngle);
path.close();
canvas.drawPath(path, paint);

This code results in slower run, but it doesn't produce artefacts. :)