1. 程式人生 > >Android影象處理——Paint之Xfermode

Android影象處理——Paint之Xfermode

上篇部落格中,我將我對Paint的ColorFilter相關的幾個子類以及用法做了總結,其中最常用的ColorMatrixColorFilter值得我們多學習學習,通過定義一個color值的4*5的矩陣,來設定Paint的各種各樣的變色效果。此外,還有PorterDuffColorFilter,其實用的並不是很多,但是PorterDuffColorFilter中使用的幾個概念尤其重要,我們要好好了解一下,PorterDuff是老外發明的一直圖形混合模式,並且Android 的API中為我們提供了18種不同的混合模式演算法,我們平時在開發的時候需要了解這些模式分別代表的含義和用法,就是靈活的運用這些模式隨意混合出我們需要的影象效果。

Xfermode

Xfermode具體怎麼翻譯,說實話,我也不知道,我習慣叫它圖片混合模式,隨便了,管它叫什麼,不妨礙我們使用它。關於Xfermode的說明,可以在Google文件中找到這樣的描述:Xfermode是在繪圖通道中自定義“傳輸模式”的基類。靜態函式建立可以呼叫或者返回任意作為模式列舉指定的預定義子類例項。當Xfermode分配給Paint,然後繪製物件與Paint就具備了所新增的xfermode。讀起來比較拗口,下面直接看Xfermode的原始碼:

public class Xfermode {

    protected void finalize() throws Throwable {
        try {
            finalizer(native_instance);
        } finally {
            super.finalize();
        }
    }

    private static native void finalizer(long native_instance);

    long native_instance;
}
看,Xfermode就這麼點程式碼,經驗告訴我們,其下必有子類,擦,變元芳了~~~

檢視一下文件發現Xfermode確實有AvoidXfermode、PixelXorXfermode、PorterDuffXfermode,下面來繼續學習一下3個子類的用法。

AvoidXfermode

看這個子類之前告訴大家一個不幸的訊息,AvoidXfermode不支援硬體加速,在高於API16的機器上不會適用,如果想測試這個子類,1,可以關閉手機的硬體加速模組;2,在AndroidManifest.xml中Application節點上設定硬體加速為false。
android:hardwareAccelerated="false"
在Android Studio下點選檢視一下AvoidXfermode的構造方法:
public AvoidXfermode(int opColor, int tolerance, Mode mode)
AvoidXfermode的構造方法也特別簡單,一共接收3個引數:第一個引數opColor是一個16進位制的帶透明度通道的顏色值,如0X12345678。第二個引數tolerance表示容差值,什麼是容差值呢?可以理解成一個表示“精確”和“模糊”的概念,下面會解釋一下。第三個引數是AvoidXfermode的模式,AvoidXfermode的模式一共有兩種:AvoidXfermode.Mode.TARGET和AvoidXfermode.Mode.AVOID。

AvoidXfermode.Mode.TARGET

在該模式下Android會判斷畫布上的顏色是否會有跟opColor不一樣的顏色,比如我opColor是紅色,那麼在TARGET模式下就會去判斷我們的畫布上是否有存在紅色的地方,如果有,則把該區域“染”上一層我們畫筆定義的顏色,否則不“染”色,而tolerance容差值則表示畫布上的畫素和我們定義的紅色之間的差別該是多少的時候才去“染”的,比如當前畫布有一個畫素的色值是(200, 20, 13),而我們的紅色值為(255, 0, 0),當tolerance容差值為255時,即便(200, 20, 13)並不等於紅色值也會被“染”色,容差值越大“染”色範圍越廣反之則反,空說無憑我們來看看具體的實現和效果:
public class CustomView3 extends View {

    private Paint mPaint;
    private Bitmap mBitmap;
    private Context mContext;
    private int x, y, w, h;
    private AvoidXfermode avoidXfermode;

    public CustomView3(Context context) {
        this(context, null);
    }

    public CustomView3(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        initRes();
        initPaint();

    }

    private void initRes() {
        //載入bitmap
        mBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.image);
        //獲取bitmap的展示起始佈局
        x = ScreenUtil.getScreenW(mContext) / 2 - mBitmap.getWidth() / 2;
        y = ScreenUtil.getScreenH(mContext) / 2 - mBitmap.getHeight() / 2;
        w = ScreenUtil.getScreenW(mContext) / 2 + mBitmap.getWidth() / 2;
        h = ScreenUtil.getScreenH(mContext) / 2 + mBitmap.getHeight() / 2;
    }

    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        avoidXfermode = new AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(mBitmap, x, y, mPaint);
        mPaint.setARGB(255, 211, 53, 243);
        mPaint.setXfermode(avoidXfermode);
        canvas.drawRect(x, y, w, h, mPaint);
    }
}
下面來執行看效果,首先確定一下開啟的模擬器是API16以下的,或者Application節點下設定了關閉“硬體加速”:

AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET):

大家可以看到,在我們的模式為TARGET容差值為0的時候此時只有當圖片中像色顏色值為0XFFFFFFFF的地方才會被染色,而其他地方不會有改變

下面我們來修改一下容差值,將容差值改成255:


