android高级UI之Paint Xfermode

在上一次https://www.cnblogs.com/webor2006/p/12660322.html学习了Paint的第二高级用法之滤镜效果,接下来这次将它的最后一个高级用法给搞定----Xfermode,这个其实在Android SDK中的APIDemo中详细提到过,我记得好几年前就想研究它,但是一直没敢研透它,所以这次必须攻克它!!

了解Xfermode:

Xfermode是干嘛用的呢?其实它的使用比较简单,跟滤镜效果一样,在Paint类中有一个专门设置Xfermode的方法:

看一下这方法的说明:

然后再看一下Xfermode这个类的说明:

 

那还是有头雾水,好,接下来看一张贴图你就熟悉了:

是不是似曾相识,是的,就是在ApiDemo中的一个例子,当时看到这觉得挺神奇的,但是就是搞不懂具体有啥用,其实从图中大概能感爱到XFermode其实就是在使用图形之间的相互组合以达到自己的想要的效果,图中可以看到有16种类型,其实在Java中有相关枚举的定义,它里面其实是有18种,瞅一下:

这些一时半会也不可能完全搞明白,先整体有个了解既可,得在未来的工作中进行巩固,这里先完整对18个模式进行一下理论说明,不过要能理解得先提前明白一个大前提:

上图中不是每个模式都有两种颜色的图形么?黄色的圆+蓝色的矩形,这里要明白:蓝色矩形表示的是源图片,黄色圆表示的是目标图片,也就是:

 

好,下面再来了解各个模式的特点:

  • ADD:饱和相加,对图像饱和度进行相加,【不常用】。
  • CLEAR:清除目标图像,其清除的部位则是与原图相交的部分。
  • DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合。
  • DST:只显示目标图像。
  • DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响。
  • DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响。
  • DST_OUT:只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤。
  • DST_OVER:将目标图像放在源图像上方。
  • LIGHTEN:变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关。
  • MULTIPLY:正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值。
  • OVERLAY:叠加。
  • SCREEN:滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖。
  • SRC:只显示源图像。
  • SRC_ATOP:在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响。
  • SRC_IN:只在源图像和目标图像相交的地方绘制【源图像】。
  • SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤。
  • SRC_OVER:将源图像放在目标图像上方。
  • XOR:在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制。

了解Xfermode的本质:

对于Xfermode有如此多的模式,每个记住也是不现实的,最好掌握它的方法就是先对它的本质有一个了解:在上面我们也说过Xfermode它的作用就是将绘制的图形的像素和Canvas上对应位置的像素按照一定的规则进行混合,此时就需要了解它到底是怎么进行混合的,此时就需要来看一下官方对于各个模式的说明了:

