web-dev-qa-db-fra.com

PorterduffXfermode: Effacer une section d'un bitmap

Le but est simplement de dessiner un bitmap et au-dessus de celui-ci, dessinez des formes qui effacent la zone sous-jacente du bitmap.

J'ai créé un code de preuve de concept simple pour essayer de comprendre comment je devrais y parvenir. Dans les différentes discussions ici, j'ai trouvé de nombreux conseils sur l'utilisation de:

Android.graphics.PorterDuff.Mode.CLEAR

Le code ci-dessous crée simplement un écran avec un arrière-plan bleu et ajoute une vue personnalisée. Cette vue dessine sur sa toile un fond rose, l’image bitmap (avec une légère bordure pour montrer le fond rose) et des cercles superposés en jaune représentant chaque PorterDuffXfermode.

import Android.app.Activity;
import Android.content.Context;
import Android.graphics.Bitmap;
import Android.graphics.BitmapFactory;
import Android.graphics.Canvas;
import Android.graphics.Color;
import Android.graphics.Paint;
import Android.graphics.PorterDuffXfermode;
import Android.graphics.Paint.Style;
import Android.graphics.PorterDuff.Mode;
import Android.graphics.RectF;
import Android.os.Bundle;
import Android.view.View;
import Android.view.Window;
import Android.widget.RelativeLayout;

public class Test extends Activity {
    Drawing d = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        RelativeLayout.LayoutParams rlp = null;

        // Create the view for the xfermode test
        d = new Drawing(this);
        rlp = new RelativeLayout.LayoutParams(600, 900);
        rlp.addRule(RelativeLayout.CENTER_IN_PARENT);
        d.setLayoutParams(rlp);

        RelativeLayout rl = new RelativeLayout(this);
        rl.setBackgroundColor(Color.rgb(0, 0, 255));
        rl.addView(d);

        // Show the layout with the test view
        setContentView(rl);
    }

    public class Drawing extends View {
        Paint[] pDraw = null;
        Bitmap bm = null;

        public Drawing(Context ct) {
            super(ct);

            // Generate bitmap used for background
            bm = BitmapFactory.decodeFile("mnt/sdcard/Pictures/test.jpg");

            // Generate array of paints
            pDraw = new Paint[16];

            for (int i = 0; i<pDraw.length; i++) {
                pDraw[i] = new Paint();
                pDraw[i].setARGB(255, 255, 255, 0);
                pDraw[i].setStrokeWidth(20);
                pDraw[i].setStyle(Style.FILL);
            }

            // Set all transfer modes
            pDraw[0].setXfermode(new PorterDuffXfermode(Mode.CLEAR));
            pDraw[1].setXfermode(new PorterDuffXfermode(Mode.DARKEN));
            pDraw[2].setXfermode(new PorterDuffXfermode(Mode.DST));
            pDraw[3].setXfermode(new PorterDuffXfermode(Mode.DST_ATOP));
            pDraw[4].setXfermode(new PorterDuffXfermode(Mode.DST_IN));
            pDraw[5].setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
            pDraw[6].setXfermode(new PorterDuffXfermode(Mode.DST_OVER));
            pDraw[7].setXfermode(new PorterDuffXfermode(Mode.LIGHTEN));
            pDraw[8].setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
            pDraw[9].setXfermode(new PorterDuffXfermode(Mode.SCREEN));
            pDraw[10].setXfermode(new PorterDuffXfermode(Mode.SRC));
            pDraw[11].setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP));
            pDraw[12].setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
            pDraw[13].setXfermode(new PorterDuffXfermode(Mode.SRC_OUT));
            pDraw[14].setXfermode(new PorterDuffXfermode(Mode.SRC_OVER));
            pDraw[15].setXfermode(new PorterDuffXfermode(Mode.XOR));
        }

        @Override
        public void onDraw(Canvas canv) {
            // Background colour for canvas
            canv.drawColor(Color.argb(255, 255, 0, 255));

            // Draw the bitmap leaving small outside border to see background
            canv.drawBitmap(bm, null, new RectF(15, 15, getMeasuredWidth() - 15, getMeasuredHeight() - 15), null);

            float w, h;
            w = getMeasuredWidth();
            h = getMeasuredHeight();

            // Loop, draws 4 circles per row, in 4 rows on top of bitmap
            // Drawn in the order of pDraw (alphabetical)
            for(int i = 0; i<4; i++) {
                for(int ii = 0; ii<4; ii++) {
                    canv.drawCircle((w / 8) * (ii * 2 + 1), (h / 8) * (i * 2 + 1), w / 8 * 0.8f, pDraw[i*4 + ii]);
                }
            }
        }
    }

}

