web-dev-qa-db-fra.com

Drawing on Canvas - PorterDuff.Mode.CLEAR dessine en noir! Pourquoi?

J'essaie de créer une vue personnalisée qui fonctionne de manière simple: il existe un bitmap qui est révélé par le chemin de l'arc - de 0 à 360 degrés. Les degrés changent avec certains FPS.

J'ai donc créé une vue personnalisée avec la méthode onDraw() remplacée:

@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 est initialisé comme suit:

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

Maintenant, tout est génial, mais ... l’arrière-plan est noir dans l’ensemble.

Si je définis canvas.drawColor(..., PorterDuff.Mode.DST) et omis canvas.drawBitmap() - l'arc est dessiné correctement sur un fond transparent.

Ma question est la suivante: comment définir les modes PorterDuff pour que cela fonctionne avec la transparence?

Bien sûr, bitmap est un fichier PNG 32 bits avec canal alpha.

20
cadavre

PorterDuff.Mode.CLEAR ne fonctionne pas avec l'accélération matérielle. Il suffit de mettre 

view.setLayerType(View.LAYER_TYPE_SOFTWARE,null); 

Fonctionne parfaitement pour moi.

22
Nitesh Tarani

Utilisez cette instruction lors de l'initialisation de la vue

setLayerType(LAYER_TYPE_HARDWARE, null);
6
Mukesh Kumar

Tout est ok dans votre code sauf une chose: vous obtenez un fond noir car votre fenêtre est opaque. Pour obtenir un résultat transparent, vous devez utiliser un autre bitmap. Dans votre méthode onDraw, créez un nouveau bitmap et faites-y tout le personnel. Après cela, dessinez cette image sur votre toile. 

Pour plus de détails et un exemple de code, veuillez lire ceci ma réponse :

2
Robert

pour résoudre un effet PorterDuff non désiré
utilise la méthode la plus simple au début, comme le problème du PO, un Path.arcTo(*, *, *, *, false) suffit, notez que c'est arcTo, pas addArc, et que false signifie no forceMoveTo avant d'ajouter arc - il n'y a pas besoin de 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);
}

si vous avez vraiment besoin de PorterDuff, principalement pour le morphing de couleur complexe, comme le mélange de dégradés, ne dessinez pas de couleur ou de forme, ni de bitmap avec l'effet de filtrage PorterDuff directement sur le canevas par défaut fourni dans onDraw(Canvas), utilisez des bitmap de mise en tampon/dest avec canal alpha --et setHasAlpha(true)-- pour stocker le résultat du filtrage PorterDuff, dessinez enfin le bitmap sur le canevas par défaut sans appliquer de filtrage, sauf le changement de matrice.
Voici un exemple de travail pour créer une image arrondie avec une bordure floue: 

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);
    }
 }

quelques notes: 1, cette vue étend ImageView pas View, il y a quelques différences.
2, pourquoi drawWithLayers --saveLayer ou saveLayerAlpha-- n'est pas recommandé: a, ils sont incertains, parfois ne fonctionnent pas correctement (afficher transparent en noir), en particulier pour View whoes onDraw(Canvas) est vide, tandis que ImageView.onDraw(Canvas) a utilisé un Drawable en dessiner; b, ils coûtent cher, ils affectent off-screen bitmap au stockage des résultats de dessin temporaires, et il n’existe aucun indice clair d’un mécanisme de recyclage des ressources.
3, en utilisant votre propre bitmap [s], est préférable pour le recyclage de ressources personnalisé.

Certaines personnes ont dit qu'il était impossible d'utiliser PorterDuff sans attribution de bitmap [s] à chaque dessin, car la largeur et la hauteur du bitmap ne pouvaient pas être déterminées avant le dessin/la mise en page/la mesure.
vous POUVEZ utiliser un bitmap tampon [s] pour le dessin de PorterDuff: 
au début, allouez des bitmap assez gros.
puis, dessinez sur le bitmap [s] avec une matrice.
et le, tracez le bitmap [s] dans un bitmap de destination avec une matrice.
enfin, dessinez le bitmap de destination dans la zone de dessin avec une matrice.

Certaines personnes recommandent setLayerType (View.LAYER_TYPE_SOFTWARE, null), ce qui n'est pas une option pour moi, car l'application onDraw (Canvas) sera appelée dans une boucle.

image du résultatimage source

1
zdave

il suffit de sauvegarder la toile et de restaurer après

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

dans ce cas, le premier rectangle sera la destination de https://developer.Android.com/reference/Android/graphics/PorterDuff.Mode et le second rectangle en tant que source

0
Mykola Tychyna

Si vous avez un arrière-plan de couleur unie, il vous suffit de définir Couleur de peinture sur votre couleur d’arrière-plan. Par exemple, si vous avez un fond blanc, vous pouvez faire:

Paint.setColor(Color.WHITE);

Toutefois, si vous devez effacer une ligne avec un arrière-plan transparent, essayez ceci: Pour dessiner avec une couleur transparente, vous devez utiliser Paint setXfermode, qui ne fonctionnera que si vous définissez un bitmap sur votre canevas. Si vous suivez les étapes ci-dessous, vous devriez obtenir le résultat souhaité.

Créez une toile et définissez son bitmap.

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);
}

Vous devriez maintenant pouvoir dessiner avec une couleur transparente en utilisant:

mCanvas.drawPath(path, mPaint);
0
Ishan