绘制图像时如何绘制外阴影

17

我目前在我的应用程序中通过画布创建图像的圆角版本。我想在图像周围绘制一个微弱的外阴影,但是我无法完全做到。我有两个问题: 1.如何绘制外部阴影(我只能绘制具有x或y偏移量的阴影) 2.如何绘制阴影,使其不具有附加图像中显示的伪影。代码:

![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]

http://i.stack.imgur.com/d7DV6.png

这是我试图实现的效果示例: enter image description here


@Leonidos 我现在附上了一个例子。你会注意到图像周围有一个微弱的外部阴影。 - RunLoop
5个回答

37

我希望实现类似的效果,但是在AppWidget上无法使用@EvelioTarazona的解决方案。这就是我想出的方法,它应该适用于任何形状的位图。

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

带有参数 size=3, dx=1, dy=3, color=BLACK 的示例

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

1
感谢您发布这样的答案,但是阴影颜色在这里无法工作。请给我建议。 - Balvinder Singh
1
谢谢您的回复,我会尝试的。 - Balvinder Singh
1
你能详细解释一下这个过程吗?特别是矩阵部分。 - Eduardo Naveda
1
@Eddnav 我正在进行基本的矩阵变换,使用内置的缩放来适应中心矩阵,将位图缩放以适应目标高度/宽度。另一个矩阵是将生成的阴影图像移动dx/dy值。我们利用框架提供的矩阵的内置线性代数运算。魔法在于setXfermode调用。我通过试错找到了这个方法,我有两个重叠的图像,其中一个有轻微的dx/dy偏移,xfer模式会创建一个新的位图,使它们重叠。 - Nathan Schwermann
1
如果您想在AppWidget中使用它,请使用remote_views.setImageViewBitmap(R.id.image, shadow);而不是final ImageView iv = (ImageView)findViewById(R.id.image); iv.setImageBitmap(shadow); - lenooh
显示剩余5条评论

18

我们开始吧

http://i.imgur.com/cKi1ckX.png 是的,我仍然喜欢Nexus S

首先,请停止以这种方式掩盖位图,您可以在不分配另一个Bitmap的情况下完成此操作,请查看有关如何绘制圆形(实际上是任何形状)图像的博客文章

其次,使用那个Drawable,您可能可以想出如何添加阴影,只需确保它不被裁剪,在Android 4.3及以上版本中,您可以使用ViewOverlay来实现,还要记住,硬件加速层有一些不支持的绘图操作,其中包括setShadowLayerBlurMaskFilter,如果性能对您不是问题,您可以像往常一样禁用它:

if (SDK_INT >= HONEYCOMB) {
  view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

你可以像之前尝试的那样使用setShadowLayer

somePaint.setShadowLayer(shadowSize, deltaX, deltaY, shadowColor);

请查看链接以获取示例。

如果您仍希望进行硬件加速,则必须冒着超额绘制的风险伪造它,您可以使用径向渐变或绘制另一个模糊的椭圆形(如之前提到的,不能使用BlurMaskFilter),或者使用预先模糊的Bitmap(更多掩模)。

对于这样微妙的阴影效果,如果需要性能,我宁愿选择平面化处理。 完整代码在香蕉摊里

更新:从L开始,您可以使用真正的阴影效果


你好,感谢你的回答和性能提示。不幸的是,我对Android还很陌生,所以我觉得外部阴影部分非常困难。如果你能够发布代码来展示如何创建外部阴影(请参见我在问题中附加的图片),我将授予你奖励。 - RunLoop
@EvelioTarazona 你好,我在代码方面遇到了一些问题。当我调用getBitMap时,我仍然只得到原始图像。是否有可能获得在图像被圆形化后的位图?此外,阴影没有出现。 - RunLoop
@RunLoop 你需要为每个要使用它的视图setLayerType(View.LAYER_TYPE_SOFTWARE, null)(或使用阴影绘制),请阅读文档:http://developer.android.com/reference/android/view/View.html#setLayerType(int%2C%20android.graphics.Paint),如前所述,如果性能对您很重要,则不要使用阴影或者通过绘制其他内容来模拟阴影。请仔细阅读,`getBitmap`方法总是会返回原始的`Bitmap`,如果需要转换的`Bitmap`,则可以像博客文章中提到的那样进行掩码处理,或只需创建一个`Canvas`并为该`RoundedAvatarDrawable`调用`draw`方法。 - eveliotc
嘿,@EvelioTarazona,好信息!很高兴在IO上见到你!我个人正在尝试将阴影添加到一个AppWidget中的位图,所以遗憾的是我不认为这种方法会起作用 :/ - Nathan Schwermann
@EvelioTarazona 是的,我认为我实际上可以使用你编写的drawable类,但仍需要第二个位图,因为我必须创建自己的Canvas来绘制。 - Nathan Schwermann
显示剩余6条评论

5

enter image description here

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

1
如何在顶部和左侧添加阴影? - philtz

2

这是我对sharmitha答案的修改。它使用Kotlin编写,并将bmAlpha的大小用于结果位图,以便阴影不被裁剪。

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
}

2

在drawable文件夹中创建一个名为round_shape.xml的xml文件

<?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>

将这个 round_shape 设为图片视图的背景,如下所示:
<ImageView
     android:id="@+id/img_profile"
     android:layout_width="120dp"
     android:layout_height="120dp"
     android:padding="2dp"
     android:scaleType="fitXY"
     android:src="@drawable/camera" />

上面的代码在您圆形位图后会在图像视图周围创建一个薄层,下面的函数可以实现此功能。
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接