Voici le résultat du test:

enter image description here

Le mode CLEAR est en haut à gauche, ce qui apparaît en noir.

Dans un autre exemple où j'essayais d'utiliser ceci, j'avais un DialogFragment où le mode CLEAR effaçait tout le DialogFragment afin que l'activité en dessous puisse être vue. D'où la raison pour laquelle j'ai utilisé beaucoup de couleurs de fond différentes dans ce test.

Est-ce que cela pourrait effacer les pixels de toute l'activité, comme cela m'a été laissé croire par d'autres exemples? J'aurais pensé que seuls les pixels de la toile liés à la vue pourraient être effacés, mais dans mon autre exemple, les pixels de la vue personnalisée, de la vue d'image sous-jacente et de l'arrière-plan DialogFragment ont tous été effacés.

Quelqu'un pourrait-il m'aider à comprendre ce qui se passe exactement et pourquoi je me trompe si terriblement?

Merci. 

EDIT: .__ J'ai reproduit un exemple qui confirme mes soupçons. Lorsque vous ajoutez cette vue personnalisée exacte, mais dans un DialogFragment, l'activité sous-jacente devient visible.

enter image description here

Cela semble être un indicateur assez clair pour moi que le Mode.CLEAR efface également la toile des vues situées en dessous? Je suppose que le noir dans le premier exemple est celui de la vue de niveau supérieur? 

Je pense que je fais quelque chose de très mauvais quelque part: S

28
B T

Le problème est l'accélération matérielle. Désactivez-le pour la vue particulière que vous peignez avec CLEAR. Si vous utilisez une vue personnalisée, procédez comme suit dans les constructeurs:

if (Android.os.Build.VERSION.SDK_INT >= 11) 
{
     setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

Vous pouvez également appeler setLayerType sur une référence de vue.

34
metaphyze

Je ne vois rien d'inattendu. Dans le cas particulier de Mode.CLEAR, la couleur et l'alpha de la destination sont effacés, ce qui permet d'afficher l'arrière-plan noir. Cet utilitaire permet d’expérimenter divers modes, couleurs et valeurs alpha, et la source peut offrir un aperçu. Dans l'image (quelque peu datée) ci-dessous, les zones CLEAR révèlent le gris pâle rayé fourni par le délégué PanelUI de la plateforme.

image

14
trashgod

Comme Romain Guy le fait remarquer ici: Google , vous devez écrire sur le bitmap, pas sur le canevas avec les cercles que vous essayez d'effacer, puis définissez-le sur le canevas principal de votre vue. Alors, faites quelque chose comme: 

// Generate bitmap used for background
            bm = BitmapFactory.decodeFile("mnt/sdcard/Pictures/test.jpg");

// Create the Paint and set the bitmap
Canvas temp = new Canvas(bm.getHeight(), bm.getWidth, Config.ARGB_8888);

// in onDraw()
temp.drawCircle((w / 8) * (ii * 2 + 1), (h / 8) * (i * 2 + 1), w / 8 * 0.8f, pDraw[i*4 + ii]);

// After loop
canvas.drawBitmap(....);

J'espère que cela t'aides.

0
AllDayAmazing