Skip to content Skip to sidebar Skip to footer

How Do I Antialias The Clip Boundary On Android's Canvas?

I'm using Android's android.graphics.Canvas class to draw a ring. My onDraw method clips the canvas to make a hole for the inner circle, and then draws the full outer circle over t

Solution 1:

As far as I know, you can't antialias clip regions.

I'd suggest using bitmap masking instead. Render the the pink, white, and light gray foreground to one bitmap, render the outer/inner circle mask (the grayscale alpha channel) to another bitmap, and then use Paint.setXfermode to render the foreground bitmap with the mask as its alpha channel.

An example can be found in the ApiDemos source code here.

Solution 2:

I know this is not a general answer, but in this particular case, you could draw arcs with a thick stroke width, instead of the circles + mask.

Solution 3:

I too had this same problem. I tried using Bitmap masking (xFermode) to fix the Aliasing, but it was heavy.

So for API < 19, I used the Bitmap masking way and for API >= 19, I used Path.Op. Instead of clipping the path and then drawing the shape. I did a REVERSE_DIFFERENCE of the path and the shape(which is of type Path). You can perform operations on Path from API 19 and above.

Works perfectly for me!

Solution 4:

you can try the following code:

publicclassGrowthViewextendsView {
privatestaticfinalStringTAG="GrowthView";
privateintbgColor= Color.parseColor("#33485d");
privateintvalColor= Color.parseColor("#ecb732");
privateint[] scores = newint[]{0, 10, 80, 180, 800, 5000, 20000, 50000, 100000};

private Context mContext;

privatefloat w;
privatefloat h;

private Paint bgPaint;
private Paint growthPaint;
private Paint textPaint;
private Paint clipPaint;
private Path bgPath;
private Path bgClipPath;
private Path growthPath;

privateintgrowthValue=0;

privatefloatbgFullAngle=240.0f;
privatefloatgapAngle= bgFullAngle / (scores.length - 1);

privatefloatgapRadius=21.5f;//实际为21px 略大半个像素避免path无法缝合errorprivatefloatouterRadius=240.0f;
privatefloatinnerRadius= outerRadius - gapRadius * 2;

private RectF outerRecF;
private RectF innerRecF;
private RectF leftBoundRecF;
private RectF rightBoundRecF;

publicGrowthView(Context context) {
    this(context, null);
}

publicGrowthView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

publicGrowthView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    this.mContext = context;
    init();
}

