圖像操縱大師Xfermode講解與實戰——Android高級UI

正值猿宵佳節,小盆友在此祝你們新年無BUG。😄java

目錄git

1、前言github

2、PorterDuffXfermodecanvas

3、實戰微信

4、寫在最後ide

1、前言

自定義UI中,少不了對多種圖像的疊加覆蓋,而須要達到預期的目的,咱們便須要今天的主角XfermodeXfermode 有三個孩子,分別是:工具

  1. AvoidXfermode
  2. PixelXorXfermode
  3. PorterDuffXfermode

而 AvoidXfermode 和 PixelXorXfermode 已經在 API 16以後被標記爲removed,因此就只剩下小兒子 PorterDuffXfermode 爲咱們合成圖像,理所固然咱們今天的重點也就在他身上。老規矩,先上幾張實戰圖,而後開始咱們今天的分享。post

Xfermode小工具優化

刮刮卡 動畫

心跳

2、PorterDuffXfermode

咱們看如下兩段源碼,可知 PorterDuffXfermode 做用時經過 Paint的setXfermode 設置,而 PorterDuffXfermode 的實例化其實還須要一個參數,類型爲 PorterDuff.Mode

// Paint 類
public Xfermode setXfermode(Xfermode xfermode) {
    int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
    int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
    if (newMode != curMode) {
        nSetXfermode(mNativePaint, newMode);
    }
    mXfermode = xfermode;
    return xfermode;
}
複製代碼
// PorterDuffXfermode 類
public class PorterDuffXfermode extends Xfermode {
    public PorterDuffXfermode(PorterDuff.Mode mode) {
        porterDuffMode = mode.nativeInt;
    }
}
複製代碼

因此通過上面得知,最終起做用的是 PorterDuff.Mode。進入源碼,會看到如下可用的模式,這段代碼是API 22 的片斷,若是你在比較高的版本看的話會有些許不一樣,但相同模式的計算公式同樣。

public enum Mode {
    /** [0, 0] */
    CLEAR       (0),
    /** [Sa, Sc] */
    SRC         (1),
    /** [Da, Dc] */
    DST         (2),
    /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
    SRC_OVER    (3),
    /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
    DST_OVER    (4),
    /** [Sa * Da, Sc * Da] */
    SRC_IN      (5),
    /** [Sa * Da, Sa * Dc] */
    DST_IN      (6),
    /** [Sa * (1 - Da), Sc * (1 - Da)] */
    SRC_OUT     (7),
    /** [Da * (1 - Sa), Dc * (1 - Sa)] */
    DST_OUT     (8),
    /** [Da, Sc * Da + (1 - Sa) * Dc] */
    SRC_ATOP    (9),
    /** [Sa, Sa * Dc + Sc * (1 - Da)] */
    DST_ATOP    (10),
    /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
    XOR         (11),
    /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
    DARKEN      (12),
    /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
    LIGHTEN     (13),
    /** [Sa * Da, Sc * Dc] */
    MULTIPLY    (14),
    /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
    SCREEN      (15),
    /** Saturate(S + D) */
    ADD         (16),
    OVERLAY     (17);

    Mode(int nativeInt) {
        this.nativeInt = nativeInt;
    }

    /** * @hide */
    public final int nativeInt;
}
複製代碼

每一個模式的效果是怎樣的呢? 咱們先看看官方給出的 Demo 圖。小盆友也跟着手寫了一遍,須要看源碼的童鞋進傳送門

可是,這個 demo 少了同樣東西,那就是透明度,不能全面的體現出Xfermode的威力。因此咱們須要先說明下參數的意思,而後給出咱們較爲全面的demo。

PorterDuff.Mode 源碼中每一個模式的組成都是 [xx, yy] 形式,咱們拿 SRC_OUT 來舉例。

/** [Sa * (1 - Da), Sc * (1 - Da)] */
SRC_OUT     (7),
複製代碼

