Drawing an outer shadow when drawing an image

2019-01-07 16:49发布

问题:

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:

回答1:

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 ViewOverlays 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.



回答2:

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;
}


回答3:

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;
}


回答4:

           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;
                }