public enum Mode {
        // these value must match their native equivalents. See SkXfermode.h
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" />
         *     <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = 0\)</p>
         * <p>\(C_{out} = 0\)</p>
         */
        CLEAR       (0),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC.png" />
         *     <figcaption>The source pixels replace the destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src}\)</p>
         * <p>\(C_{out} = C_{src}\)</p>
         */
        SRC         (1),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST.png" />
         *     <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
         * <p>\(C_{out} = C_{dst}\)</p>
         */
        DST         (2),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OVER.png" />
         *     <figcaption>The source pixels are drawn over the destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
         * <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
         */
        SRC_OVER    (3),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_OVER.png" />
         *     <figcaption>The source pixels are drawn behind the destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p>
         * <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
         */
        DST_OVER    (4),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_IN.png" />
         *     <figcaption>Keeps the source pixels that cover the destination pixels,
         *     discards the remaining source and destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
         * <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p>
         */
        SRC_IN      (5),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_IN.png" />
         *     <figcaption>Keeps the destination pixels that cover source pixels,
         *     discards the remaining source and destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
         * <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p>
         */
        DST_IN      (6),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OUT.png" />
         *     <figcaption>Keeps the source pixels that do not cover destination pixels.
         *     Discards source pixels that cover destination pixels. Discards all
         *     destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p>
         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p>
         */
        SRC_OUT     (7),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_OUT.png" />
         *     <figcaption>Keeps the destination pixels that are not covered by source pixels.
         *     Discards destination pixels that are covered by source pixels. Discards all
         *     source pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p>
         * <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p>
         */
        DST_OUT     (8),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_ATOP.png" />
         *     <figcaption>Discards the source pixels that do not cover destination pixels.
         *     Draws remaining source pixels over destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
         * <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
         */
        SRC_ATOP    (9),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_ATOP.png" />
         *     <figcaption>Discards the destination pixels that are not covered by source pixels.
         *     Draws remaining destination pixels over source pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src}\)</p>
         * <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
         */
        DST_ATOP    (10),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_XOR.png" />
         *     <figcaption>Discards the source and destination pixels where source pixels
         *     cover destination pixels. Draws remaining source pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
         */
        XOR         (11),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_DARKEN.png" />
         *     <figcaption>Retains the smallest component of the source and
         *     destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)</p>
         */
        DARKEN      (16),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_LIGHTEN.png" />
         *     <figcaption>Retains the largest component of the source and
         *     destination pixel.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\)</p>
         */
        LIGHTEN     (17),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_MULTIPLY.png" />
         *     <figcaption>Multiplies the source and destination pixels.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
         * <p>\(C_{out} = C_{src} * C_{dst}\)</p>
         */
        MULTIPLY    (13),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_SCREEN.png" />
         *     <figcaption>Adds the source and destination pixels, then subtracts the
         *     source pixels multiplied by the destination.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
         * <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p>
         */
        SCREEN      (14),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_ADD.png" />
         *     <figcaption>Adds the source pixels to the destination pixels and saturates
         *     the result.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p>
         * <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p>
         */
        ADD         (12),
        /**
         * <p>
         *     <img src="{@docRoot}reference/android/images/graphics/composite_OVERLAY.png" />
         *     <figcaption>Multiplies or screens the source and destination depending on the
         *     destination color.</figcaption>
         * </p>
         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
         * <p>\(\begin{equation}
         * C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\
         * \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) & otherwise \end{cases}
         * \end{equation}\)</p>
         */
        OVERLAY     (15);

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

        /**
         * @hide
         */
        public final int nativeInt;
    }

其中发现每个模式的注释貌似都是HTML的,其中貌似有点看不到,拿其中的一个模式的说明举例:

 其实关于它的含义在枚举类上有说明:

在上述的过程中有提到(\alpha_{out})表示透明输出值,(C_{out}).表示是颜色输出值,这就道出了Xfermode的一个核心本质:其实就是通过两个图形计算的到这两个关键输出值就是构成我们图形混合,这里看一个更加复杂的模式计算公式:

这里再对其进行一个解释:一个像素的颜色都是由四个分量组成,即ARGB,A表示的是我们Alpha值,RGB表示的是颜色
S表示的是原像素,原像素的值表示[Sa,Sc] Sa表示的就是源像素的Alpha值,Sc表示源像素的颜色值。所以要理解上述公式把握以下关键点既可:

alpha------透明度。
C------颜色。
src------源图。
dst------目标图。
out------输出。 

这样对于不同的模式其实也就是公式不一样而已。

混合模式分类:

要想更好的对所有的模式有一个比较好的记忆,其实是有技巧可寻的,它们可以分为三大类:

SRC相关:优先显示的源图

DST相关:优先显示的是目标图

其它相关:

Xfermode实际运用:

好,在对Xfermode有一定了解之后,那它具体对于实际工作有何作用呢?所以下面会以实际的经典案例作为代表感受一下,当然它的作用远不止这些,具体就得在实际工作中来挖掘了,这里先将在开篇显示的16种模式的代码贴一下,也道出来Xfermode的使用方式,比较简单:

