web-dev-qa-db-ja.com

Androidのキャンバスのクリップ境界をアンチエイリアスするにはどうすればよいですか?

私はAndroidの Android.graphics.Canvas クラス リングを描くため を使用しています。私のonDrawメソッドは、キャンバスをクリップして内側の円に穴を開け、その穴の上に外側の円全体を描画します。

    clip = new Path();
    clip.addRect(outerCircle, Path.Direction.CW);
    clip.addOval(innerCircle, Path.Direction.CCW);

    canvas.save();
    canvas.clipPath(clip);
    canvas.drawOval(outerCircle, lightGrey);
    canvas.restore();

その結果、きれいなアンチエイリアス処理された外側のエッジとギザギザの醜い内側のエッジを持つリングが作成されます。

aliased

内側のエッジをアンチエイリアスするために何ができますか?

ダイアログが少し透明なので、真ん中に灰色の円を描いてごまかしたくありません。 (この透明性は、他の背景ではそれほど微妙ではありません。)

31
Jesse Wilson

私の知る限り、アンチエイリアスで領域をクリップすることはできません。

代わりにビットマップマスキングを使用することをお勧めします。ピンク、白、ライトグレーの前景を1つのビットマップにレンダリングし、外側/内側の円マスク(グレースケールアルファチャネル)を別のビットマップにレンダリングしてから、Paint.setXfermodeを使用してマスクを使用して前景ビットマップをレンダリングします。アルファチャネル。

例はApiDemosソースコード ここ にあります。

20
Roman Nurik

これが一般的な答えではないことはわかっていますが、この特定のケースでは、円とマスクの代わりに、太いストローク幅で円弧を描くことができます。

3
HRJ

私も同じ問題を抱えていました。ビットマップマスキング(xFermode)を使用してエイリアシングを修正しようとしましたが、重いものでした。

したがって、API <19の場合はビットマップマスキング方法を使用し、API> = 19の場合はPath.Opを使用しました。パスをクリップしてからシェイプを描画する代わりに。 pathshape(タイプはPath)のREVERSE_DIFFERENCEを実行しました。 API19以降のパスで操作を実行できます。

私にとって完璧に動作します!

1
Henry

次のコードを試すことができます。

public class GrowthView extends View {
private static final String TAG = "GrowthView";
private int bgColor = Color.parseColor("#33485d");
private int valColor = Color.parseColor("#ecb732");
private int[] scores = new int[]{0, 10, 80, 180, 800, 5000, 20000, 50000, 100000};

private Context mContext;

private float w;
private float h;

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

private int growthValue = 0;

private float bgFullAngle = 240.0f;
private float gapAngle = bgFullAngle / (scores.length - 1);

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

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

public GrowthView(Context context) {
    this(context, null);
}

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

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

private void init() {
    Xfermode xFermode = new PorterDuffXfermode(PorterDuff.Mode.DARKEN);

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

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

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

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



    bgPath = new Path();
    growthPath = new Path();

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

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

    rightBoundRecF = new RectF(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 = new RectF(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 = new Path();
    bgClipPath.arcTo(innerRecF, 150.0f, 359.9f, true);
    bgClipPath.close();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //bg
    float startAngle = 150.0f;
    float endRecfFullAngle = 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();

    //growth
    if (getGrowthVal() != 0) {
        float temp = 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);
}

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

private RectF getDynamicRecF(int growthVal) {
    float dynamicAngle = 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);
    return new RectF(_w - gapRadius, _y - gapRadius, _w + gapRadius, _y + gapRadius);
}

private int getGrowthVal() {
    return this.growthValue;
}

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

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

private int getLevel(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) {
    StringBuilder builder = new StringBuilder(String.valueOf(value));
    return value < 1000 ? builder.toString() : builder.insert(builder.length() - 3, ',').toString();
}

}

Xfermode Apiをcanvas.clipPath()で使用すると、この問題が解決する場合があります... 結果

0
Zhang Xiang