Emboss edges of a image shape showing the depth in android
Asked Answered
G

3

7

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 this? or how can I use the EmbossMaskFilter for this.

Required output

enter image description here

My output

enter image description here

Path path2 = new Path();
public Paint fillPaint = null;
// called in constructor
public void createPath()
{
    //path 2 Big one
    araay = new Point[]{new Point(144,320),new Point(109,200), new Point(171,308),new Point(178,240),new Point(171,172),new Point(109,282),new Point(144,160)};
    AddBeziers(path2, araay, 320, 144);
    AddLine(path2, 216, 144 );
    AddLine(path2, 216, 216 );
    AddLine(path2, 144, 320);

     MaskFilter mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6,   3.5f);
    fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    fillPaint.setColor(Color.WHITE);
    fillPaint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    fillPaint.setAntiAlias(true);
    fillPaint.setDither(true);
    fillPaint.setStrokeJoin(Paint.Join.ROUND);
    fillPaint.setStrokeCap(Paint.Cap.ROUND);
    fillPaint.setStyle(Paint.Style.FILL);
    paint.setMaskFilter(mEmboss);   
}

 // add lines to the path
protected Path AddLine(Path path, int endX, int endY) {
    //path.moveTo(startX, startY);

    path.lineTo(endX, endY);
    return path;
}

// add curves to the path
protected Path AddBeziers(Path path, Point[] points, int lastX, int lastY) {

    if (points[0].X != lastX && points[0].Y != lastY)
        path.moveTo(points[0].X, points[0].Y);

    int index = 1;

    path.cubicTo(points[index].X, points[index].Y, points[index + 1].X,
        points[index + 1].Y, points[index + 2].X, points[index + 2].Y);
    index = index + 3;
    path.cubicTo(points[index].X, points[index].Y, points[index + 1].X,
        points[index + 1].Y, points[index + 2].X, points[index + 2].Y);

    return path;
}

//draw on canvas
@Override
public void onDraw(Canvas canvas) {

    canvas.drawPath(path2, fillPaint);
    super.onDraw(canvas);
}
Greyback answered 22/7, 2015 at 12:15 Comment(0)
C
4

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;
private int bw = 780;
private int bh = 780;

private void 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 = new int[256 * 4];
    mAllocationHist.copyTo(hist);

    // ...and walk it from the end looking for the first non empty bin
    int 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 here enter image description here

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

    Bitmap mBitmapCutter = Bitmap.createBitmap(bw, bh, Bitmap.Config.ARGB_8888);
    c = new Canvas(mBitmapCutter);
    path2 = new Path();
    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.

private void createScript() {
    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():

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

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

Cytotaxonomy answered 3/8, 2015 at 23:34 Comment(3)
This is exactly what I was after. Can you please please give more information on this? How to efficiently implement this in java/android? (I have the path object as well.)Greyback
Hi thanks for your great answer. I tried it out, but didnt have any effect. Please see the output below.Greyback
#32679700 I have created a new question for this.Greyback
B
4

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;

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

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

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

        SeekBar ambientLightSeekBar = (SeekBar) findViewById(R.id.ambientLightSeekBar);
        ambientLightSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                float value = (float) progress / 1000F;
                ambientLightTextView.setText(Float.toString(value));
                drawView.setAmbientLight(value);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });

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

        SeekBar specularHighlightSeekBar = (SeekBar) findViewById(R.id.specularHighlightSeekBar);
        specularHighlightSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                float value = (float) progress / 100f;
                specularHighlightTextView.setText(Float.toString(value));
                drawView.setSpecularHighlight(value);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });

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

        SeekBar blurRadiusSeekBar = (SeekBar) findViewById(R.id.blurRadiusSeekBar);
        blurRadiusSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                float value = (float) progress / 5F;
                blurRadiusTextView.setText(Float.toString(value));
                drawView.setBlurRadius(value);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}
            @Override
            public void onStopTrackingTouch(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;

public class DrawView extends View {

    private Rect rect = new Rect();

    private Paint paint;

    private EmbossMaskFilter filter;

    private float[] mLightSource = new float[] {2, 5, 5};

    private float mAmbientLight = 0.5F;

    private float mSpecularHighlight = 8F;

    private float mBlurRadius = 15f;

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

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

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

    private void init() {

        setLayerType(LAYER_TYPE_SOFTWARE, null);

        filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);

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

    @Override
    protected void onDraw(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);
    }

    public void setAmbientLight(float value) {
        if (value > 1.0F) value = 1.0F;
        if (value < 0) value = 0;
        mAmbientLight = value;
        filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);
        paint.setMaskFilter(filter);
        invalidate();
    }

    public void setSpecularHighlight(float value) {
        mSpecularHighlight = value;
        filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);
        paint.setMaskFilter(filter);
        invalidate();
    }

    public void setBlurRadius(float value) {
        mBlurRadius = value;
        filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius);
        paint.setMaskFilter(filter);
        invalidate();
    }
}

activity_main.xml:

<RelativeLayout xmlns: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.DrawView
        android:id="@+id/drawView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layerType="software" />

    <TextView
        android: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" />

    <TextView
        android: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" />

    <SeekBar
        android: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" />

    <TextView
        android: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" />

    <TextView
        android: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" />

    <SeekBar
        android: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" />

    <TextView
        android: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" />

    <TextView
        android: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" />

    <SeekBar
        android: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>
Boyle answered 30/7, 2015 at 3:10 Comment(2)
Thanks Kris for your time, I don't think the required output is using emboss at all. I did some experiments last two weeks. Emboss filter creates dark and light edges, this is a unfair advantage of the game player because they can figure out the locations based on lighting. See this link code.msdn.microsoft.com/windowsdesktop/…. They have created something called alpha blend bevel, but the algorithm uses low level APIs . I was wandering if there is a way to get this effect. Any idea?Greyback
Further What I have is a BItmap drawn to a shape using PorterDuff mode. Emboss filter does not apply at all on a bitmap apart from the lines.Greyback
C
4

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;
private int bw = 780;
private int bh = 780;

private void 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 = new int[256 * 4];
    mAllocationHist.copyTo(hist);

    // ...and walk it from the end looking for the first non empty bin
    int 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 here enter image description here

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

    Bitmap mBitmapCutter = Bitmap.createBitmap(bw, bh, Bitmap.Config.ARGB_8888);
    c = new Canvas(mBitmapCutter);
    path2 = new Path();
    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.

private void createScript() {
    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():

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

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

Cytotaxonomy answered 3/8, 2015 at 23:34 Comment(3)
This is exactly what I was after. Can you please please give more information on this? How to efficiently implement this in java/android? (I have the path object as well.)Greyback
Hi thanks for your great answer. I tried it out, but didnt have any effect. Please see the output below.Greyback
#32679700 I have created a new question for this.Greyback
P
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.

Peta answered 31/7, 2015 at 14:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.