package com.paintgradient.test.xfermode;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {

    Paint mPaint;
    float mItemSize = 0;
    float mItemHorizontalOffset = 0;
    float mItemVerticalOffset = 0;
    float mCircleRadius = 0;
    float mRectSize = 0;
    int mCircleColor = 0xffffcc44;//黄色
    int mRectColor = 0xff66aaff;//蓝色
    float mTextSize = 25;

    private static final Xfermode[] sModes = {
            new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
            new PorterDuffXfermode(PorterDuff.Mode.SRC),
            new PorterDuffXfermode(PorterDuff.Mode.DST),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
            new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
            new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
            new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
            new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
            new PorterDuffXfermode(PorterDuff.Mode.XOR),
            new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
            new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
            new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
            new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
    };

    private static final String[] sLabels = {
            "Clear", "Src", "Dst", "SrcOver",
            "DstOver", "SrcIn", "DstIn", "SrcOut",
            "DstOut", "SrcATop", "DstATop", "Xor",
            "Darken", "Lighten", "Multiply", "Screen"
    };

    public MyView(Context context) {
        super(context);
        init(null, 0);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

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

    private void init(AttributeSet attrs, int defStyle) {
        if (Build.VERSION.SDK_INT >= 11) {
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setTextSize(mTextSize);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setStrokeWidth(2);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置背景色
        //canvas.drawARGB(255, 139, 197, 186);

        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();

        for (int row = 0; row < 4; row++) {
            for (int column = 0; column < 4; column++) {
                canvas.save();
                int layer = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
                mPaint.setXfermode(null);
                int index = row * 4 + column;
                float translateX = (mItemSize + mItemHorizontalOffset) * column;
                float translateY = (mItemSize + mItemVerticalOffset) * row;
                canvas.translate(translateX, translateY);
                //画文字
                String text = sLabels[index];
                mPaint.setColor(Color.BLACK);
                float textXOffset = mItemSize / 2;
                float textYOffset = mTextSize + (mItemVerticalOffset - mTextSize) / 2;
                canvas.drawText(text, textXOffset, textYOffset, mPaint);
                canvas.translate(0, mItemVerticalOffset);
                //画边框
                mPaint.setStyle(Paint.Style.STROKE);
                mPaint.setColor(0xff000000);
                canvas.drawRect(2, 2, mItemSize - 2, mItemSize - 2, mPaint);
                mPaint.setStyle(Paint.Style.FILL);
                //画圆,也就是目标图
                mPaint.setColor(mCircleColor);
                float left = mCircleRadius + 3;
                float top = mCircleRadius + 3;
                canvas.drawCircle(left, top, mCircleRadius, mPaint);
                mPaint.setXfermode(sModes[index]);//xfermode核心用法
                //画矩形,也就是源图
                mPaint.setColor(mRectColor);
                float rectRight = mCircleRadius + mRectSize;
                float rectBottom = mCircleRadius + mRectSize;
                canvas.drawRect(left, top, rectRight, rectBottom, mPaint);
                mPaint.setXfermode(null);
                //canvas.restore();
                canvas.restoreToCount(layer);
            }
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mItemSize = w / 4.5f;
        mItemHorizontalOffset = mItemSize / 6;
        mItemVerticalOffset = mItemSize * 0.426f;
        mCircleRadius = mItemSize / 3;
        mRectSize = mItemSize * 0.6f;
    }
}

核心代码其实比较好理解,就不多说了。

SRC相关实际案例:

SRC_OUT:

只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤。

应用一:橡皮擦效果

效果:

具体代码:

package com.paintgradient.test.xfermode;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.paintgradient.test.R;

public class EraserView_SRCOUT extends View {
    private Paint mBitPaint;
    private Bitmap BmpDST, BmpSRC;
    private Path mPath;
    private float mPreX, mPreY;

    public EraserView_SRCOUT(Context context, AttributeSet attrs) {
        super(context, attrs);

        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        mBitPaint.setColor(Color.RED);
        mBitPaint.setStyle(Paint.Style.STROKE);
        mBitPaint.setStrokeWidth(45);

        BmpSRC = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy6, null);
        BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        //先把手指轨迹画到目标Bitmap上
        Canvas c = new Canvas(BmpDST);
        c.drawPath(mPath, mBitPaint);

        //然后把目标图像画到画布上
        canvas.drawBitmap(BmpDST, 0, 0, mBitPaint);

        //计算源图像区域
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        canvas.drawBitmap(BmpSRC, 0, 0, mBitPaint);

        mBitPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(event.getX(), event.getY());
                mPreX = event.getX();
                mPreY = event.getY();
                return true;
            case MotionEvent.ACTION_MOVE:
                float endX = (mPreX + event.getX()) / 2;
                float endY = (mPreY + event.getY()) / 2;
                mPath.quadTo(mPreX, mPreY, endX, endY);
                mPreX = event.getX();
                mPreY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        postInvalidate();
        return super.onTouchEvent(event);
    }
}

其实实现原理比较简单,就是先绘制目标图,然后设置一下xfermode的模式为SRC_OUT,最后设置目标图,目标就是根据手势来绘制图像,由于模式的作用,相交处就空白了,所以就能实现这种效果:

应用二:刮刮乐

有了橡皮效果,那刮刮乐的实现那就雷同了。

效果:

 

具体代码:

package com.paintgradient.test.xfermode;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.paintgradient.test.R;

public class GuaGuaCardView_SRCOUT extends View {
    private Paint mBitPaint;
    private Bitmap BmpDST, BmpSRC, BmpText;
    private Path mPath;
    private float mPreX, mPreY;

    public GuaGuaCardView_SRCOUT(Context context, AttributeSet attrs) {
        super(context, attrs);

        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        mBitPaint.setColor(Color.RED);
        mBitPaint.setStyle(Paint.Style.STROKE);
        mBitPaint.setStrokeWidth(45);

        BmpText = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka_text1, null);
        BmpSRC = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka, null);
        BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawBitmap(BmpText, 0, 0, mBitPaint);

        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        //先把手指轨迹画到目标Bitmap上
        Canvas c = new Canvas(BmpDST);
        c.drawPath(mPath, mBitPaint);

        //然后把目标图像画到画布上
        canvas.drawBitmap(BmpDST, 0, 0, mBitPaint);

        //计算源图像区域
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        canvas.drawBitmap(BmpSRC, 0, 0, mBitPaint);

        mBitPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(event.getX(), event.getY());
                mPreX = event.getX();
                mPreY = event.getY();
                return true;
            case MotionEvent.ACTION_MOVE:
                float endX = (mPreX + event.getX()) / 2;
                float endY = (mPreY + event.getY()) / 2;
                mPath.quadTo(mPreX, mPreY, endX, endY);
                mPreX = event.getX();
                mPreY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        postInvalidate();
        return super.onTouchEvent(event);
    }
}