AvoidXfermode(0XFFFFFFFF, 255, AvoidXfermode.Mode.TARGET)

而當容差值為255的時候只要是跟0XFFFFFFFF有點接近的地方都會被染色

AvoidXfermode.Mode.AVOID

則與TARGET恰恰相反,TARGET是我們指定的顏色是否與畫布的顏色一樣,而AVOID是我們指定的顏色是否與畫布不一樣,其他的都與TARGET類似
AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.AVOID):

當模式為AVOID容差值為0時,只有當圖片中畫素顏色值與0XFFFFFFFF完全不一樣的地方才會被染色
AvoidXfermode(0XFFFFFFFF, 255, AvoidXfermode.Mode.AVOID):


當容差值為255時,只要與0XFFFFFFFF稍微有點不一樣的地方就會被染色
那麼這玩意究竟有什麼用呢?比如說當我們只想在白色的區域畫點東西或者想把白色區域的地方替換為另一張圖片的時候就可以採取這種方式!

PixelXorXfermode

PixelXorXfermode是Xfermode下的另外一種影象混排模式,該類特別簡單,不過呢,也很不幸的,在API16中已經過時了。我們來做一個簡單的瞭解,先看PixelXorXfermode的構造方法:

public PixelXorXfermode(int opColor) 
構造方法很簡單,只要傳遞一個16進位制帶透明通道的顏色值即可,那麼這個引數有什麼用呢?我在Google文件中,找到了這樣的一個演算法:實際上PixelXorXfermode內部是按照“opColor ^ src ^ dst”這個異或演算法運算的,得到一個不透明的(alpha = 255)的色彩值,設定到影象中,下面我們接著上面用到的圖片Demo寫個PixelXorXfermode的Demo:
public class CustomView3 extends View {

    private Paint mPaint;
    private Bitmap mBitmap;
    private Context mContext;
    private int x, y, w, h;
    private PixelXorXfermode pixelXorXfermode;

    public CustomView3(Context context) {
        this(context, null);
    }

    public CustomView3(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        initRes();
        initPaint();

    }

    private void initRes() {
        //載入bitmap
        mBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.image);
        //獲取bitmap的展示起始佈局
        x = ScreenUtil.getScreenW(mContext) / 2 - mBitmap.getWidth() / 2;
        y = ScreenUtil.getScreenH(mContext) / 2 - mBitmap.getHeight() / 2;
        w = ScreenUtil.getScreenW(mContext) / 2 + mBitmap.getWidth() / 2;
        h = ScreenUtil.getScreenH(mContext) / 2 + mBitmap.getHeight() / 2;
    }

    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        pixelXorXfermode = new PixelXorXfermode(0XFFFF0000);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //先繪製Bitmap,src
        canvas.drawBitmap(mBitmap, x, y, mPaint);
        //隨便設定一個純色測試
        mPaint.setARGB(255, 211, 53, 243);
        //設定Xfermode
        mPaint.setXfermode(pixelXorXfermode);
        //在bitmap上混排一個純色的矩形(dst)
        canvas.drawRect(x, y, w, h, mPaint);
    }
}
混排後的影象是:

PixelXorXfermode在底層已經取出src,dst每個畫素點與opColor進行了opColor ^ src ^ dst運算了,結果輸出就是上圖所示的那樣!好了,我只學這麼多了,因為它已經過時了,同樣上面的AvoidXfermode也是,過時了,瞭解即可。下面是對Xfermode的第三個子類,也是唯一一個還沒有過時的,非常重要的子類PorterDuffXfermode的學習。

PorterDuffXfermode

同樣PorterDuffXfermode也是Xfermode的子類,我們先看看它的構造方法:
public PorterDuffXfermode(PorterDuff.Mode mode)
PorterDuffXfermode的構造方法很簡單,構造方法中需要傳遞一個PorterDuff.Mode引數,關於PorterDuff.Mode,我們在上篇部落格中已經學習完了,其實跟ColorFilter的子類PorterDuffColorFilter的混排模式是一樣的。Android系統一共提供了18種混排模式,在模擬器的ApiDemos/Graphics/XferModes,有張效果圖: 這張圖可以很形象的說明圖片各種混排模式下的效果。其中Src代表原圖,Dst代表目標圖,兩張圖片使用不同的混排方式後,得到的影象是如上圖所示的。PorterDuff.Mode也提供了18種混排模式已經演算法,其中比上圖多了ADD和OVERLAY兩種模式: 其中Sa全稱為Source alpha表示源圖的Alpha通道;Sc全稱為Source color表示源圖的顏色;Da全稱為Destination alpha表示目標圖的Alpha通道;Dc全稱為Destination color表示目標圖的顏色,[...,..]前半部分計算的是結果影象的Alpha通道值,“,”後半部分計算的是結果影象的顏色值。影象混排後是依靠這兩個值來重新計算ARGB值的,具體計算演算法,抱歉,我也不知道,不過不要緊,不瞭解計算演算法也不影響我們程式設計師寫程式的。我們只要對照上面的apiDemo中提供的圖片就能推測出混排後的結果的,下面將會對照ApiDemos/Graphics/XferModes的程式進行修改,來測試各個模組的效果,測試程式如下:
public class XfermodeView extends View {

