天天看點

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

貝塞爾曲線

PorterDuff及Xfermode

PorterDuff、Xfermode類主要用于圖形合成時的圖像過渡模式計算,其概念來自于1984年在ACM SIGGRAPH計算機圖形學出版物上發表了“Compositing digital images(合成數字圖像)”的Tomas Porter和Tom Duff,合成圖像的概念極大地推動了圖形圖像學的發展,PorterDuffXfermode類名就來源于這倆人的名字組合PorterDuff。

PorterDuff

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線
PorterDuff的16種模式圖:
PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線
Enum Values
PorterDuff.Mode ADD Saturate(S + D)
PorterDuff.Mode CLEAR [0, 0] 所繪制不會送出到畫布上
PorterDuff.Mode DARKEN [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] 取兩圖層全部區域,交集部分顔色加深
PorterDuff.Mode DST [Da, Dc] 顯示下層繪制圖檔
PorterDuff.Mode DST_ATOP [Sa, Sa * Dc + Sc * (1 - Da)] 取上層非交集部分與下層交集部分
PorterDuff.Mode DST_IN [Sa * Da, Sa * Dc] 取兩層繪制交集。顯示下層
PorterDuff.Mode DST_OUT [Da * (1 - Sa), Dc * (1 - Sa)] 取下層繪制非交集部分
PorterDuff.Mode DST_OVER [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] 上下層都顯示。下層居上顯示
PorterDuff.Mode LIGHTEN [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] 取兩圖層全部,點亮交集部分顔色
PorterDuff.Mode MULTIPLY [Sa * Da, Sc * Dc] 取兩圖層交集部分疊加後顔色
PorterDuff.Mode OVERLAY
PorterDuff.Mode SCREEN [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] 取兩圖層全部區域,交集部分變為透明色
PorterDuff.Mode SRC [Sa, Sc] 顯示上層繪制圖檔
PorterDuff.Mode SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc] 取下層非交集部分與上層交集部分
PorterDuff.Mode SRC_IN [Sa * Da, Sc * Da] 取兩層繪制交集。顯示上層
PorterDuff.Mode SRC_OUT [Sa * (1 - Da), Sc * (1 - Da)] 取上層繪制非交集部分
PorterDuff.Mode SRC_OVER [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] 正常繪制顯示,上下層繪制疊蓋
PorterDuff.Mode XOR [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] 異或:去除兩圖層交集部分

Xfermode

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線
Xfermode有三個子類 :前兩個類在API 16被遺棄了,而且不是本文的主題内容
class 說明
AvoidXfermode 指定了一個顔色和容差,強制Paint避免在它上面繪圖(或者隻在它上面繪圖)。
PixelXorXfermode 當覆寫已有的顔色時,應用一個簡單的像素異或操作。
PorterDuffXfermode 這是一個非常強大的轉換模式,使用它,可以使用圖像合成的16條Porter-Duff規則的任意一條來控制Paint如何與已有的Canvas圖像進行互動。

要應用轉換模式,可以使用setXferMode方法,如下所示:

AvoidXfermode avoid = new AvoidXfermode(Color.BLUE, , AvoidXfermode.Mode. AVOID);
paint.setXfermode(avoid);
           

舉個栗子

Thanks To http://www.jianshu.com/p/d11892bbe055

當Alpha通道的值為1時,圖像完全可見;當Alpha通道值為0時,圖像完全不可見;當Alpha通道的值介于0和1之間時,圖像隻有一部分可見。Alpha通道描述的是圖像的形狀,而不是透明度。

以SCREEN的計算方式為例:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],“[……]”裡分為兩部分,其中“,”前的部分“Sa + Da - Sa * Da”計算的值代表SCREEN模式的Alpha通道,而“,”後的部分“Sc + Dc - Sc * Dc”計算SCREEN模式的顔色值,圖形混合後的圖檔依靠這個矢量來計算ARGB的值。關于圖像Alpha合成的知識詳見維基百科Alpha compositing。

Demo

public class PorterDuffXfermodeView extends View {
    private Paint mPaint;
    private Bitmap dstBmp, srcBmp;
    private RectF dstRect, srcRect;

    private Xfermode mXfermode;
    private PorterDuff.Mode mPorterDuffMode = PorterDuff.Mode.MULTIPLY;

    public PorterDuffXfermodeView(Context context) {
        super(context);
        init();
    }

