Embossed edges of an image shape showing depth in an android

I want to show a three-dimensional embossed look and feel, as shown in the following image. I used EmbossMaskFilter, but I can not get it to show the effect (see code below). Is there any other way to do this? or how can I use EmbossMaskFilter for this.

Required conclusion

enter image description here

My conclusion

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); } 
+6
source share
3 answers

If you only want to process bitmap images (as opposed to 3D or vectors), the best option is probably the following:

  • Create a screen mask from a puzzle piece,
  • Use the Gaussians Difference to process it (I used 12 and 2 pixel kernels in this example), then normalize and invert the result,
  • Alpha mixing output "2" to the original image using the mask (1.) as a stencil channel.

Mask Gaussian difference OtDOu.png

UPDATE: here comes the code. I tried to reuse variable names to make them easier to understand. The code uses Renderscript features when possible 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 (eg 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); 

Take a short break and see what we have. Please note that the entire image on the left is opaque (i.e., including the space outside the puzzle), and now we need to correctly cut the shape and antialiases. Unfortunately, the use of the original form will not work, as it is too large and cut too much, which leads to unpleasant artifacts near the edge (figure on the right).

enter image description here enter image description here

Therefore, we draw a different path, this time using a narrower move ...

  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 result. Let use it to mask the background image. enter image description here

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

enter image description here

Hello! Now just the Renderscript installation 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 to see if other bumps give more nice angles along the way.

+4
source

I think you will need a larger blur radius and lower values ​​for ambient light and mirror illumination.

I played with this by creating a user interface for setting options.

image 1

image 2

Here is the code I used, so you can try it. I used a simple blue rectangle, but you should easily connect any 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> 
+5
source

If you're not afraid to dive into low-level image processing, you can use > convolution matrices to achieve an embossing effect. You can use the silhouette of the image (i.e. 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 this image (representing the alpha channel):

raw alpha

you will get this effect:

embossed alpha channel

All calculated values ​​are reduced by 127 to be in the range 0-255. I used n = 10 in this particular example. You can manipulate the radius using a matrix of different sizes (it is not difficult to extrapolate it) and depth by adjusting the value of n (the larger the value, the finer the effect).

Given the original alpha channel and the calculated mask, you can determine the offsets to apply to the corresponding pixels of the original image, thereby creating an embossing effect.

Hope this helps.

+2
source

All Articles