14

I have a mask bitmap with a half is red color and ones is transparent like this https://www.dropbox.com/s/931ixef6myzusi0/s_2.png

I want to use mask bitmap to draw content on canvas only visible in red area, code like this:

Paint paint = new Paint();


public void draw(Canvas canvas) {
// draw content here
  ...

//and mask bitmap here
  paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.DST_IN));
  canvas.drawBitmap(maskBitmap, 0, 0, paint);

}

The result as my expecting (content only visible in red area, BUT THE TRANSPARENT AREA BECOME BLACK IS PROBLEM!)

this image result :https://www.dropbox.com/s/mqj48992wllfkiq/s_2%20copy.png Anyone help me???

FlyingPumba
  • 1,015
  • 2
  • 15
  • 37
Thomc
  • 202
  • 1
  • 4
  • 10

5 Answers5

47

Here is a solution which helped me to implement masking:

public void draw(Canvas canvas) {
        Bitmap original = BitmapFactory.decodeResource(getContext().getResources(),R.drawable.original_image);
        Bitmap mask = BitmapFactory.decodeResource(getContext().getResources(),R.drawable.mask_image);

        //You can change original image here and draw anything you want to be masked on it.

        Bitmap result = Bitmap.createBitmap(mask.getWidth(), mask.getHeight(), Config.ARGB_8888);
        Canvas tempCanvas = new Canvas(result);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
        tempCanvas.drawBitmap(original, 0, 0, null);
        tempCanvas.drawBitmap(mask, 0, 0, paint);
        paint.setXfermode(null);

        //Draw result after performing masking
        canvas.drawBitmap(result, 0, 0, new Paint());
}

The mask should be a white image with transparency.
It will work like this:
original image + mask = result image

Sergey Pekar
  • 8,555
  • 7
  • 47
  • 54
  • this will be quite slow because the off screen canvas is not hardware accelerated – thumbmunkeys Nov 18 '15 at 14:11
  • 1
    I find that canvas graphics are still quite necessary even if you use OpenGL, because it can generate graphics on the fly that can be later loaded to an OpenGL shader. That said, this is still quite useful. – Brian Sep 15 '16 at 21:19
  • 2
    but creating bitmap in onDraw() may cause out of memory Exception – dileep krishnan Dec 28 '17 at 09:53
  • It should not be white, as you mentioned. Transparent channel suffices. Check https://developer.android.com/reference/android/graphics/PorterDuff.Mode – Fattum Nov 21 '18 at 13:40
  • What you passed in parameter of draw – Prince Dholakiya Feb 07 '19 at 09:17
  • @DPrince I overrides onDraw method in my subclass of the View class. Check here https://developer.android.com/reference/android/view/View.html#onDraw(android.graphics.Canvas) – Sergey Pekar Feb 08 '19 at 03:29
7

I encountered the same problem in my custom view and instead of decoding the bitmap from a resource, I had created the original bitmap and the masking bitmap from the scratch via canvas.draw*() methods (since both the original and mask are basic shapes). I was getting the blank opaque space instead of a transparent one. I fixed it by setting a hardware layer to my view.

View.setLayerType(LAYER_TYPE_HARDWARE, paint);

More info on why this is to be done here: https://stackoverflow.com/a/33483016/4747587

Community
  • 1
  • 1
Henry
  • 17,490
  • 7
  • 63
  • 98
1

Same answer as @Sergey Pekar give but I have updated it in Kotlin.

enter image description here

fun ImageView.getMaskBitmap(imageUrl: String? = null, mContent: Int, mMaskedImage : Int) {
    runOnBackground {
        // if you have https image url then use below line
        //val original: Bitmap = BitmapFactory.decodeStream(URL(imageUrl).openConnection().getInputStream())

        // if you have png or jpg image then use below line
        val original: Bitmap = BitmapFactory.decodeResource(resources, mContent)

        val mask = BitmapFactory.decodeResource(resources, mMaskedImage) // mMaskedImage Your masking image
        val result: Bitmap = Bitmap.createBitmap(mask.width, mask.height, Bitmap.Config.ARGB_8888, true)
        val tempCanvas = Canvas(result)
        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
        tempCanvas.apply {
            drawBitmap(original, 0f, 0f, null)
            drawBitmap(mask, 0f, 0f, paint)
        }
        paint.xfermode = null

        //Draw result after performing masking
        runOnBackground(onMainThread = {
            this.apply {
                setImageBitmap(result)
                scaleType = ImageView.ScaleType.FIT_CENTER
            }
        })
    }
}

Github Demo

Shweta Chauhan
  • 6,739
  • 6
  • 37
  • 57
0
Bitmap finalMasking = stackMaskingProcess(imageBitmap, bitmapMasking);


private Bitmap stackMaskingProcess(Bitmap _originalBitmap, Bitmap _maskingBitmap) {
        try {
            if (_originalBitmap != null)
            {
                int intWidth = _originalBitmap.getWidth();
                int intHeight = _originalBitmap.getHeight();
                resultMaskBitmap = Bitmap.createBitmap(intWidth, intHeight, Bitmap.Config.ARGB_8888);
                getMaskBitmap = Bitmap.createScaledBitmap(_maskingBitmap, intWidth, intHeight, true);
                Canvas mCanvas = new Canvas(resultMaskBitmap);
                Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
                mCanvas.drawBitmap(_originalBitmap, 0, 0, null);
                mCanvas.drawBitmap(getMaskBitmap, 0, 0, paint);
                paint.setXfermode(null);
                paint.setStyle(Paint.Style.STROKE);
            }
        } catch (OutOfMemoryError o) {
            o.printStackTrace();
        }
        return resultMaskBitmap;
    }
Praful Parmar
  • 213
  • 2
  • 6
0

I like the approach from Er. Praful Parmar's answer but for me it did not quite work as expected. I had problems, because some scaling was going on without intention.
My Bitmaps had a different density than my device and this messed things up.

Also I wanted to reduce the creation of Objects, so I moved the Paint object to a constant for reuse.

So here is my utils method:

  public static final//
  Bitmap createWithMask(final Bitmap img, final Bitmap mask) {
    final Bitmap result = Bitmap.createBitmap(img.getWidth(), img.getHeight(),
        Bitmap.Config.ARGB_8888);
    result.setDensity(originalBitmap.getDensity()); // to avoid scaling if density of 'img' is different form the default on your device
    final Canvas canvas = new Canvas(result);
    canvas.drawBitmap(img, 0, 0, null);
    canvas.drawBitmap(mask, 0, 0, PAINT_FOR_MASK);
    return result;
  }//end-method

  private static final Paint PAINT_FOR_MASK = createPaintForMask();

  private static final//
  Paint createPaintForMask() {
    final Paint paint = new Paint();
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    return paint;
  }//end-method
MrSmith42
  • 9,961
  • 6
  • 38
  • 49