    public PorterDuffXfermodeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    private void init(){
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        dstBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.destination);
        srcBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.source);
        mXfermode = new PorterDuffXfermode(mPorterDuffMode);
    }

    public void setPorterDuffMode(int mode) {
        mPorterDuffMode = intToMode(mode);
        mXfermode = new PorterDuffXfermode(mPorterDuffMode);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);
        //背景色設為白色,友善比較效果
        canvas.drawColor(Color.WHITE);
        //将繪制操作儲存到新的圖層,因為圖像合成是很昂貴的操作,将用到硬體加速,這裡将圖像合成的處理放到離屏緩存中進行
        int saveCount = canvas.saveLayer(srcRect, mPaint, Canvas.ALL_SAVE_FLAG);
        //繪制目标圖
        canvas.drawBitmap(dstBmp, null, dstRect, mPaint);
        //設定混合模式
        mPaint.setXfermode(mXfermode);
        //繪制源圖
        canvas.drawBitmap(srcBmp, null, srcRect, mPaint);
        //清除混合模式
        mPaint.setXfermode(null);
        //還原畫布
        canvas.restoreToCount(saveCount);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int width = w <= h ? w : h;
        int centerX = w / ;
        int centerY = h / ;
        int quarterWidth = width / ;
        srcRect = new RectF(centerX - quarterWidth, centerY - quarterWidth, centerX + quarterWidth, centerY + quarterWidth);
        dstRect = new RectF(centerX - quarterWidth, centerY - quarterWidth, centerX + quarterWidth, centerY + quarterWidth);
    }

    public static final PorterDuff.Mode intToMode(int val) {
        switch (val) {
            default:
            case  : return PorterDuff.Mode.CLEAR;
            case  : return PorterDuff.Mode.SRC;
            case  : return PorterDuff.Mode.DST;
            case  : return PorterDuff.Mode.SRC_OVER;
            case  : return PorterDuff.Mode.DST_OVER;
            case  : return PorterDuff.Mode.SRC_IN;
            case  : return PorterDuff.Mode.DST_IN;
            case  : return PorterDuff.Mode.SRC_OUT;
            case  : return PorterDuff.Mode.DST_OUT;
            case  : return PorterDuff.Mode.SRC_ATOP;
            case : return PorterDuff.Mode.DST_ATOP;
            case : return PorterDuff.Mode.XOR;
            case : return PorterDuff.Mode.DARKEN;
            case : return PorterDuff.Mode.LIGHTEN;
            case : return PorterDuff.Mode.MULTIPLY;
            case : return PorterDuff.Mode.SCREEN;
            case : return PorterDuff.Mode.ADD;
            case : return PorterDuff.Mode.OVERLAY;
        }
    }
}
           

其中用到的圖檔資源分别如下:

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

destination.png

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

source.png

CLEAR

清除模式,[0, 0],即圖像中所有像素點的alpha和顔色值均為0,Demo中的實際效果就是白色背景,圖略。

SRC

[Sa, Sc],隻保留源圖像的 alpha 和 color ,是以繪制出來隻有源圖,如source。有時候會感覺分不清先繪制的是源圖還是後繪制的是源圖,這個時候可以這麼記,先繪制的是目标圖。

DST

[Da, Dc],隻保留了目标圖像的alpha和color值,是以繪制出來的隻有目标圖,如destination。

SRC_OVER

[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc],在目标圖像上層繪制源圖像

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

DST_OVER

[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc],與SRC_OVER相反,此模式是目标圖像被繪制在源圖像的上方

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

SRC_IN

[Sa * Da, Sc * Da],在兩者相交的地方繪制源圖像,并且繪制的效果會受到目标圖像對應地方透明度的影響

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

DST_IN

[Sa * Da, Sa * Dc],可以和SRC_IN 進行類比,在兩者相交的地方繪制目标圖像,并且繪制的效果會受到源圖像對應地方透明度的影響

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

SRC_OUT

[Sa * (1 - Da), Sc * (1 - Da)],從字面上可以了解為在不相交的地方繪制源圖像,那麼我們來看看效果是不是這樣,如下圖。實際上color 是 Sc * ( 1 - Da ) ,表示如果相交處的目标色的alpha是完全不透明的,這時候源圖像會完全被過濾掉,否則會受到相交處目标色 alpha 影響,呈現出對應色值。

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

DST_OUT

[Da * (1 - Sa), Dc * (1 - Sa)],可以類比SRC_OUT , 在不相交的地方繪制目标圖像,相交處根據源圖像alpha進行過濾,完全不透明處則完全過濾,完全透明則不過濾

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

SRC_ATOP

[Da, Sc * Da + (1 - Sa) * Dc],源圖像和目标圖像相交處繪制源圖像,不相交的地方繪制目标圖像,并且相交處的效果會受到源圖像和目标圖像alpha的影響

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

DST_ATOP

