Skip to content Skip to sidebar Skip to footer

Emboss Edges Of A Image Shape Showing The Depth In Android

I want to show 3D embossed look and feel as shown in following image. I used EmbossMaskFilter but cannot get it to show the effect (see code below). Is there a different way to do

Solution 1:

If you only want to do bitmap processing (as opposed to 3D or vectors), your best bet probably is to:

  1. Generate a stencil mask from your puzzle piece,
  2. Use Difference of Gaussians to process it (I used kernels of size 12 and 2 pixels in this example), then normalize and invert the result,
  3. Alpha-blend the output of "2" into original image using the mask (1.) as the stencil channel.

MaskDifference of GaussiansResult

UPDATE: here comes the code. I tried to reuse your variable names so that it's easier to understand. The code uses Renderscript intrinsics whenever possible in order to make things faster and more interesting.

private Paint fillPaint = null;
private Path path2;
private Bitmap mBitmapIn;
private Bitmap mBitmapPuzzle;
private RenderScript mRS;
private Allocation mInAllocation;
private Allocation mPuzzleAllocation;
private Allocation mCutterAllocation;

private Allocation mOutAllocation;
private Allocation mOutAllocation2;
private Allocation mAllocationHist;
private ScriptIntrinsicBlur mScriptBlur;
private ScriptIntrinsicBlend mScriptBlend;
private ScriptIntrinsicHistogram mScriptHistogram;
private ScriptIntrinsicLUT mScriptLUT;
private Context ctx;
privateint bw = 780;
privateint bh = 780;

privatevoid init()
{
    mBitmapIn = loadBitmap(R.drawable.cat7); // background image
    mBitmapPuzzle = Bitmap.createBitmap(bw, bh, Bitmap.Config.ARGB_8888);  // this will hold the puzzle
    Canvas c = new Canvas(mBitmapPuzzle);

    path2 = new Path();
    createPath(5);  // create the path with stroke width of 5 pixels
    c.drawPath(path2, fillPaint);  // draw it on canvas

    createScript();  // get renderscripts and Allocations ready// Apply gaussian blur of radius 25 to our drawing
    mScriptBlur.setRadius(25);
    mScriptBlur.setInput(mPuzzleAllocation);
    mScriptBlur.forEach(mOutAllocation);

    // Now apply the blur of radius 1
    mScriptBlur.setRadius(1);
    mScriptBlur.setInput(mPuzzleAllocation);
    mScriptBlur.forEach(mOutAllocation2);

    // Subtract one blur result from another
    mScriptBlend.forEachSubtract(mOutAllocation, mOutAllocation2);

    // We now want to normalize the result (e.g. make it use full 0-255 range).// To do that, we will first compute the histogram of our image
    mScriptHistogram.setOutput(mAllocationHist);
    mScriptHistogram.forEach(mOutAllocation2);

    // copy the histogram to Java array...int []hist = newint[256 * 4];
    mAllocationHist.copyTo(hist);

    // ...and walk it from the end looking for the first non empty binint i;
    for(i = 255; i > 1; i--)
        if((hist[i * 4] | hist[i * 4 + 1] | hist[i * 4 + 2]) != 0)
            break;

    // Now setup the LUTs that will map the image to the new, wider range.// We also use the opportunity to inverse the image ("255 -").for(int x = 0; x <= i; x++)
    {
        int val = 255 - x * 255 / i;

        mScriptLUT.setAlpha(x, 255);  // note we always make it fully opaque
        mScriptLUT.setRed(x, val);
        mScriptLUT.setGreen(x, val);
        mScriptLUT.setBlue(x, val);
    }


    // the mapping itself.
    mScriptLUT.forEach(mOutAllocation2, mOutAllocation);

Let's make a short break and see what we have so far. Observe that the entire image on the left is opaque (i.e. including the space outside the puzzle), and we now have to cut out the shape and antialias its edge properly. Unfortunately, using original shape won't work, as it is too large and cuts out too much, leading to unpleasant artifacts near the edge (figure on the right).

enter image description hereenter image description here

We therefore draw another path, this time using a narrower stroke...

BitmapmBitmapCutter= Bitmap.createBitmap(bw, bh, Bitmap.Config.ARGB_8888);
    c = newCanvas(mBitmapCutter);
    path2 = newPath();
    createPath(1);  // stroke width 1
    c.drawPath(path2, fillPaint);
    mCutterAllocation = Allocation.createFromBitmap(mRS, mBitmapCutter);

    // cookie cutter now
    mScriptBlend.forEachDstIn(mCutterAllocation, mOutAllocation);

...for a much better looking result. Let's use it to mask out a background image. enter image description here

    mScriptBlend.forEachMultiply(mOutAllocation, mInAllocation);
    mInAllocation.copyTo(mBitmapPuzzle);
}

