最近在做一个Android位图阴影处理的效果,今天把思路总结一下:
分析如下:
Android sdk提供了BlurMaskFilter工具类可以给图片添加阴影效果,代码如下:
—/**
* This takes a mask, and blurs its edge by the specified radius. Whether or
* or not to include the original mask, and whether the blur goes outside,
* inside, or straddles, the original mask’s border, is controlled by the
* Blur enum.
*/
`public class BlurMaskFilter extends MaskFilter {
public enum Blur { NORMAL(0), //!< blur inside and outside of the original border SOLID(1), //!< include the original mask, blur outside OUTER(2), //!< just blur outside the original border INNER(3); //!< just blur inside the original border Blur(int value) { native_int = value; } final int native_int; } /** * Create a blur maskfilter. * * @param radius The radius to extend the blur from the original mask. Must be > 0. * @param style The Blur to use * @return The new blur maskfilter */ public BlurMaskFilter(float radius, Blur style) { native_instance = nativeConstructor(radius, style.native_int); } private static native int nativeConstructor(float radius, int style);
}
`——-
Note:
BlurMaskFilter 可以在指定的半径范围内对一张位图的边缘进行模糊处理。是否包括原始面具,是否对位图内、外或者跨越内外同时进行模糊处理可以通过给代码中的枚举Blur初始化不同参数来决定。
Blur 对应的个各个参数含义分别是:(翻译参考here)
NORMAL(0), //!< 在目标内外显示面具,从边缘向目标内和目标外到离边缘radius宽的地方,向外显示面具时都会同时显示在目标边缘处获得的颜色。
SOLID(1), //!< 在目标外显示面具,从边缘向目标外到离边缘radius宽的地方,并且该部分会显示出从目标边缘获得的颜色,显示目标
OUTER(2), //!< 在目标外显示面具,从边缘向目标外到离边缘radius宽的地方,并且该部分会显示出从目标边缘获得的颜色,不显示目标
INNER(3); //!< 在目标内显示面具,从边缘向目标内到离边缘radius宽的地方显示,radius为初始化BlurMaskFilter的一个值
构造好一个BlurMaskFilter 对象后可以通过android.graphics.Paint类的setMaskFilter方法传给画笔对象,后面我们可以使用这个具有阴影效果的画笔来对位图的边缘进行处理。
现在我们有一个可以进行模糊处理的画笔,下面该怎么利用这个画笔对一个指定的位图的边缘进行处理呢?我们还需要看SDK提供的另外一个工具类,这个类是android.graphics.Bitmap的一个方法extractAlpha,代码如下:
/** * Returns a new bitmap that captures the alpha values of the original. * These values may be affected by the optional Paint parameter, which * can contain its own alpha, and may also contain a MaskFilter which * could change the actual dimensions of the resulting bitmap (e.g. * a blur maskfilter might enlarge the resulting bitmap). If offsetXY * is not null, it returns the amount to offset the returned bitmap so * that it will logically align with the original. For example, if the * paint contains a blur of radius 2, then offsetXY[] would contains * -2, -2, so that drawing the alpha bitmap offset by (-2, -2) and then * drawing the original would result in the blur visually aligning with * the original. * * <p>The initial density of the returned bitmap is the same as the original's. * * @param paint Optional paint used to modify the alpha values in the * resulting bitmap. Pass null for default behavior. * @param offsetXY Optional array that returns the X (index 0) and Y * (index 1) offset needed to position the returned bitmap * so that it visually lines up with the original. * @return new bitmap containing the (optionally modified by paint) alpha * channel of the original bitmap. This may be drawn with * Canvas.drawBitmap(), where the color(s) will be taken from the * paint that is passed to the draw call. */ public Bitmap extractAlpha(Paint paint, int[] offsetXY) { checkRecycled("Can't extractAlpha on a recycled bitmap"); int nativePaint = paint != null ? paint.mNativePaint : 0; Bitmap bm = nativeExtractAlpha(mNativeBitmap, nativePaint, offsetXY); if (bm == null) { throw new RuntimeException("Failed to extractAlpha on Bitmap"); } bm.mDensity = mDensity; return bm; }
Note:
这个方法的作用是,返回一个新的位图,这个位图只是获取了原始位图的透明值Alpha,但是没有RGB,所以我们看到的这个位图是一个黑色的位图。关于位图ARGB的相关知识,可以参考here。
在这个方法里面有两个参数,一个是画笔paint,一个是偏移量offsetXY。我们可以将上面得到的具有阴影效果的画笔传进来,这样得到的新的位图边缘就会有阴影的处理;偏移量offsetXY是用来指定画笔对位图边缘绘制阴影效果的半径范围,这个值是由上面构造BlurMaskFilter 的时候传进的radius参数是决定的。要提到的一点是,新得到的位图大小是可能比原始位图要大。假设BlurMaskFilter构造的时候传入的radius值是6,原始位图大小是144x144,那么新得到的位图大小就是(144+6x2)x(144+6x2)。偏移量offsetXY[ 0 ]=offsetXY[ 1 ]=-6。
原始位图
获取原始位图透明通道后的新位图
现在我们已经得到了边缘具有阴影效果的位图,我们定义为shadowAlphaBitmap,但是这个位图还不是我们期望的最终效果,下面我们需要将这个位图和原始位图进行拼接。
具体思路是这样:
首先定义一个新的画布canvas
给这个画布canvas初始化一张rgba位图,位图的大小跟shadowAlphaBitmap一致。这里面多提一点细节知识,给canvas初始化的bitmap必须是isMutable的类型,意思就是这个bitmap的像素是允许被修改的,不然会报错。比如通过资源id加载的位图就不是可以被改变的,是不可以初始化给canvas使用的。Canvas源码如下:
/** * Construct a canvas with the specified bitmap to draw into. The bitmap * must be mutable. * * <p>The initial target density of the canvas is the same as the given * bitmap's density. * * @param bitmap Specifies a mutable bitmap for the canvas to draw into. */ public Canvas(Bitmap bitmap) { if (!bitmap.isMutable()) { throw new IllegalStateException("Immutable bitmap passed to Canvas constructor"); } throwIfRecycled(bitmap); mNativeCanvas = initRaster(bitmap.ni()); mFinalizer = new CanvasFinalizer(mNativeCanvas); mBitmap = bitmap; mDensity = bitmap.mDensity; }
将shadowAlphaBitmap绘制到画布上
将原始位图制到画布上。绘制原始位图的时候需要注意一下,由于原始位图是比shadowAlphaBitmap小,长宽各小-offsetXY[ 0 ],和-offsetXY[ 1 ],因此我们绘制原始位图的时候需要对原始位图做一下平移,这样才能使得原始位图在画布中居中。此时原始位图和画布之间就是我们期望看到的阴影了。
具体代码如下:
private Bitmap getShadowBitmap(Bitmap srcBitmap){ Paint shadowPaint = new Paint(); BlurMaskFilter blurMaskFilter = new BlurMaskFilter(6,BlurMaskFilter.Blur.NORMAL); shadowPaint.setMaskFilter(blurMaskFilter); int[] offsetXY = new int[2]; Bitmap shadowBitmap = srcBitmap.extractAlpha(shadowPaint,offsetXY); Bitmap canvasBgBitmap = Bitmap.createBitmap( shadowBitmap.getWidth(), shadowBitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(); canvas.setBitmap(canvasBgBitmap); canvas.drawBitmap(shadowBitmap, 0, 0, shadowPaint); canvas.drawBitmap(srcBitmap, -offsetXY[0], -offsetXY[1], null); shadowBitmap.recycle(); return canvasBgBitmap; }
最后得到的边缘有阴影效果的位图
总结:
获取图片阴影效果,重点需要理解下面几个知识点:
- BlurMaskFilter 的用法。
- Bitmap自带方法extractAlpha的含义和用法。
- 位图ARGB基础支持
- 使用画布Canvas修改位图的相关知识
代码github地址