[Sa, Sa * Dc + Sc * (1 - Da)],源圖像和目标圖像相交處繪制目标圖像,不相交的地方繪制源圖像,并且相交處的效果會受到源圖像和目标圖像alpha的影響

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

XOR

[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc],在不相交的地方按原樣繪制源圖像和目标圖像,相交的地方受到對應alpha和顔色值影響,按公式進行計算,如果都完全不透明則相交處完全不繪制

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

DARKEN

[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)],該模式處理過後,會感覺效果變暗,即進行對應像素的比較,取較暗值,如果色值相同則進行混合;

從算法上看,alpha值變大,色值上如果都不透明則取較暗值,非完全不透明情況下使用上面算法進行計算,受到源圖和目标圖對應色值和alpha值影響。

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

LIGHTEN

[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],可以和 DARKEN 對比起來看,DARKEN 的目的是變暗,LIGHTEN 的目的則是變亮,如果在均完全不透明的情況下,色值取源色值和目标色值中的較大值,否則按上面算法進行計算。

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

MULTIPLY

[Sa * Da, Sc * Dc],正片疊底,即檢視每個通道中的顔色資訊,并将基色與混合色複合。結果色總是較暗的顔色,任何顔色與黑色複合産生黑色,任何顔色與白色複合保持不變,當用黑色或白色以外的顔色繪畫時,繪畫工具繪制的連續描邊産生逐漸變暗的顔色。

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

SCREEN

[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],濾色,濾色模式與我們所用的顯示屏原理相同,是以也有版本把它翻譯成螢幕;簡單的說就是保留兩個圖層中較白的部分,較暗的部分被遮蓋;當一層使用了濾色(螢幕)模式時,圖層中純黑的部分變成完全透明,純白部分完全不透明,其他的顔色根據顔色級别産生半透明的效果。

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

ADD

Saturate(S + D),飽和度疊加

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

OVERLAY

像素是進行 Multiply (正片疊底)混合還是 Screen (螢幕)混合,取決于底層顔色,但底層顔色的高光與陰影部分的亮度細節會被保留

PorterDuff及Xfermode 貝塞爾曲線貝塞爾曲線

水波紋進度條

Thanks to http://mp.weixin.qq.com/s/GvgcGXhQwnPIimqqkVBOUw?ref=myread

這些隻能夠顯示一些合成的效果,要想實作水波紋的效果還是不夠的,我們還需要借助于貝塞爾曲線來實作水波效果。我們使用到的是Path類的quadTo(x1, y1, x2, y2)方法,屬于二階貝塞爾曲線,使用一張圖來展示二階貝塞爾曲線,這裡的(x1,y1)是控制點,(x2,y2)是終止點,起始點預設是Path的起始點(0,0)。關于使用貝塞爾曲線來實作水波效果的原理就是:通過for循環畫出兩個波紋,我們以WL代表水波紋的長度。需要波紋的-WL點、-3/4*WL點、-1/2*WL、-1/4*WL四個點,通過path的quadTo畫出,并無限重複。

水波紋

public class WaveView extends View {
    private int width;
    private int height;

    private Path mPath;
    private Paint mPathPaint;

    private float mWaveHight = f;//水波紋的高度
    private float mWaveWidth = f;//水波紋的寬度
    private String mWaveColor = "#FFFFFF";//水波顔色
    private int mWaveSpeed = ;

    private Paint mTextPaint;
    private String currentText = "";
    private String mTextColor = "#000000";
    private int mTextSize = ;

    private int maxProgress = ;
    private int currentProgress = ;
    private float currentY;

    private float distance = ;
    private int RefreshGap = ;

