How to create emboss around a Bitmap?
Asked Answered
B

2

8

The popular game Words with Friends draws letter tiles at the game board as a single entity -

You can see a yellow linear gradient applied to all letter tiles in the following screenshot and also an emboss effect on the edge:

app screenshot

In my word game I would like to have similar effects:

emboss example

So I create a game board sized mBitmap, then draw all tiles into it and finally draw the bitmap into my custom view -

Setup:

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

// create yellow linear gradient
mGradStart = new Point(3 * mWidth / 4, mHeight / 3);
mGradEnd = new Point(mWidth / 4, 2 * mHeight / 3);
LinearGradient gradient = new LinearGradient(
        mGradStart.x,
        mGradStart.y,
        mGradEnd.x,
        mGradEnd.y,
        new int[]{ 0xCCFFCC00, 0xCCFFCC99, 0xCCFFCC00 },
        null,
        TileMode.CLAMP);

// create the big bitmap holding all tiles
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);

mPaintGrad = new Paint();
mPaintGrad.setShader(gradient);

mPaintEmboss = new Paint();
mPaintEmboss.setShader(gradient);

EmbossMaskFilter filter = new EmbossMaskFilter(
    new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f);
mPaintEmboss.setMaskFilter(filter);

Drawing:

@Override
protected void onDraw(Canvas canvas) {
    mGameBoard.draw(canvas);

    // draw all tiles as rectangles into big bitmap 
    // (this code will move to onTouchEvent later)
    mBitmap.eraseColor(Color.TRANSPARENT);
    for (SmallTile tile: mTiles) {
        mCanvas.drawRect(
                tile.left, 
                tile.top, 
                tile.left + tile.width, 
                tile.top + tile.height, 
                mPaintGrad);
        tile.draw(mCanvas);
    }
    canvas.drawBitmap(mBitmap, 0, 0, mPaintEmboss); // emboss NOT displayed
    canvas.drawText("TEXT WORKS OK", 400, 400, mPaintEmboss); // ebmoss OK
    canvas.drawRect(300, 600, 800, 1200, mPaintEmboss); // emboss OK
}

The EmbossMaskFilter effect works OK with drawText() and drawRect() calls, but it does NOT work for the drawBitmap():

app screenshot

My question: is it possible to use some combinations of PorterDuff.Mode (and extractAlpha?) to draw an emboss around my big bitmap?

UPDATE:

By looking at HolographicOutlineHelper.java I have been able to add an outer shadow:

app screenshot

with the following code in MyView.java -

Setup:

public MyView(Context context, AttributeSet attrs) {
    super(context, attrs);

    mScale = getResources().getDisplayMetrics().density;
    mGradStart = new Point(3 * mWidth / 4, mHeight / 3);
    mGradEnd = new Point(mWidth / 4, 2 * mHeight / 3);
    LinearGradient gradient = new LinearGradient(
            mGradStart.x,
            mGradStart.y,
            mGradEnd.x,
            mGradEnd.y,
            new int[]{ 0xCCFFCC00, 0xCCFFCC99, 0xCCFFCC00 },
            null,
            TileMode.CLAMP);

    mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);

    mPaintGrad = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    mPaintGrad.setShader(gradient);

    mPaintBlur = new Paint();
    mPaintBlur.setColor(Color.BLACK);
    BlurMaskFilter blurFilter = new BlurMaskFilter(mScale * 1, Blur.OUTER);
    mPaintBlur.setMaskFilter(blurFilter);
}

Drawing:

private void prepareBitmaps() {
    mBitmap.eraseColor(Color.TRANSPARENT);
    for (SmallTile tile: mTiles) {
        mCanvas.drawRect(
                tile.left, 
                tile.top, 
                tile.left + tile.width, 
                tile.top + tile.height, 
                mPaintGrad);
        tile.draw(mCanvas);
    }

    mAlphaBitmap = mBitmap.extractAlpha(mPaintBlur, mOffset);
}

@Override
protected void onDraw(Canvas canvas) {
    mGameBoard.draw(canvas);
    canvas.drawBitmap(mAlphaBitmap, mOffset[0], mOffset[1], mPaintBlur);
    canvas.drawBitmap(mBitmap, 0, 0, mPaintGrad);
}

but unfortunately the app is acting slow now - and I still don't know how to add an emboss effect around the bitmap.

Boyt answered 7/6, 2015 at 19:14 Comment(0)
M
3

I'm not sure i got exacly what you need, but if you just want to apply EmbossMaskFilter around some png letter with alpha channel, you can pretty much do this trick with

EmbossMaskFilter filter = new EmbossMaskFilter(new float[]{1, 1, 1}, 0.5f, 0.6f, 2f);

Paint paintEmboss = new Paint();
paintEmboss.setMaskFilter(embossMaskFilter);

Bitmap helperBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas helperCanvas = new Canvas(helperBitmap);

Bitmap alpha = src.extractAlpha();
helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
alpha.recycle();

...
canvas.drawBitmap(helperBitmap, 0, 0, anyPaint);