其中底图为:

 

然后上面待刮的图为:

而具体实现基本跟上面橡皮擦的雷同,只是说绘制路径是在上面待刮的图片上进行的:

SRC_IN:

只在源图像和目标图像相交的地方绘制【源图像】。

应用一:倒影图像

效果:

关于倒影效果也可以用之前https://www.cnblogs.com/webor2006/p/12178704.html咱们学的Paint的渲染技法来达成。

具体代码:

package com.paintgradient.test.xfermode;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;

import com.paintgradient.test.R;

public class InvertedImageView_SRCIN extends View {
    private Paint mBitPaint;
    private Bitmap BmpDST, BmpSRC, BmpRevert;

    public InvertedImageView_SRCIN(Context context, AttributeSet attrs) {
        super(context, attrs);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.invert_shade, null);
        BmpSRC = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy6, null);

        Matrix matrix = new Matrix();
        matrix.setScale(1F, -1F);
        // 生成倒影图
        BmpRevert = Bitmap.createBitmap(BmpSRC, 0, 0, BmpSRC.getWidth(), BmpSRC.getHeight(), matrix, true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawBitmap(BmpSRC, 0, 0, mBitPaint);

        //再画出倒影
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        canvas.translate(0, BmpSRC.getHeight());

        canvas.drawBitmap(BmpDST, 0, 0, mBitPaint);
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(BmpRevert, 0, 0, mBitPaint);

        mBitPaint.setXfermode(null);

        canvas.restoreToCount(layerId);
    }
}

其中invert_shade图片为:

它其实是一个透明的渐变图:

其实现思路也比较简单:

应用二:圆角图像

效果:

这里目标图为一个圆角图:

也就是长这样:

而源图就是小姐姐:

具体代码:

package com.paintgradient.test.xfermode;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;

import com.paintgradient.test.R;

public class RoundImageView_SRCIN extends View {
    private Paint mBitPaint;
    private Bitmap BmpDST,BmpSRC;

