天天看点

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;
        }
    }
}