enter image description here

Hello there! Now just the Renderscript setup code.

privatevoidcreateScript() {
    mRS = RenderScript.create(ctx);

    mPuzzleAllocation = Allocation.createFromBitmap(mRS, mBitmapPuzzle);

    // three following allocations could actually use createSized(),// but the code would be longer.
    mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn);
    mOutAllocation = Allocation.createFromBitmap(mRS, mBitmapPuzzle);
    mOutAllocation2 = Allocation.createFromBitmap(mRS, mBitmapPuzzle);

    mAllocationHist = Allocation.createSized(mRS, Element.I32_3(mRS), 256);

    mScriptBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));
    mScriptBlend = ScriptIntrinsicBlend.create(mRS, Element.U8_4(mRS));
    mScriptHistogram = ScriptIntrinsicHistogram.create(mRS, Element.U8_4(mRS));
    mScriptLUT = ScriptIntrinsicLUT.create(mRS, Element.U8_4(mRS));
}

And finally onDraw():

@OverrideprotectedvoidonDraw(Canvas canvas) {
    canvas.drawBitmap(mBitmapPuzzle, 0, 0, fillPaint);
    super.onDraw(canvas);
}

TODO: check if other stroke miters would give more pleasant corners.

Solution 2:

I think you will need a larger blur radius and smaller values for ambient light and specular highlight.

I played around with this by creating a UI to tweak parameters.

image 1

image 2

Here is the code I used, so you can try it out. I used a simple blue rectangle, but you should be able to easily plug in whatever image you want to draw.

MainActivity.java:

import android.graphics.EmbossMaskFilter;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.SeekBar;
import android.widget.TextView;

publicclassMainActivityextendsActionBarActivity {

    @OverrideprotectedvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        finalDrawViewdrawView= (DrawView) findViewById(R.id.drawView);

        finalTextViewambientLightTextView= (TextView) findViewById(R.id.ambientLightTextView);

        SeekBarambientLightSeekBar= (SeekBar) findViewById(R.id.ambientLightSeekBar);
        ambientLightSeekBar.setOnSeekBarChangeListener(newSeekBar.OnSeekBarChangeListener() {
            @OverridepublicvoidonProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                floatvalue= (float) progress / 1000F;
                ambientLightTextView.setText(Float.toString(value));
                drawView.setAmbientLight(value);
            }

            @OverridepublicvoidonStartTrackingTouch(SeekBar seekBar) {}
            @OverridepublicvoidonStopTrackingTouch(SeekBar seekBar) {}
        });

        finalTextViewspecularHighlightTextView= (TextView) findViewById(R.id.specularHighlightTextView);

        SeekBarspecularHighlightSeekBar= (SeekBar) findViewById(R.id.specularHighlightSeekBar);
        specularHighlightSeekBar.setOnSeekBarChangeListener(newSeekBar.OnSeekBarChangeListener() {
            @OverridepublicvoidonProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                floatvalue= (float) progress / 100f;
                specularHighlightTextView.setText(Float.toString(value));
                drawView.setSpecularHighlight(value);
            }

            @OverridepublicvoidonStartTrackingTouch(SeekBar seekBar) {}
            @OverridepublicvoidonStopTrackingTouch(SeekBar seekBar) {}
        });

        finalTextViewblurRadiusTextView= (TextView) findViewById(R.id.blurRadiusTextView);

        SeekBarblurRadiusSeekBar= (SeekBar) findViewById(R.id.blurRadiusSeekBar);
        blurRadiusSeekBar.setOnSeekBarChangeListener(newSeekBar.OnSeekBarChangeListener() {
            @OverridepublicvoidonProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                floatvalue= (float) progress / 5F;
                blurRadiusTextView.setText(Float.toString(value));
                drawView.setBlurRadius(value);
            }

            @OverridepublicvoidonStartTrackingTouch(SeekBar seekBar) {}
            @OverridepublicvoidonStopTrackingTouch(SeekBar seekBar) {}
        });
    }

}

DrawView.java:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.EmbossMaskFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