    //PorterDuff模式常量 可以在此更改不同的模式測試
    private static final PorterDuff.Mode MODE = PorterDuff.Mode.CLEAR;
    private PorterDuffXfermode porterDuffXfermode;
    private int screenW, screenH; //螢幕寬高
    private Bitmap srcBitmap, dstBitmap;
    //源圖和目標圖寬高
    private int width = 120;
    private int height = 120;

    public XfermodeView(Context context) {
        this(context, null);
    }

    public XfermodeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        screenW = ScreenUtil.getScreenW((Activity) context);
        screenH = ScreenUtil.getScreenH((Activity) context);
        //建立一個PorterDuffXfermode物件
        porterDuffXfermode = new PorterDuffXfermode(MODE);
        //建立原圖和目標圖
        srcBitmap = makeSrc(width, height);
        dstBitmap = makeDst(width, height);
    }

    //建立一個圓形bitmap,作為dst圖
    private Bitmap makeDst(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0xFFFFCC44);
        c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), p);
        return bm;
    }

    // 建立一個矩形bitmap,作為src圖
    private Bitmap makeSrc(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0xFF66AAFF);
        c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, p);
        return bm;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setFilterBitmap(false);
        paint.setStyle(Paint.Style.FILL);
        //繪製“src”藍色矩形原圖
        canvas.drawBitmap(srcBitmap, screenW / 8 - width / 4, screenH / 12 - height / 4, paint);
        //繪製“dst”黃色圓形原圖
        canvas.drawBitmap(dstBitmap, screenW / 2, screenH / 12, paint);

        //建立一個圖層,在圖層上演示圖形混合後的效果
        int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.MATRIX_SAVE_FLAG |
                Canvas.CLIP_SAVE_FLAG |
                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                Canvas.CLIP_TO_LAYER_SAVE_FLAG);

        //先繪製“dst”黃色圓形
        canvas.drawBitmap(dstBitmap, screenW / 4, screenH / 3, paint);
        //設定Paint的Xfermode
        paint.setXfermode(porterDuffXfermode);
        canvas.drawBitmap(srcBitmap, screenW / 4, screenH / 3, paint);
        paint.setXfermode(null);
        // 還原畫布
        canvas.restoreToCount(sc);
    }
}
為了方便觀察,需要將Activity_main.xml的背景色設定為黑色。

1.PorterDuff.Mode.CLEAR。中文描述:所繪製源影象不會提交到畫布上。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.CLEAR;

2.PorterDuff.Mode.SRC。中文描述:只顯示源影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.SRC;


3.PorterDuff.Mode.DST。中文描述:只顯示目標影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.DST;

4.PorterDuff.Mode.SRC_OVER。中文描述:正常繪製顯示,源影象居上顯示。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.SRC_OVER;

5.PorterDuff.Mode.DST_OVER。中文描述: 上下層都顯示。目標影象居上顯示。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.DST_OVER;

6.PorterDuff.Mode.SRC_IN。中文描述: 取兩層繪製交集中的源影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.SRC_IN;

7.PorterDuff.Mode.DST_IN。中文描述:取兩層繪製交集中的目標影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.DST_IN;

8.PorterDuff.Mode.SRC_OUT。中文描述:只在源影象和目標影象不相交的地方繪製源影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.SRC_OUT;

9.PorterDuff.Mode.DST_OUT。中文描述:只在源影象和目標影象不相交的地方繪製目標影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.DST_OUT;

10.PorterDuff.Mode.SRC_ATOP。中文描述:在源影象和目標影象相交的地方繪製源影象,在不相交的地方繪製目標影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.SRC_ATOP;

11.PorterDuff.Mode.DST_ATOP。中文描述:在源影象和目標影象相交的地方繪製目標影象而在不相交的地方繪製源影象。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.DST_ATOP;


12.PorterDuff.Mode.XOR。中文描述:異或:去除兩圖層交集部分

private static final PorterDuff.Mode MODE = PorterDuff.Mode.XOR;

13.PorterDuff.Mode.DARKEN。中文描述:取兩圖層全部區域,交集部分顏色加深

private static final PorterDuff.Mode MODE = PorterDuff.Mode.DARKEN;

14.PorterDuff.Mode.LIGHTEN。中文描述:取兩圖層全部,點亮交集部分顏色

private static final PorterDuff.Mode MODE = PorterDuff.Mode.LIGHTEN;

15.PorterDuff.Mode.MULTIPLY。中文描述:取兩圖層交集部分疊加後顏色

private static final PorterDuff.Mode MODE = PorterDuff.Mode.MULTIPLY;

16.PorterDuff.Mode.SCREEN。中文描述:濾色。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.SCREEN;

以下是android中新加的兩種模式:

17.ADD。中文描述:飽和度相加。

private static final PorterDuff.Mode MODE = PorterDuff.Mode.ADD;

18.OVERLAY。中文描述:疊加

private static final PorterDuff.Mode MODE = PorterDuff.Mode.OVERLAY;

原始碼請在這裡下載,ps:原始碼在Android Studio中構建。