"xx" 指的就是 Sa * (1 - Da),其值決定了這張合成圖的透明度。而透明度的取值範圍爲 [0, 1]。0表明着徹底透明,而1表明徹底可見。

「yy」 指的就是 Sc * (1 - Da),其值決定了這張合成圖的顏色值。

聰明的童鞋還會注意到 SaDaScDc這幾個值。他們各自表明(結合着英文記,更容易):

  • Sa(Source Alpha):源圖像的透明值;
  • Da(Destination Alpha):目標圖像的透明值;
  • Sc(Source Color):源圖像的色值;
  • Dc(Destination Color):目標圖像的色值;

源圖像目標圖像 又是什麼呢?記住一句話就能夠,先設置的爲目標圖(Dst),後設置的爲源圖(Src)

全部的疑惑咱們已經先點破,接下里就給出咱們比較全面的Demo,這是小盆友以官方所示的十六種模式提供的Xfermode小工具,若是有時候拿捏不許具體使用什麼模式時,能夠進行加入這個工具來進行琢磨。對該小工具感興趣的請進傳送門

接下來咱們便逐個講解模式,所使用的圖片均來自 Xfermode工具 的zinc例子。

一、CLEAR

註釋給出的是 [0, 0] , 透明度 爲0,即徹底看不見顏色 爲0,即無色; 最終呈現以下圖,什麼都沒有。

二、SRC

註釋給出的是[Sa, Sc], 透明度 爲Sa,即取決於源圖的透明值顏色 爲Sc,即取源圖的色值; 最終呈現以下圖,由於都是取源圖的值,因此最終就是顯示 源圖

三、DST

註釋給出的是[Da, Dc],透明度 爲Da,即取目標圖的透明度顏色 爲Dc,即取目標圖的色值; 最終呈現以下圖,由於都取目標圖的值,因此最終呈現的就是 目標圖

四、SRC_OVER

註釋給出的是 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] ,其實就是源圖蓋於目標圖上,如有透明度,則會看到下一層,從名字也能夠很好的記憶。

五、DST_OVER

註釋給出的是 [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc],和 SRC_OVER相反目標圖蓋於源圖上,有透明度的地方能夠看到下一層

六、SRC_IN

註釋給出的是 [Sa * Da, Sc * Da]

透明度爲 Sa * Da,說明 透明度取決源圖和目標圖的各自透明度,只有二者的透明均爲1時(徹底可見),最終成像區域的透明才爲徹底可見,不然會被相應弱化。

色值爲 Sc * Da,說明呈現圖像 色值以源圖渲染

最終呈現效果以下,成像的結果是 目標圖和源圖的交集

七、DST_IN

註釋給出的是 [Sa * Da, Sa * Dc]

透明度爲 Sa * Da,說明 透明度取決源圖和目標圖的各自透明度,只有二者的透明均爲1時(徹底可見),最終成像區域的透明才爲徹底可見,不然會被相應弱化。

色值爲 Sa * Dc,說明呈現圖像 色值以目標圖渲染

最終呈現效果以下,成像的結果是 目標圖和源圖的交集

八、SRC_OUT

註釋給出的是 [Sa * (1 - Da), Sc * (1 - Da)]

透明度爲 Sa * (1 - Da),說明 透明度取決源圖和目標圖的透明度,值得注意的是,目標圖的透明值越大,反而最終結果越弱,即目標圖透明度爲1的地方,則最終圖像不顯示該地方。目標圖透明度不爲1的區域,則會對最終圖進行削弱透明度。目標圖透明度爲0的區域,則不會影響到最終圖像。

色值爲 Sc * (1 - Da),說明呈現圖像 色值以源圖渲染

最終呈現效果以下,成像的結果是 以源圖爲主,剔除與目標圖交集的地方 (由於還受透明度影響)。

九、DST_OUT

註釋給出的是 [Da * (1 - Sa), Dc * (1 - Sa)]

