Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create emboss around a Bitmap?

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.

like image 789
Alexander Farber Avatar asked Jun 07 '15 19:06

Alexander Farber


People also ask

What is color emboss?

Image embossing is a computer graphics technique in which each pixel of an image is replaced either by a highlight or a shadow, depending on light/dark boundaries on the original image. Low contrast areas are replaced by a gray background.


1 Answers

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

like image 73
varren Avatar answered Sep 29 '22 19:09

varren