Android Paint&Xfermode总结

setXfermode(Xfermode xfermode)

Xfermode渡模式,在使用Paint的时候,我们能通过使用PorterDuffXfermodeXfermode能够完成图像组合的效果将绘制的图形的像素和Canvas上对应位置的像素按照一定的规则进行混合,形成新的像素,再更新到Canvas中形成最终的图形,由于AvoidXfermode, PixelXorXfermode都已经被标注为过时了,所以这次主要研究的是仍然在使用的PorterDuffXfermode。具体效果见下图:





这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:


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



源码详解
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;
}

/**
 * @hide
 */
public static int modeToInt(Mode mode) {
    return mode.nativeInt;
}

/**
 * @hide
 */
public static Mode intToMode(int val) {
    switch (val) {
        default:
        case  0: return Mode.CLEAR;
        case  1: return Mode.SRC;
        case  2: return Mode.DST;
        case  3: return Mode.SRC_OVER;
        case  4: return Mode.DST_OVER;
        case  5: return Mode.SRC_IN;
        case  6: return Mode.DST_IN;
        case  7: return Mode.SRC_OUT;
        case  8: return Mode.DST_OUT;
        case  9: return Mode.SRC_ATOP;
        case 10: return Mode.DST_ATOP;
        case 11: return Mode.XOR;
        case 16: return Mode.DARKEN;
        case 17: return Mode.LIGHTEN;
        case 13: return Mode.MULTIPLY;
        case 14: return Mode.SCREEN;
        case 12: return Mode.ADD;
        case 15: return Mode.OVERLAY;
    }
}


在上述的计算公式当中关键字的意思:
alpha------透明度
C------颜色
src------原图
dst------目标图
out------输出


具体应用

图MyView实现代码
public class MyView extends View {

    Paint mPaint;
    float mItemSize = 0;
    float mItemHorizontalOffset = 0;
    float mItemVerticalOffset = 0;
    float mCircleRadius = 0;
    float mRectSize = 0;
    /**
     * 红色
     */
    int mCircleColor = 0xfbd41511;
    /**
     * 蓝色
     */
    int mRectColor = 0xff9000ff;
    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]);
                //画矩形
                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;
    }



实例代码镜像或倒影



public class InverteImgView extends View {
    private Paint mBitPaint;
    private Bitmap BmpDST,BmpSRC,BmpRevert;
    public InverteImgView(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.timg,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);
    }
}

 注:

 matrix.setScale(1F, -1F);
/** Set the matrix to scale by sx and sy. */              
public void setScale(float sx, float sy) {
    nSetScale(native_instance, sx, sy);
}

当sx为-1时为左右镜像效果如下图 :         sy为-1效果倒影如上图:


    setLayerType


圆形


public class CircleImageView extends View {
    private Paint mBitPaint;
    private Bitmap BDST,BSRC;

    public CircleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        BDST = BitmapFactory.decodeResource(getResources(),R.drawable..........,null);
        BSRC = BitmapFactory.decodeResource(getResources(),R.drawable.............,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(BDST,0,0,mBitPaint);
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        canvas.drawBitmap(BSRC,0,0,mBitPaint);

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



猜你喜欢

转载自blog.csdn.net/u014133119/article/details/80769591