publicclassDrawViewextendsView {

    privateRectrect=newRect();

    private Paint paint;

    private EmbossMaskFilter filter;

    privatefloat[] mLightSource = newfloat[] {2, 5, 5};

    privatefloatmAmbientLight=0.5F;

    privatefloatmSpecularHighlight=8F;

    privatefloatmBlurRadius=15f;

    publicDrawView(Context context) {
        super(context);
        init();
    }

    publicDrawView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    publicDrawView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    privatevoidinit() {

        setLayerType(LAYER_TYPE_SOFTWARE, null);

        filter = newEmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);

        paint = newPaint();
        paint.setColor(0xFF0000FF);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setMaskFilter(filter);
    }

    @OverrideprotectedvoidonDraw(Canvas canvas) {
        super.onDraw(canvas);

        rect.left = getWidth() / 3;
        rect.right = rect.left * 2;
        rect.top = getHeight() / 3;
        rect.bottom = rect.top * 2;

        canvas.drawRect(rect, paint);
    }

    publicvoidsetAmbientLight(float value) {
        if (value > 1.0F) value = 1.0F;
        if (value < 0) value = 0;
        mAmbientLight = value;
        filter = newEmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);
        paint.setMaskFilter(filter);
        invalidate();
    }

    publicvoidsetSpecularHighlight(float value) {
        mSpecularHighlight = value;
        filter = newEmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);
        paint.setMaskFilter(filter);
        invalidate();
    }

    publicvoidsetBlurRadius(float value) {
        mBlurRadius = value;
        filter = newEmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);
        paint.setMaskFilter(filter);
        invalidate();
    }
}

activity_main.xml:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context=".MainActivity"><!-- make sure you match this up with the actual package name in your code --><com.example.embossmaskfilter.DrawViewandroid:id="@+id/drawView"android:layout_width="200dp"android:layout_height="200dp"android:layout_alignParentTop="true"android:layout_centerHorizontal="true"android:layerType="software" /><TextViewandroid:id="@+id/textView2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentStart="true"android:layout_below="@id/drawView"android:layout_centerVertical="true"android:text="Ambient Light"android:textAppearance="?android:attr/textAppearanceSmall" /><TextViewandroid:id="@+id/ambientLightTextView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_alignTop="@id/textView2"android:text="0.5"android:textAppearance="?android:attr/textAppearanceSmall" /><SeekBarandroid:id="@+id/ambientLightSeekBar"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_alignParentStart="true"android:layout_below="@+id/textView2"android:max="1000" /><TextViewandroid:id="@+id/textView3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentStart="true"android:layout_below="@id/ambientLightSeekBar"android:layout_centerVertical="true"android:text="Specular Highlight"android:textAppearance="?android:attr/textAppearanceSmall" /><TextViewandroid:id="@+id/specularHighlightTextView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_alignTop="@id/textView3"android:text="0.5"android:textAppearance="?android:attr/textAppearanceSmall" /><SeekBarandroid:id="@+id/specularHighlightSeekBar"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_alignParentStart="true"android:layout_below="@+id/textView3"android:max="1000" /><TextViewandroid:id="@+id/textView4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentStart="true"android:layout_below="@id/specularHighlightSeekBar"android:layout_centerVertical="true"android:text="Blur Radius"android:textAppearance="?android:attr/textAppearanceSmall" /><TextViewandroid:id="@+id/blurRadiusTextView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_alignTop="@id/textView4"android:text="0.5"android:textAppearance="?android:attr/textAppearanceSmall" /><SeekBarandroid:id="@+id/blurRadiusSeekBar"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_alignParentStart="true"android:layout_below="@+id/textView4"android:max="1000" /></RelativeLayout>

Solution 3:

If you're not afraid of dabbling into some low-level image processing, you can use convolution matrices to achieve an emboss effect. You could use your image's silhouette (i.e. the alpha channel) as an input to the filter. For example, if you use this 5x5 matrix:

| -2  0  0  0  0 |
|  0 -1  0  0  0 |
|  0  0  n  0  0 | * (1/n)
|  0  0  0  1  0 |
|  0  0  0  0  2 |

and apply it to such image (representing an alpha channel):

unprocessed alpha channel

you'll get this effect:

embossed alpha channel

All of the computed values have been decreased by 127 to ensure that they will be in range of 0-255. I used n = 10 in this particular example. You can manipulate the radius by using a matrix of different size (it's not hard to extrapolate) and the depth by adjusting the value of n (the bigger the value, the subtler the effect).

Given the original alpha channel, and the computed mask you can determine offsets to apply to the respective pixels of the original image, thus creating an emboss effect.

Hope that helps.

Post a Comment for "Emboss Edges Of A Image Shape Showing The Depth In Android"