Drawing an outer shadow when drawing an image
I currently create a rounded version of an image in my app by drawing to a canvas. I would like to draw a faint outershadow around the image, but I cant quite get it right. I have 2 questions: 1. How can I draw an outer shadow (I can only seem to draw a shadow with a x or y offset) 2. How can I draw the shadow so that it does not have the artifacts shown in the attached image. Code:
![public Bitmap getRoundedCornerBitmap(Bitmap bitmap, float cornerRadius) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth()+6, bitmap.getHeight() +6, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
int shadowRadius = getDipsFromPixel(3);
final Rect imageRect = new Rect(shadowRadius, shadowRadius, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(imageRect);
// This does not achieve the desired effect
Paint shadowPaint = new Paint();
shadowPaint.setAntiAlias(true);
shadowPaint.setColor(Color.BLACK);
shadowPaint.setShadowLayer((float)shadowRadius, 2.0f, 2.0f,Color.BLACK);
canvas.drawOval(rectF, shadowPaint);
canvas.drawARGB(0, 0, 0, 0);
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(color);
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, imageRect, imageRect, paint);
return output;
}][1]
This is an example of the effect I am trying to achieve:
Solution 1:
I wanted a similar effect, but on an AppWidget so unfortunately I couldn't use @EvelioTarazona's solution. This is what I came up with, it should work with a bitmap of any shape.
final Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
final Bitmap shadow = addShadow(src, src.getHeight(), src.getWidth(), Color.BLACK, 3, 1, 3);
final ImageView iv = (ImageView)findViewById(R.id.image);
iv.setImageBitmap(shadow);
public Bitmap addShadow(final Bitmap bm, final int dstHeight, final int dstWidth, int color, int size, float dx, float dy) {
final Bitmap mask = Bitmap.createBitmap(dstWidth, dstHeight, Config.ALPHA_8);
final Matrix scaleToFit = new Matrix();
final RectF src = new RectF(0, 0, bm.getWidth(), bm.getHeight());
final RectF dst = new RectF(0, 0, dstWidth - dx, dstHeight - dy);
scaleToFit.setRectToRect(src, dst, ScaleToFit.CENTER);
final Matrix dropShadow = new Matrix(scaleToFit);
dropShadow.postTranslate(dx, dy);
final Canvas maskCanvas = new Canvas(mask);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
maskCanvas.drawBitmap(bm, scaleToFit, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_OUT));
maskCanvas.drawBitmap(bm, dropShadow, paint);
final BlurMaskFilter filter = new BlurMaskFilter(size, Blur.NORMAL);
paint.reset();
paint.setAntiAlias(true);
paint.setColor(color);
paint.setMaskFilter(filter);
paint.setFilterBitmap(true);
final Bitmap ret = Bitmap.createBitmap(dstWidth, dstHeight, Config.ARGB_8888);
final Canvas retCanvas = new Canvas(ret);
retCanvas.drawBitmap(mask, 0, 0, paint);
retCanvas.drawBitmap(bm, scaleToFit, null);
mask.recycle();
return ret;
}
Solution 2:
Here we go
Yup I still dig the Nexus S
First of all, please stop masking bitmaps that way, you can accomplish this without allocating another Bitmap
, checkout this blog post about how to draw rounded (and actually any shape) images.
Second using that Drawable
you probably can figure out how to add your shadow, just make sure it does not get clipped, on 18+ you could use ViewOverlay
s for that, also keep in mind that there are several unsupported drawing operations for hardware accelerated layers, that includes setShadowLayer
and BlurMaskFilter
, if performance is not an issue for you, you can disable it as always:
if (SDK_INT >= HONEYCOMB) {
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
And use setShadowLayer
as you were trying already:
somePaint.setShadowLayer(shadowSize, deltaX, deltaY, shadowColor);
For a sample please check the link at the end.
If you still want to be hardware accelerated you will have to fake it at risk of overdrawing, you could use a radial gradient or draw another oval blurring it yourself (as mentioned before can't use BlurMaskFilter
) or use a pre-blurred Bitmap
(more masking).
For such a subtle shadow I would rather just go flat if performance is required, the full sauce is in the banana stand.
Update: Starting L you can use real shadows.
Solution 3:
ImageView imageView=findViewById(R.id.iv);
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.images);
imageView.setImageBitmap(doHighlightImage(icon));
public static Bitmap doHighlightImage(Bitmap src) {
Bitmap bmOut = Bitmap.createBitmap(src.getWidth() + 96, src.getHeight() + 96, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmOut);
canvas.drawColor(0, Mode.CLEAR);
Paint ptBlur = new Paint();
ptBlur.setMaskFilter(new BlurMaskFilter(15, BlurMaskFilter.Blur.NORMAL));
int[] offsetXY = new int[2];
Bitmap bmAlpha = src.extractAlpha(ptBlur, offsetXY);
Paint ptAlphaColor = new Paint();
ptAlphaColor.setColor(Color.BLACK);
canvas.drawBitmap(bmAlpha, offsetXY[0], offsetXY[1], ptAlphaColor);
bmAlpha.recycle();
canvas.drawBitmap(src, 0, 0, null);
return bmOut;
}
Solution 4:
Create a xml name it round_shape.xml in drawable folder
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="#bebebe" />
</shape>
Set this round_shape as back ground of image view like below
<ImageView
android:id="@+id/img_profile"
android:layout_width="120dp"
android:layout_height="120dp"
android:padding="2dp"
android:scaleType="fitXY"
android:src="@drawable/camera" />
The above code create a thin layer around image view after you have round the bitmap , below function will do this
public Bitmap roundBit(Bitmap bm) {
Bitmap circleBitmap = Bitmap.createBitmap(bm.getWidth(),
bm.getHeight(), Bitmap.Config.ARGB_8888);
BitmapShader shader = new BitmapShader(bm, TileMode.CLAMP,
TileMode.CLAMP);
Paint paint = new Paint();
paint.setShader(shader);
paint.setAntiAlias(true);
Canvas c = new Canvas(circleBitmap);
c.drawCircle(bm.getWidth() / 2, bm.getHeight() / 2, bm.getWidth() / 2,
paint);
return circleBitmap;
}
Solution 5:
This is my modification of sharmitha's answer. It's Kotlin and it uses the bmAlpha's size for the result Bitmap so that the shadow is not cropped.
private val metrics: DisplayMetrics by lazy {
val metrics = DisplayMetrics()
val manager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
manager.defaultDisplay.getMetrics(metrics)
metrics
}
private fun withDensity(value: Float): Float = metrics.density * value
private fun addShadow(src: Bitmap, blurRadius: Float, @ColorInt shadowColor: Int): Bitmap {
val offsetXY = IntArray(2)
val bmAlpha = src.extractAlpha(
Paint().apply { maskFilter = BlurMaskFilter(withDensity(blurRadius), BlurMaskFilter.Blur.NORMAL) },
offsetXY
)
val bmOut = Bitmap.createBitmap(bmAlpha.width, bmAlpha.height, Bitmap.Config.ARGB_8888)
Canvas(bmOut).apply {
drawColor(0, PorterDuff.Mode.CLEAR)
drawBitmap(
bmAlpha,
0f,
0f,
Paint().apply { color = shadowColor })
drawBitmap(
src,
0f - offsetXY[0].toFloat(),
0f - offsetXY[1].toFloat(),
null)
}
bmAlpha.recycle()
return bmOut
}