web-dev-qa-db-ja.com

キャンバスに描画-PorterDuff.Mode.CLEARは黒を描画します!どうして?

シンプルに機能するカスタムビューを作成しようとしています。0度から360度までの円弧パスによって表示されるビットマップがあります。一部のFPSでは学位が変化しています。

そこで、オーバーライドされたonDraw()メソッドを使用してカスタムビューを作成しました。

_@Override
protected void onDraw(Canvas canvas) {

    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    arcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
    canvas.drawArc(arcRectF, -90, currentAngleSweep, true, arcPaint);
    arcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(bitmap, circleSourceRect, circleDestRect, arcPaint);
}
_

arcPaintは次のように初期化されます。

_arcPaint = new Paint();
arcPaint.setAntiAlias(true);
arcPaint.setColor(Color.RED); // Color doesn't matter
_

さて、すべてが素晴らしく描かれていますが...背景は全体ビューで黒です。

canvas.drawColor(..., PorterDuff.Mode.DST)を設定してcanvas.drawBitmap()を省略した場合-円弧は透明な背景に適切に描画されます。

私の質問は-透過性で動作するようにPorterDuffモードを設定する方法は?

もちろんbitmapは、アルファチャネル付きの32ビットPNGです。

24
cadavre

PorterDuff.Mode.CLEARは、ハードウェアアクセラレーションでは機能しません。設定するだけ

view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 

私にとっては完璧に機能します。

38
Nitesh Tarani

ビューの初期化中にこのステートメントを使用します

setLayerType(LAYER_TYPE_HARDWARE, null);
11
Mukesh Kumar

1つを除いて、コードはすべて問題ありません。ウィンドウが不透明であるため、背景が黒くなります。透明な結果を得るには、別のビットマップに描画する必要があります。 onDrawメソッドで、新しいビットマップを作成し、それに対してすべてのスタッフを実行してください。その後、このビットマップをキャンバスに描画します。

詳細とサンプルコードについては、 this my answer をお読みください。

2
Robert

望ましくないPorterDuff効果を解決する
OPの問題のように、最初は最も単純な方法を使用します。Path.arcTo(*, *, *, *, false)で十分です。arcToではなくaddArcであり、falseは、弧を追加する前の_no forceMoveTo_を意味します-PorterDuffは必要ありません。

_Path arcPath = new Path();
@Override
protected void onDraw(Canvas canvas) {
    arcPath.rewind();
    arcPath.moveTo(arcRectF.centerX, arcRectF.centerY);
    arcPath.arcTo(arcRectF, -90, currentAngleSweep, false);
    arcPath.close();
    canvas.clipPath(arcPath, Region.Op.DIFFERENCE);
    canvas.drawBitmap(bitmap, circleSourceRect, circleDestRect, arcPaint);
}
_

グラデーションのブレンドなど、主に複雑なカラーモーフィングのためにPorterDuffが本当に必要な場合は、PorterDuffフィルタリング効果を使用して色または形状またはビットマップをonDraw(Canvas)で提供されるデフォルトのキャンバスに直接描画せず、バッファリング/宛先ビットマップを使用してください[s]アルファチャネルとsetHasAlpha(true)--を使用してPorterDuffフィルタリングの結果を保存し、最後に、マトリックスの変更以外のフィルタリングを適用せずに、ビットマップをデフォルトのキャンバスに描画します。
これは、境界線がぼやけた丸い画像を作成するための実用的な例です:

_import Android.annotation.SuppressLint;
import Android.content.Context;
import Android.graphics.Bitmap;
import Android.graphics.Canvas;
import Android.graphics.Color;
import Android.graphics.Matrix;
import Android.graphics.Paint;
import Android.graphics.Path;
import Android.graphics.PorterDuff;
import Android.graphics.PorterDuffXfermode;
import Android.graphics.RadialGradient;
import Android.graphics.Rect;
import Android.graphics.RectF;
import Android.graphics.Shader;
import Android.support.annotation.Nullable;
import Android.util.AttributeSet;
import Android.widget.ImageView;

/**
* Created by zdave on 6/22/17.
*/

