Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Masking a Bitmap using another Bitmap

I have two bitmaps. Here is Bitmap 1:

Bitmap 1: Background

And here is Bitmap 2:

Bitmap 2: frame

What the final results shall be:

Bitmap Final

I would appreciate a code, however, I'd appreciate more a reference to an documentation or tutorial. I would like to understand the code completely and I have been searching on developer.android.com for so long with no luck. Thanks.

like image 394
Abdalrahman Shatou Avatar asked Oct 21 '22 07:10

Abdalrahman Shatou


1 Answers

Over 3 years and no answer? I can fix that.

As stated in the comments, Bitmap 2 is transparent around the edges and in the middle (only the contour is there) so the first step is to fill the center with white. There are plenty of flood fill algorithms available. I used https://stackoverflow.com/a/8925653/852795 because it was easy, although there are others that are certainly faster. This is necessary as it enables the next step.

The second step is to combine the filled Bitmap 2 with Bitmap 1 using Porter/Duff Composting. PorterDuff.Mode.SRC_ATOP will effectively paint the Bitmap 1 into the now white area of Bitmap 2 while leaving the area outside of the contour transparent.

Here is the code:

package test.testapplication;

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.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.graphics.Bitmap.Config;

import java.util.LinkedList;
import java.util.Queue;

public class MainActivity extends AppCompatActivity {

    Bitmap mask, background, filledMask, overlay;
    Canvas c;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mask = BitmapFactory.decodeResource(getResources(), R.drawable.mask);
        background = BitmapFactory.decodeResource(getResources(), R.drawable.background);

        // get the mask, copy it to filledMask and then flood from the center with CYAN
        filledMask = Bitmap.createBitmap(mask.getWidth(), mask.getHeight(), Config.ARGB_8888);
        c = new Canvas(filledMask);
        c.drawBitmap(mask, 0, 0, new Paint());
        Point center = new Point(filledMask.getWidth() / 2, filledMask.getHeight() / 2);
        floodFill(filledMask, center, Color.TRANSPARENT, Color.WHITE);


        // create new overlay Bitmap, draw the filledMask and then add the background using PorterDuff
        overlay = Bitmap.createBitmap(filledMask.getWidth(), filledMask.getHeight(), Config.ARGB_8888);
        c = new Canvas(overlay);
        Paint p = new Paint();
        p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        c.drawBitmap(filledMask, 0, 0, new Paint());
        c.drawBitmap(background, 0, 0, p);

        DrawView drawView = new DrawView(this);
        // set background to light blue in order to see transparent areas
        drawView.setBackgroundColor(0xffd2d7fe);
        setContentView(drawView);
        drawView.requestFocus();
    }

    public class DrawView extends View {
        Paint p = new Paint();
        int top = 0;

        public DrawView(Context context) {
            super(context);
        }

        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawBitmap(mask, 0, 0, p);
            top += mask.getHeight();

            canvas.drawBitmap(filledMask, 0, top, p);
            top += filledMask.getHeight();

            canvas.drawBitmap(background, 0, top, p);
            top += background.getHeight();

            canvas.drawBitmap(overlay, 0, top, p);
        }
    }

    // method from https://stackoverflow.com/a/8925653/852795
    public void floodFill(Bitmap bmp, Point pt, int targetColor, int replacementColor) {

        Queue<Point> q = new LinkedList<>();
        q.add(pt);
        while (q.size() > 0) {
            Point n = q.poll();
            if (bmp.getPixel(n.x, n.y) != targetColor) continue;

            Point w = n, e = new Point(n.x + 1, n.y);
            while ((w.x > 0) && (bmp.getPixel(w.x, w.y) == targetColor)) {
                bmp.setPixel(w.x, w.y, replacementColor);
                if ((w.y > 0) && (bmp.getPixel(w.x, w.y - 1) == targetColor)) q.add(new Point(w.x, w.y - 1));
                if ((w.y < bmp.getHeight() - 1) && (bmp.getPixel(w.x, w.y + 1) == targetColor)) q.add(new Point(w.x, w.y + 1));
                w.x--;
            }
            while ((e.x < bmp.getWidth() - 1) && (bmp.getPixel(e.x, e.y) == targetColor)) {
                bmp.setPixel(e.x, e.y, replacementColor);

                if ((e.y > 0) && (bmp.getPixel(e.x, e.y - 1) == targetColor)) q.add(new Point(e.x, e.y - 1));
                if ((e.y < bmp.getHeight() - 1) && (bmp.getPixel(e.x, e.y + 1) == targetColor)) q.add(new Point(e.x, e.y + 1));
                e.x++;
            }
        }
    }
}

When run, the output (after adding a light blue tint to the background in order to 'see' the transparent areas of the images) should look like this, with the images, respectively, being Bitmap 2, Bitmap 2 filled, Bitmap 1 and finally the combination of Bitmap 2 filled and Bitmap 1:

enter image description here

There appears to be a bit of 'fuzziness' just inside the contour, but that's probably an artifact of the flood fill, or perhaps the original Bitmap 2. Playing around with both of those might clear that up.

like image 101
Mark Cramer Avatar answered Oct 27 '22 10:10

Mark Cramer