    public RoundImageView_SRCIN(Context context, AttributeSet attrs) {
        super(context, attrs);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.shade,null);
        BmpDST = Bitmap.createScaledBitmap(BmpDST, 500, 400, true);
        BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null);
        BmpSRC = Bitmap.createScaledBitmap(BmpSRC, 500, 400, true);
    }



    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(BmpDST,0,0,mBitPaint);
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(BmpSRC,0,0,mBitPaint);

        mBitPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }
}

就是只汇缓跟目标图相交的源图部分,很明显源图除了四个角没有相交,其它的都跟原图相交了,所以就能达到一个圆角的效果图了。

DST相关实际案例:

DST_IN:

只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响。

应用一:心电图效果:

效果:

具体代码:

package com.paintgradient.test.xfermode;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

import com.paintgradient.test.R;

public class HeartMap_DSTIN extends View {

    private Paint mPaint;
    private int mItemWaveLength = 0;
    private int dx = 0;

    private Bitmap BmpSRC, BmpDST;

    public HeartMap_DSTIN(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.RED);

        BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.heartmap, null);
        BmpSRC = Bitmap.createBitmap(BmpDST.getWidth(), BmpDST.getHeight(), Bitmap.Config.ARGB_8888);

        mItemWaveLength = BmpDST.getWidth();
        startAnim();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Canvas c = new Canvas(BmpSRC);
        //清空bitmap
        c.drawColor(Color.RED, PorterDuff.Mode.CLEAR);
        //画上矩形
        c.drawRect(BmpDST.getWidth() - dx, 0, BmpDST.getWidth(), BmpDST.getHeight(), mPaint);

        //模式合成
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        canvas.drawBitmap(BmpDST, 0, 0, mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(BmpSRC, 0, 0, mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }


    public void startAnim() {
        ValueAnimator animator = ValueAnimator.ofInt(0, mItemWaveLength);
        animator.setDuration(6000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }
}

其中目标图就是一个心电的图,线之外的都是透明的:

由于此模式只会显示相交部分的目标图,所以这里用了一个位移动画一点点从右边来在目标图上面绘制源图,从而就产生了心电图的效果了:

其它相关实际案例:

LIGHTEN:

变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关。较亮的颜色覆盖较暗的颜色,若两者深浅程度相同则混合。

应用:书架 头顶灯光变亮效果

效果:

 

具体代码:

package com.paintgradient.test.xfermode;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;

import com.paintgradient.test.R;

public class LightBookView_LIGHTEN extends View {
    private Paint mBitPaint;
    private Bitmap BmpDST,BmpSRC;

    public LightBookView_LIGHTEN(Context context, AttributeSet attrs) {
        super(context, attrs);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.book_bg,null);
        BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.book_light,null);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(BmpDST,0,0,mBitPaint);
        //目标
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));
        //
        canvas.drawBitmap(BmpSRC,0,0,mBitPaint);

        mBitPaint.setXfermode(null);
        canvas.restoreToCount(layerId);

    }
}

其中book_bg为:

 

而book_light为:

由于网页是白底,所以感觉这图片是空的,其实是长这样,顶部有一个灯光:

 

比较容易理解,就是交叉部分显示亮光,不交叉部分融合,所以最终效果。

MULTIPLY:

正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值。

效果:

具体代码:

public class TwitterView_MULTIPLY extends View {
    private Paint mBitPaint;
    private Bitmap BmpDST, BmpSRC;

    public TwitterView_MULTIPLY(Context context, AttributeSet attrs) {
        super(context, attrs);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.twiter_bg, null);
        BmpSRC = BitmapFactory.decodeResource(getResources(), R.drawable.twiter_light, null);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(BmpDST, 0, 0, mBitPaint);
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
        canvas.drawBitmap(BmpSRC, 0, 0, mBitPaint);

        mBitPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }
}

涉及到两个图片:

twiter_bg:

其实除了内容区其它都是透明的:

twiter_light:

同样也是除内容区外都是透明的:

可以发现最终透明的区域没有像素,只有相交的最后会融合,所以最终的效果就是一个发光小鸟的轮廓。

至此,关于Paint三大高级应用就全部学习完了,也对于Paint的了解更进了一步,之后在实际工作中再慢慢对它进行巩固。

posted on 2020-04-10 09:25  cexo  阅读(696)  评论(0编辑  收藏  举报

导航