public class BlurredCircleImageViewShader extends ImageView {
private Canvas mCanvas;
private Paint mPaint;
private Matrix matrix;
private static final float GRADIENT_RADIUS = 600f;  //any value you like, but should be big enough for better resolution.
private Shader gradientShader;
private Bitmap bitmapGradient;
private Bitmap bitmapDest;
private Canvas canvasDest;
public BlurredCircleImageViewShader(Context context) {
    this(context, null);
}

public BlurredCircleImageViewShader(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

public BlurredCircleImageViewShader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    matrix = new Matrix();
    int[] colors = new int[]{Color.BLACK, Color.BLACK, Color.TRANSPARENT};
    float[] colorStops = new float[]{0f, 0.5f, 1f};
    gradientShader = new RadialGradient(GRADIENT_RADIUS, GRADIENT_RADIUS, GRADIENT_RADIUS, colors, colorStops, Shader.TileMode.CLAMP);
    mPaint.setShader(gradientShader);

    bitmapGradient = Bitmap.createBitmap((int)(GRADIENT_RADIUS * 2), (int)(GRADIENT_RADIUS * 2), Bitmap.Config.ARGB_8888);
    bitmapDest = bitmapGradient.copy(Bitmap.Config.ARGB_8888, true);

    Canvas canvas = new Canvas(bitmapGradient);
    canvas.drawRect(0, 0, GRADIENT_RADIUS * 2, GRADIENT_RADIUS * 2, mPaint);

    canvasDest = new Canvas(bitmapDest);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getMeasuredWidth();
    setMeasuredDimension(width, width);
}

@Override
protected void onDraw(Canvas canvas){
    /*uncomment each of them to show the effect, the first and the third one worked, the second show the same problem as OP's*/
    //drawWithLayers(canvas);  //unrecommended.
    //drawWithBitmap(canvas);  //this shows transparent as black
    drawWithBitmapS(canvas);   //recommended.
}
@SuppressLint("WrongCall")
private void drawWithLayers(Canvas canvas){
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    float width = canvas.getWidth();
    float hWidth = width / 2;
    //both saveLayerAlpha saveLayer worked here, and if without either of them,
    //the transparent area will be black.
    //int count = canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), 255, Canvas.ALL_SAVE_FLAG);
    int count = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
    super.onDraw(canvas);
    float scale = hWidth/GRADIENT_RADIUS;
    matrix.setTranslate(hWidth - GRADIENT_RADIUS, hWidth - GRADIENT_RADIUS);
    matrix.postScale(scale, scale, hWidth, hWidth);
    gradientShader.setLocalMatrix(matrix);

    canvas.drawRect(0, 0, width, width, mPaint);

    canvas.restoreToCount(count);
}
@SuppressLint("WrongCall")
private void drawWithBitmap(Canvas canvas){
    super.onDraw(canvas);
    float scale = canvas.getWidth() / (GRADIENT_RADIUS * 2);
    matrix.setScale(scale, scale);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvas.drawBitmap(bitmapGradient, matrix, mPaint);  //transparent area is still black.
}
@SuppressLint("WrongCall")
private void drawWithBitmapS(Canvas canvas){
    float scale = canvas.getWidth() / (GRADIENT_RADIUS * 2);
    int count = canvasDest.save();
    canvasDest.scale(1/scale, 1/scale); //tell super to draw in 1/scale.
    super.onDraw(canvasDest);
    canvasDest.restoreToCount(count);

    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvasDest.drawBitmap(bitmapGradient, 0, 0, mPaint);

    matrix.setScale(scale, scale);  //to scale bitmapDest to canvas.
    canvas.drawBitmap(bitmapDest, matrix, null);
    }
 }
_

注:1、このビューはImageViewではなくViewを拡張します。いくつかの違いがあります。
2、drawWithLayers --saveLayerまたはsaveLayerAlpha--が推奨されない理由:a、それらは不確かであり、正しく機能しない場合があります(黒)、特にViewの場合、whoes onDraw(Canvas)は空ですが、ImageView.onDraw(Canvas)Drawableを使用して描画します。 b、それらは高価であり、一時的な描画結果を保存するために_off-screen bitmap_を割り当てます。また、リソースリサイクルメカニズムの明確な手掛かりはありません。
独自のビットマップを使用する3は、カスタマイズされたリソースのリサイクルに適しています。

一部の人々は、ビットマップの幅、高さは描画/レイアウト/測定する前に決定できないため、すべての描画にビットマップを割り当てずにPorterDuffを使用することは不可能だと述べました。
PorterDuffの描画にバッファリングビットマップを使用できます:
最初に、十分に大きなビットマップを割り当てます。
次に、ビットマップにいくつかの行列を描画します。
そして、ビットマップをいくつかの行列を含むdestビットマップに描画します。
最後に、何らかのビットマップを使用して、destビットマップをキャンバスに描画します。

一部の人々はonLayerType(View.LAYER_TYPE_SOFTWARE、null)をお勧めします。これは、onDraw(Canvas)がループで呼び出されるため、私にとってはオプションではありません。

結果画像ソース画像

1
zdave

キャンバスを保存して後で復元するだけです

canvas.saveLayer(clipContainer, null, Canvas.ALL_SAVE_FLAG);
canvas.drawRoundRect(rectTopGrey, roundCorners, roundCorners, greyPaint);
canvas.drawRoundRect(rectTopGreyClip, roundCorners, roundCorners, clipPaint);
canvas.restore();

この場合、最初の長方形は https://developer.Android.com/reference/Android/graphics/PorterDuff.Mode の宛先として、2番目の長方形はソースとして

0
Mykola Tychyna

単色の背景がある場合は、ペイント色を背景色に設定するだけです。たとえば、白い背景の場合は次のようにできます。

Paint.setColor(Color.WHITE);

ただし、透明な背景で線を消去する必要がある場合は、次のことを試してください。透明な色で描画するには、ビットマップをキャンバスに設定した場合にのみ機能するPaint setXfermodeを使用する必要があります。以下の手順に従うと、望ましい結果が得られるはずです。

キャンバスを作成し、そのビットマップを設定します。

mCanvas = new Canvas();
mBitmap= Bitmap.createBitmap(scrw, scrh, Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);
When you want to erase something you just need to use setXfermode.

public void onClickEraser() 
{ 
   if (isEraserOn)
      mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
   else
      mPaint.setXfermode(null);
}

これで、次を使用して透明色で描画できるはずです。

mCanvas.drawPath(path, mPaint);
0
Ishan