You will never want all of this code in 1 onDraw, because it creates lots of objects in memory. And src.extractAlpha(); creates new Bitmap each time. (Btw i always get out of memory error from your project git . Added mAlphaBitmap.recycle(); and it could at least boot. But it still lagges like hell)

So, i played with your git repository and got some results. Here is demo image and git repo of first commit:

enter image description here

But then i realized, that you don't need EmbossMaskFilter around letters, you need them around rectangles. And it can be done pretty much the same way. Here is how i done this:

  1. Create new helper static Bitmap and Canvas for emboss background, just like mAlphaBitmap
  2. On each prepareBitmaps() paint rects on helper bitmap. Solid color with no alpha.
  3. Extract alpha from created bitmap like this Bitmap alpha = helperCanvas.extractAlpha();
  4. Draw extracted alpha bitmap on helper with paint with emboss filter helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
  5. In onDraw print helperBitmap with some alpha before main Bitmap.

Here is screenshot without alpha(because it is much easier to see the shapes this way) enter image description here

Here is git demo of this version: https://github.com/varren/AndroidEmbossMaskFilterForPng/blob/1d692d576e78bd434252a8a6c6ad2ee9f4c6dbd8/app/src/main/java/de/afarber/mytiles2/MyView.java

And here is essential part of code i changed in your project:

private static final EmbossMaskFilter filter = 
                 new EmbossMaskFilter(new float[]{1, 1, 1}, 0.5f, 0.6f, 2f);
private static Canvas helperCanvas;
private static Paint paintEmboss;

public  Canvas getHelperCanvas(int width, int height){
    if (mAlphaBitmap == null) {
        mAlphaBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        helperCanvas = new Canvas(mAlphaBitmap);
        paintEmboss = new Paint();
        paintEmboss.setColor(Color.BLACK);
    }
    return helperCanvas;
}

private void prepareBitmaps() {
    mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    helperCanvas = getHelperCanvas(mBitmap.getWidth(),mBitmap.getHeight());
    helperCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    paintEmboss.setMaskFilter(null);
    paintEmboss.setAlpha(255);

    for (SmallTile tile: mTiles) {
        if (!tile.visible) continue;
        helperCanvas.drawRect(tile.left,tile.top,tile.left + tile.width,
                tile.top + tile.height,paintEmboss);
        mCanvas.drawRect(tile.left, tile.top,tile.left + tile.width, 
                tile.top + tile.height, mPaintGrad);
        tile.draw(mCanvas);
    }

    paintEmboss.setMaskFilter(filter);
    Bitmap alpha = mAlphaBitmap.extractAlpha();
    helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
}

protected void onDraw(Canvas canvas) {
     // ...
     paintEmboss.setAlpha(255); //todo change alpha here
    if(mAlphaBitmap!= null)canvas.drawBitmap(mAlphaBitmap, 0,0, paintEmboss);
    if(mBitmap!= null)canvas.drawBitmap(mBitmap, 0, 0, mPaintGrad);
    // ...
}

And the last 3-d step i made is to move everything from onDraw to prepareBitmaps() and preformance is fine now, but we have text destortion on resize. so here is source code for this step.

And here is kinda fine working final solution. Moving all paints with filters solved preformance issues, but i think there are still better options to implement this. As i said erlier i don't know is it what you need, but this code pretty much creates Emboss around Bitmap

PS: kinda cool effect when splitting and adding cells together

PS2: new EmbossMaskFilter(new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f); this will not look the same on diferent devices with diferent screen resolution

Macrocosm answered 13/6, 2015 at 17:45 Comment(4)
Thanks... You can make emboss look same by multiplying with mScale=getResources().getDisplayMetrics().density;Boyt
Yeah, just wanted to emphasize that it could be an issue in demo code and emboss effect could be hard to see because of screen resolution.Macrocosm
@Macrocosm EmbossMaskFilter is deprecated and throws an exception on Android oreo.Perfervid
@WaheedAbbas it was deprecated by Mike Reed · 2 years, 1 month ago, the post is almost 5 years old, you can write your own modern solution if you want to, probably will help somebody with similar problem.Macrocosm
H
1

Here's a suggestion using a custom layout.

You'll need your own layout for the scrabble board. Since it's grid, this should be pretty easy to code.

The basic idea is to have a set of PNG shadow images, one for each type of combination of adjacent cells. In your layout onDraw(), draw the shadows first, then draw the tile in onLayout().

In onDraw(), iterate through your array of tiles placeholders. If you have a tile, then for each edge, inspect the adjacent cells. Depending on what's adjacent, choose the correct shadow image and draw it.

You can reduce the number of shadow images substantially by having a shadow image which is exactly the width of a tile and then specializing the corner area: one for 270 degrees, one for straight alignment, one for 90 degrees.

I don't know if using porter-duff can help since you still need to determine all these "edge" cases (no pun intended).

Harrumph answered 7/6, 2015 at 21:10 Comment(1)
Yes, that's how Word with Friends does it (by looking at their numerous graphical assets)... but I would like to find a programmatical way to draw an emboss around all letter tiles.Boyt

© 2022 - 2024 — McMap. All rights reserved.