    private boolean mIsStop = false;
    private static final int INVALIDATE = ;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mIsStop) {
                return;
            }
            switch (msg.what) {
                case INVALIDATE:
                    invalidate();
                    sendEmptyMessageDelayed(INVALIDATE, RefreshGap);
                    break;
            }
        }
    };

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

    public WaveView(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }

    public WaveView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public void setCurrent(int currentProgress, String currentText) {
        this.currentProgress = currentProgress;
        this.currentText = currentText;
    }

    public void setWaveColor(String mWaveColor) {
        this.mWaveColor = mWaveColor;
    }

    private void init() {
        mPath = new Path();

        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setStyle(Paint.Style.FILL);

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        handler.sendEmptyMessageDelayed(INVALIDATE, );
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
        currentY = height;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(createImage(), , , null);
    }

    private Bitmap createImage() {
        mPathPaint.setColor(Color.parseColor(mWaveColor));
        mTextPaint.setColor(Color.parseColor(mTextColor));
        mTextPaint.setTextSize(mTextSize);

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(bmp);

        float currentMidY = height * (maxProgress - currentProgress) / maxProgress;
        if (currentY > currentMidY) {
            currentY = currentY - (currentY - currentMidY) / ;
        }
        mPath.reset();
        if (maxProgress > currentProgress) {
            //之是以0-distance是因為有原點向上增加的
            mPath.moveTo( - distance, currentY);
            //顯示的區域内的水波紋的數量
            int waveNum = width / ((int) mWaveWidth);
            int num = ;
            for (int i = ; i < waveNum; i++) {
                mPath.quadTo(mWaveWidth * (num + ) - distance, currentY - mWaveHight, mWaveWidth * (num + ) - distance, currentY);
                mPath.quadTo(mWaveWidth * (num + ) - distance, currentY + mWaveHight, mWaveWidth * (num + ) - distance, currentY);
                num += ;
            }
            distance += mWaveWidth / mWaveSpeed;
            distance = distance % (mWaveWidth * );
            mPath.lineTo(width, height);
            mPath.lineTo(, height);
        } else {
            mIsStop = true;
            mPath.moveTo(, );
            mPath.lineTo(, height);
            mPath.lineTo(width, height);
            mPath.lineTo(width, );
        }
        mPath.close();
        canvas.drawPath(mPath, mPathPaint);
        canvas.drawText(currentText, width / , height / , mTextPaint);
        return bmp;
    }
}
           

圖檔水波紋

public class WaveProgressView extends View {
    private int width;
    private int height;

    private Bitmap backgroundBitmap;

    private Path mPath;
    private Paint mPathPaint;

    private float mWaveHight = f;
    private float mWaveWidth = f;
    private String mWaveColor = "#FFFFFF";
    private int mWaveSpeed = ;

    private Paint mTextPaint;
    private String currentText = "";
    private String mTextColor = "#FFFFFF";
    private int mTextSize = ;

    private int maxProgress = ;
    private int currentProgress = ;
    private float currentY;

    private float distance = ;
    private int RefreshGap = ;

    private static final int INVALIDATE = ;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case INVALIDATE:
                    invalidate();
                    sendEmptyMessageDelayed(INVALIDATE, RefreshGap);
                    break;
            }
        }
    };

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

    public WaveProgressView(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }

    public WaveProgressView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public void setCurrent(int currentProgress, String currentText) {
        this.currentProgress = currentProgress;
        this.currentText = currentText;
    }

    public void setWaveColor(String mWaveColor) {
        this.mWaveColor = mWaveColor;
    }

    private void init() {
        backgroundBitmap = getBitmapFromDrawable(getBackground());

        mPath = new Path();

        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setStyle(Paint.Style.FILL);

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        handler.sendEmptyMessageDelayed(INVALIDATE, );
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        width = MeasureSpec.getSize(widthMeasureSpec);
        currentY = height = MeasureSpec.getSize(heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (backgroundBitmap != null) {
            canvas.drawBitmap(createImage(), , , null);
        }
    }

    private Bitmap createImage() {
        mPathPaint.setColor(Color.parseColor(mWaveColor));
        mTextPaint.setColor(Color.parseColor(mTextColor));
        mTextPaint.setTextSize(mTextSize);

        mPathPaint.setColor(Color.parseColor(mWaveColor));
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(bmp);

        float currentMidY = height * (maxProgress - currentProgress) / maxProgress;
        if (currentY > currentMidY) {
            currentY = currentY - (currentY - currentMidY) / ;
        }
        mPath.reset();
        //之是以0-distance是因為有原點向上增加的
        mPath.moveTo( - distance, currentY);
        //顯示的區域内的水波紋的數量
        int waveNum = width / ((int) mWaveWidth);
        int num = ;
        for (int i = ; i < waveNum; i++) {
            mPath.quadTo(mWaveWidth * (num + ) - distance, currentY - mWaveHight, mWaveWidth * (num + ) - distance, currentY);
            mPath.quadTo(mWaveWidth * (num + ) - distance, currentY + mWaveHight, mWaveWidth * (num + ) - distance, currentY);
            num += ;
        }
        distance += mWaveWidth / mWaveSpeed;
        distance = distance % (mWaveWidth * );
        mPath.lineTo(width, height);
        mPath.lineTo(, height);
        mPath.close();
        canvas.drawPath(mPath, mPathPaint);

        backgroundBitmap = Bitmap.createScaledBitmap(backgroundBitmap, width, height, true);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));

        canvas.drawBitmap(backgroundBitmap, , , paint);

        canvas.drawText(currentText, width / , height / , mTextPaint);
        return bmp;
    }

    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            drawable  = getResources().getDrawable(R.mipmap.ic_launcher);
        }

        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
        try {
            Bitmap bitmap;
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(, , canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }
}