標籤:android 陰影 效果
最近在做一個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地址
Android 圖片陰影處理分析!