privatevoidinit() {
    XfermodexFermode=newPorterDuffXfermode(PorterDuff.Mode.DARKEN);

    bgPaint = newPaint();
    bgPaint.setStyle(Paint.Style.FILL);
    bgPaint.setColor(bgColor);
    bgPaint.setStrokeWidth(0.1f);
    bgPaint.setAntiAlias(true);

    growthPaint = newPaint();
    growthPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    growthPaint.setColor(valColor);
    growthPaint.setStrokeWidth(1f);
    growthPaint.setAntiAlias(true);

    clipPaint = newPaint();
    clipPaint.setStyle(Paint.Style.FILL);
    clipPaint.setColor(Color.WHITE);
    clipPaint.setStrokeWidth(.1f);
    clipPaint.setAntiAlias(true);
    clipPaint.setXfermode(xFermode);

    textPaint = newPaint();
    textPaint.setTextSize(96);//todo comfirm the textSize
    textPaint.setStrokeWidth(1f);
    textPaint.setAntiAlias(true);
    textPaint.setTextAlign(Paint.Align.CENTER);
    textPaint.setColor(valColor);



    bgPath = newPath();
    growthPath = newPath();

    //todo 暂定中心点为屏幕中心DisplayMetricsmetrics=newDisplayMetrics();
    WindowManagerwm= (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    wm.getDefaultDisplay().getMetrics(metrics);
    w = metrics.widthPixels;
    h = metrics.heightPixels;

    outerRecF = newRectF(w / 2 - outerRadius, h / 2 - outerRadius, w / 2 + outerRadius, h / 2 + outerRadius);
    innerRecF = newRectF(w / 2 - innerRadius, h / 2 - innerRadius, w / 2 + innerRadius, h / 2 + innerRadius);

    rightBoundRecF = newRectF(w / 2 + (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 - gapRadius,
            h / 2 + (innerRadius + gapRadius) / 2 - gapRadius,
            w / 2 + (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 + gapRadius,
            h / 2 + (innerRadius + gapRadius) / 2 + gapRadius);

    leftBoundRecF = newRectF(w / 2 - (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 - gapRadius,
            h / 2 + (innerRadius + gapRadius) / 2 - gapRadius,
            w / 2 - (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 + gapRadius,
            h / 2 + (innerRadius + gapRadius) / 2 + gapRadius);

    bgClipPath = newPath();
    bgClipPath.arcTo(innerRecF, 150.0f, 359.9f, true);
    bgClipPath.close();
}

@OverrideprotectedvoidonDraw(Canvas canvas) {
    super.onDraw(canvas);
    //bgfloatstartAngle=150.0f;
    floatendRecfFullAngle=180.0f;
    bgPath.arcTo(outerRecF, startAngle, bgFullAngle, true);
    bgPath.arcTo(rightBoundRecF, 30.0f, endRecfFullAngle, true);
    bgPath.arcTo(innerRecF, startAngle, bgFullAngle);
    bgPath.arcTo(leftBoundRecF, -30.0f, endRecfFullAngle);
    bgPath.rMoveTo(w / 2 - outerRadius * (float) Math.pow(3, 0.5) / 2, h / 2 + outerRadius / 2);
    bgPath.setFillType(Path.FillType.WINDING);
    bgPath.close();

    //growthif (getGrowthVal() != 0) {
        floattemp= getGrowthAngle(getGrowthVal());
        growthPath.arcTo(outerRecF, startAngle, temp, true);
        growthPath.arcTo(getDynamicRecF(getGrowthVal()), getDynamicOriginAngle(getGrowthVal()), endRecfFullAngle, true);
        growthPath.arcTo(innerRecF, startAngle, temp);
        growthPath.arcTo(leftBoundRecF, -30.0f, endRecfFullAngle);
        growthPath.rMoveTo(w / 2 - outerRadius * (float) Math.pow(3, 0.5) / 2, h / 2 + outerRadius / 2);
        growthPath.close();
    }
    canvas.drawText(formatVal(getGrowthVal()), w / 2, h / 2, textPaint);
    canvas.clipPath(bgClipPath, Region.Op.DIFFERENCE);
    canvas.drawPath(bgPath, bgPaint);
    canvas.drawPath(growthPath, growthPaint);
    canvas.drawPath(bgClipPath, clipPaint);
}

privatefloatgetDynamicOriginAngle(int growthVal) {
    return growthVal <= 30 ? getGrowthAngle(growthVal) + 150 :
            getGrowthAngle(growthVal) - 210;
}

private RectF getDynamicRecF(int growthVal) {
    floatdynamicAngle= getGrowthAngle(growthVal);
    //动态圆心float_w= w / 2 + (float) Math.sin(Math.toRadians(dynamicAngle - 120)) * (outerRadius - gapRadius);
    float_y= h / 2 - (float) Math.sin(Math.toRadians(dynamicAngle - 30)) * (outerRadius - gapRadius);
    returnnewRectF(_w - gapRadius, _y - gapRadius, _w + gapRadius, _y + gapRadius);
}

privateintgetGrowthVal() {
    returnthis.growthValue;
}

publicvoidsetGrowthValue(int value) {
    if (value < 0 || value > 100000) {
        try {
            thrownewException("成长值不在范围内");
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            e.printStackTrace();
        }
    }
    this.growthValue = value;
    invalidate();
}

privatefloatgetGrowthAngle(int growthVal) {
    return gapAngle * (getLevel(growthVal) - 1)
            + gapAngle * (growthVal - scores[getLevel(growthVal) - 1]) /
            (scores[getLevel(growthVal)] - scores[getLevel(growthVal) - 1]);
}

privateintgetLevel(int score) {
    return score < 0 ? -1 : score <= 10 ? 1 : score <= 80 ? 2 : score <= 180 ? 3 : score <= 800 ?
            4 : score <= 5000 ? 5 : score <= 20000 ? 6 : score <= 50000 ? 7 : 8;
}

private String formatVal(int value) {
    StringBuilderbuilder=newStringBuilder(String.valueOf(value));
    return value < 1000 ? builder.toString() : builder.insert(builder.length() - 3, ',').toString();
}

}

Use the Xfermode Api with canvas.clipPath() may resolve this problem... Result

Post a Comment for "How Do I Antialias The Clip Boundary On Android's Canvas?"