透明度爲 Da * (1 - Sa),說明 透明度取決源圖和目標圖的透明度,值得注意的是,源圖的透明值越大,反而最終結果越弱,即源圖透明度爲1的地方,則最終圖像不顯示該地方。源圖透明度不爲1的區域,則會對最終圖進行削弱透明度。源圖透明度爲0的區域,則不會影響到最終圖像。

色值爲 Dc * (1 - Sa),說明呈現圖像 色值以目標圖渲染

最終呈現效果以下,成像的結果是 以目標圖圖爲主,剔除與源圖交集的地方 (由於還受透明度影響)。

十、SRC_ATOP

註釋給出的是 [Da, Sc * Da + (1 - Sa) * Dc]

透明度爲 Da,說明 最終圖像的可見區域只取決於目標圖像

色值 Sc * Da + (1 - Sa) * Dc,說明由 目標圖和源圖共同決定

最終呈現的效果以下,成像的結果是在 目標圖的區域內,源圖覆蓋在它上面。

十一、DST_ATOP

註釋給出的是 [Sa, Sa * Dc + Sc * (1 - Da)]

透明度爲 Sa,說明 最終圖像的可見區域只取決於源圖像

色值 Sa * Dc + Sc * (1 - Da),說明由 目標圖和源圖共同決定

最終呈現的效果以下,成像的結果是在 源圖的區域內,目標圖覆蓋在它上面。

十二、XOR

註釋給出的是 [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]

透明度 Sa + Da - 2 * Sa * Da,說明 透明受源圖和目標圖的共同影響,當二者透明度爲1時,最終此區域的透明度反而會爲0。

色值 Sa * Dc + Sc * (1 - Da),說明由 目標圖和源圖共同決定

最終呈現的效果以下,成像的結果爲 不相交的地方,以各自的圖像呈現。相交的地方受二者的透明度影響

1三、DARKEN

註釋給出的是 [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]

透明度爲 Sa + Da - Sa*Da,從公式能夠知道 透明度受源圖和目標圖的共同影響,而且最終的透明度數值會大些或是保持原值

色值 Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc),說明由 目標圖和源圖共同決定

最終呈現的效果以下,成像的結果爲 圖像的顏色會稍微偏重些

1四、LIGHTEN

註釋給出的是 [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]

透明度爲 Sa + Da - Sa*Da,從公式能夠知道 透明度受源圖和目標圖的共同影響,而且最終的透明度數值會大些或是保持原值

色值 Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc),說明由 目標圖和源圖共同決定

最終呈現的效果以下,成像的結果爲 相交部分圖像的顏色會偏亮些

在這裏插入圖片描述

1五、MULTIPLY

註釋給出的是 [Sa * Da, Sc * Dc],最終成像以下,與 DST_INSRC_IN 有些相似,只是以灰度顯示。

1六、SCREEN

註釋給出的是 [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],最終成像以下,會削弱相交部分的顏色,呈現出更爲亮的色澤。

在這裏插入圖片描述

1七、ADD

註釋給出的是 Saturate(S + D),效果圖以下

1八、OVERLAY

3、實戰

一、刮刮卡

(1)效果圖

(2)效果分析

想必你們能看出,這裏須要兩層圖,一層爲「黑蜘蛛」的圖,一層爲灰色遮罩。根據咱們手指的滑動軌跡「擦拭掉」該地方的灰色遮罩。最後在手指擡起時,判斷被「擦拭掉」的區域是否已經超出20%,若是超出,則再也不繪製遮罩,達到底層圖顯現的效果。

(3)具體實現

第一步,咱們經過 onTouchEvent 實現記錄手指滑動的軌跡。 但值得注意的是,這裏作了一個小優化,使用了貝塞爾曲線,使滑動軌跡會更加的順滑,具體代碼以下

「貝塞爾曲線」 感興趣的童鞋,能夠查看小盆友的另外一片文章 自帶美感的貝塞爾曲線原理與實戰

public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mPreX = event.getX();
            mPreY = event.getY();
            mPath.moveTo(mPreX, mPreY);
            break;
        case MotionEvent.ACTION_MOVE:
            float endX = (mPreX + event.getX()) / 2;
            float endY = (mPreY + event.getY()) / 2;
            // 此處使用貝塞爾曲線
            mPath.quadTo(mPreX, mPreY, endX, endY);
            mPreX = endX;
            mPreY = endY;
            break;
        case MotionEvent.ACTION_UP:
            post(calculatePixelsRunnable);
            break;
    }
    postInvalidate();
    return true;
}
複製代碼

第二步,咱們須要將獲取到的軌跡做用於 灰色塗層 上,達到「刮卡」效果。這裏其實可使用的模式不止一個,主要看設置的 灰色塗層手指路徑 的前後順序。咱們使用的爲 DST_OUT

這裏值得注意的是,須要開闢一個新的圖層, 以避免模式效果做用到其餘的圖像上。具體代碼以下

// 開闢新的一個圖層
int layer = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

canvas.drawBitmap(mCoatingLayerBitmap, 0, 0, mPaint);
mPaint.setXfermode(mXfermode);
canvas.drawPath(mPath, mPaint);

mCanvas.drawPath(mPath, mPaint);

mPaint.setXfermode(null);

canvas.restoreToCount(layer);
複製代碼

通過這兩步,效果就已經達到,由於咱們繼承的是 ImageView ,因此 「黑蜘蛛」 圖層的放入便已經實現。

第三步,自動去除 「灰色圖層」 的操做,在每次手指擡起時,就會開啓一個線程來計算 「灰色圖層」 的像素色值,若是超過20%被擦拭,則說明能夠去除該 「灰色圖層」。具體代碼以下:

private Runnable calculatePixelsRunnable = new Runnable() {
    @Override
    public void run() {

        int width = getWidth();
        int height = getHeight();

        float totalPixel = width * height;

        int[] pixel = new int[width * height];

        mCoatingLayerBitmap.getPixels(pixel, 0, width, 0, 0, width, height);

        int cleanPixel = 0;
        for (int col = 0; col < height; ++col) {
            for (int row = 0; row < width; ++row) {
                if (pixel[col * width + row] == 0) {
                    cleanPixel++;
                }
            }
        }

        float result = cleanPixel / totalPixel;

        if (result >= PERCENT) {
            isShowAll = true;
            postInvalidate();
        }

    }
};
複製代碼

核心三步便已經在以上實現,剩餘的即是組裝起來,這裏再也不過多贅述,完整代碼請進傳送門

二、心跳

(1)效果圖

(2)動畫分析

咱們藉助以上小盆友手繪的一張圖來說解,綠色的心跳做爲目標圖,藍色的做爲源圖,經過不斷的增大dx的距離,從而讓藍色的源圖寬度不斷縮小,最終使用 DST_IN 模式合成就能夠達到一點點出現的效果。

至於如何讓dx一點點增大,咱們使用了屬性動畫。這個例子比較簡單,咱們就再也不粘貼代碼。有興趣的童鞋請進傳送門

關於 屬性動畫 小盆友在另外一篇博客中有詳細講述其原理和應用,感興趣的話,能夠進傳送門

4、寫在最後

經過Xfermode的多種模式組合能夠繪製出一些酷炫的圖像和效果,限制咱們的永遠仍是咱們的想象力和那懶惰的雙手😄。最後若是你從這篇文章有所收穫,請給我個贊❤️,並關注我吧。文章中若有理解錯誤或是晦澀難懂的語句,請評論區留言,咱們進行討論共同進步。你的鼓勵是我前進的最大動力。

高級UI系列的Github地址:請進入傳送門,若是喜歡的話給我一個star吧😄

若是須要更爲深刻的探討,加我微信吧😁。

相關文章
相關標籤/搜索