Paint 常用方法解析第二篇

Paint 常用方法解析第一篇中分析了Paint的setColorFilter方法,下面接着分析Paint的其它set方法。

3 setMaskFilter

用来给Paint设置遮罩过滤器,该方法接受一个MaskFilter类型的参数,MaskFilter源码如下:

/**
 * MaskFilter is the base class for object that perform transformations on
 * an alpha-channel mask before drawing it. A subclass of MaskFilter may be
 * installed into a Paint. Blur and emboss are implemented as subclasses of MaskFilter.
 */
public class MaskFilter {

    protected void finalize() throws Throwable {
        nativeDestructor(native_instance);
        native_instance = 0;  // Other finalizers can still call us.
    }

    private static native void nativeDestructor(long native_filter);
    long native_instance;
}

从注释中可以得到如下两个信息:
1> MaskFilter是用来在绘制之前变化透明度通道值的基类。
2> MaskFilter不应该被直接使用,即应该使用MaskFilter的子类
Google一共为我们提供了2个MaskFilter子类:

《Paint 常用方法解析第二篇》

3.1 BlurMaskFilter(模糊遮罩过滤器)

先来看看源码:

/**
 * This takes a mask, and blurs its edge by the specified radius. Whether or
 * or not to include the original mask, and whether the blur goes outside,
 * inside, or straddles, the original mask's border, is controlled by the
 * Blur enum.
 */
public class BlurMaskFilter extends MaskFilter {

    public enum Blur {
        /**
         * Blur inside and outside the original border.
         */
        NORMAL(0),

        /**
         * Draw solid inside the border, blur outside.
         */
        SOLID(1),

        /**
         * Draw nothing inside the border, blur outside.
         */
        OUTER(2),

        /**
         * Blur inside the border, draw nothing outside.
         */
        INNER(3);
        
        Blur(int value) {
            native_int = value;
        }
        final int native_int;
    }
    
    /**
     * Create a blur maskfilter.
     *
     * @param radius The radius to extend the blur from the original mask. Must be > 0.
     * @param style  The Blur to use
     * @return       The new blur maskfilter
     */
    public BlurMaskFilter(float radius, Blur style) {
        native_instance = nativeConstructor(radius, style.native_int);
    }

    private static native long nativeConstructor(float radius, int style);
}

上面代码的注释已经很清晰了,BlurMaskFilter通过指定的模糊半径来模糊边界,并且通过BlurMaskFilter.Blur来控制模糊的范围。
BlurMaskFilter的构造函数的参数:
1> radius 表示是Blur Radius,即阴影的模糊半径
2> style 用来控制模糊的范围
NORMAL :同时模糊边框的内部和外部
SOLID:加粗边框内部,模糊边框外部
OUTER:边框内部不绘制(即透明),模糊边框外部
INNER:模糊边框内部,边框外部不做处理
下面就举个例子:

public class MaskFilterView extends View {
    private Paint paint = null;
    private Bitmap bitmap = null;

    public MaskFilterView(Context context) {
        super(context);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public MaskFilterView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaskFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        BlurMaskFilter blurMaskFilter = new BlurMaskFilter(30, BlurMaskFilter.Blur.NORMAL);
        paint.setMaskFilter(blurMaskFilter);
        paint.setColor(Color.BLUE);
        paint.setTextSize(210f);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText("ANDROID", 100, 300, paint);
        canvas.drawRect(200, 500, 500, 800, paint);
        canvas.translate(200, 1000);
        canvas.drawBitmap(bitmap, 0, 0, paint);
    }

运行截图如下:

《Paint 常用方法解析第二篇》

上面是NORMAL类型得到的结果,SOLID、OUTER、INNER的运行截图依次如下:

《Paint 常用方法解析第二篇》 SOLID

《Paint 常用方法解析第二篇》 OUTER

《Paint 常用方法解析第二篇》 INNER

应用 图片的阴影效果

上面说过
MaskFilter是用来在绘制之前变化透明度通道值的基类,因此我们可以先获取到图片的alpha通道值并且对图片的alpha通道值进行模糊处理,然后依次绘制图片的alpha通道值和图片,代码如下:

public class MaskFilterView2 extends View {
    private Paint paint = null;
    private Bitmap alphaBitmap = null;
    private Bitmap bitmap = null;

    public MaskFilterView2(Context context) {
        super(context);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public MaskFilterView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaskFilterView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        BlurMaskFilter blurMaskFilter = new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER);
        paint.setMaskFilter(blurMaskFilter);
        paint.setColor(Color.DKGRAY);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_shader);
        alphaBitmap = bitmap.extractAlpha();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(200, 200);
        canvas.drawBitmap(alphaBitmap, 0, 0, paint);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }

运行截图如下:

《Paint 常用方法解析第二篇》

3.2 EmbossMaskFilter(浮雕遮罩过滤器)

先来看看源码:

public class EmbossMaskFilter extends MaskFilter {
    /**
     * Create an emboss maskfilter
     *
     * @param direction  array of 3 scalars [x, y, z] specifying the direction of the light source
     * @param ambient    0...1 amount of ambient light
     * @param specular   coefficient for specular highlights (e.g. 8)
     * @param blurRadius amount to blur before applying lighting (e.g. 3)
     * @return           the emboss maskfilter
     */
    public EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) {
        if (direction.length < 3) {
            throw new ArrayIndexOutOfBoundsException();
        }
        native_instance = nativeConstructor(direction, ambient, specular, blurRadius);
    }

    private static native long nativeConstructor(float[] direction, float ambient, float specular, float blurRadius);
}

由上面的代码可知,EmbossMaskFilter通过指定光源的方向、环境光强度、镜面高亮系数和模糊半径实现来实现浮雕效果。
EmbossMaskFilter是通过构造方法指定上面所说的4个属性,构造方法参数如下:
direction:光源的方向,取值为长度为3的数组[x,y,z]
ambient:环境光的强度,取值范围0…1
specular: 镜面高亮系数
blurRadius:模糊半径
下面举个例子:

public class EmbossFilterView extends View {
    private Paint paint = null;
    private Bitmap bitmap = null;

    public EmbossFilterView(Context context) {
        super(context);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public EmbossFilterView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public EmbossFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        float[] direction = new float[]{10, 10, 10};
        float ambient = 0.5f;
        float specular = 5;
        float blurRadius = 5;
        EmbossMaskFilter embossMaskFilter = new EmbossMaskFilter(direction, ambient, specular, blurRadius);
        paint.setMaskFilter(embossMaskFilter);
        paint.setColor(Color.BLUE);
        paint.setTextSize(210f);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText("ANDROID", 100, 300, paint);
        canvas.drawRect(200, 500, 500, 800, paint);
        canvas.translate(200, 1000);
        canvas.drawBitmap(bitmap, 0, 0, paint);
    }
}

运行截图如下:

《Paint 常用方法解析第二篇》

4 setPathEffect

参考Drawable绘制过程源码分析和自定义Drawable实现动画中的2.1

5 setShader

参考Drawable绘制过程源码分析和自定义Drawable实现动画中的2.2

6 setXfermode

用来给Paint设置Xfermode模式,该方法接受一个Xfermode类型的参数,Xfermode的源码如下:

/**
 * Xfermode is the base class for objects that are called to implement custom
 * "transfer-modes" in the drawing pipeline. The static function Create(Modes)
 * can be called to return an instance of any of the predefined subclasses as
 * specified in the Modes enum. When an Xfermode is assigned to an Paint, then
 * objects drawn with that paint have the xfermode applied.
 */
public class Xfermode {

    protected void finalize() throws Throwable {
        try {
            finalizer(native_instance);
            native_instance = 0;
        } finally {
            super.finalize();
        }
    }

    private static native void finalizer(long native_instance);

    long native_instance;
}

可以看到Xfermode并没有具体的实现,因此Xfermode一定有子类去实现一些方法供我们使用:

《Paint 常用方法解析第二篇》

可以看到Xfermode一共有3个子类,对于AvoidXfermode和PixelXorXfermode这两个类,在API16时被作废,在API24的时候被移除(将targetSdkVersion和compileSdkVersion设置为24或者更高时,AvoidXfermode和PixelXorXfermode是找不到的),因此下面就不会再研究这两个类。

3.1 PorterDuffXfermode

public class PorterDuffXfermode extends Xfermode {
    /**
     * @hide
     */
    public final PorterDuff.Mode mode;

    /**
     * Create an xfermode that uses the specified porter-duff mode.
     *
     * @param mode           The porter-duff mode that is applied
     */
    public PorterDuffXfermode(PorterDuff.Mode mode) {
        this.mode = mode;
        native_instance = nativeCreateXfermode(mode.nativeInt);
    }
    
    private static native long nativeCreateXfermode(int mode);
}

可以看到PorterDuffXfermode只是提供了一个接受PorterDuff.Mode类型参数的构造方法供我们使用,PorterDuff是由Thomas Porter和Tom Duff两个人的名字组成,其概念最早来自于他们在1984年题为“合成数字图像”的开创性文章中,因为Thomas Porter和Tom Duff的工作只关注 source和destination的alpha通道的影响,所以在原始论文中描述的12种运算在这里被称为alpha合成模式,PorterDuff类还提供了几种混合模式,它们类似地定义了合成source和destination但不限于alpha通道的结果, 这些混合模式不是由Thomas Porter和Tom Duff定义的,但为了方便起见,这些模式已被包括在PorterDuff类中。

下面展示的所有示例图都使用相同的Source image和Destination image:

《Paint 常用方法解析第二篇》

下面会依次列举PorterDuff类枚举的18种合成模式的图像表示和计算公式,计算公式中的Sa表示Source image的alpha通道值,Sc表示Source image的颜色值,Da代表Destination image的alpha通道值,Dc代表Destination image的颜色值,Ra代表Result image的alpha通道值,Rc表示Result image的颜色值。
12种alpha合成模式
1> SRC

《Paint 常用方法解析第二篇》

Destination像素被丢弃,使Source完好无损。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Sa\& Rc=Sc\end{align})

2> SRC_OVER

《Paint 常用方法解析第二篇》

Source像素被绘制在目标像素之上。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Sa+(1-Sa)Da\& Rc=Sc+(1-Sa)
Dc\end{align})

3> SRC_IN

《Paint 常用方法解析第二篇》

保留覆盖Destination像素的Source像素,丢弃剩余的Source和Destination像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=SaDa\& Rc=Sc
Da\end{align})

4> SRC_ATOP

《Paint 常用方法解析第二篇》

丢弃不和Destination像素重叠的Source像素。 在Destination像素上绘制剩余的Source像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Da\& Rc=DaSc+(1-Sa)
Dc\end{align})

5> DST

《Paint 常用方法解析第二篇》

Source像素被丢弃,使Destination完好无损。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Da\& Rc=Dc\end{align})

6> DST_OVER

《Paint 常用方法解析第二篇》

Source像素被绘制在Destination像素之后。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Da+(1-Da)Sa\& Rc=Dc+ (1-Da)
Sc\end{align})

7> DST_IN

《Paint 常用方法解析第二篇》

保留覆盖Source像素的Destination像素,丢弃剩余的Source和Destination像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=SaDa\& Rc=Dc
Sa\end{align})

8> DST_ATOP

《Paint 常用方法解析第二篇》

丢弃未被Source像素覆盖的Destination像素。 在Source像素上绘制剩余的Destination像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Sa\& Rc=SaDc+(1-Da)
Sc\end{align})

9> CLEAR

《Paint 常用方法解析第二篇》

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=0\& Rc=0\end{align})

10> SRC_OUT

《Paint 常用方法解析第二篇》

保留Destination像素未覆盖的Source像素。 舍弃Destination像素覆盖的Source像素。 丢弃所有Destination像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=(1-Da)Sa\& Rc=(1-Da)
Sc\end{align})

11> DST_OUT

《Paint 常用方法解析第二篇》

保持Source像素未覆盖的Destination像素。 舍弃Source像素覆盖的Destination像素。 丢弃所有Source像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=(1-Sa)Da\& Rc=(1-Sa)
Dc\end{align})

12> XOR

《Paint 常用方法解析第二篇》

丢弃重叠的Source和Destination像素。 绘制剩余的Source和Destination像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=(1-Da)Sa+(1-Sa)
Da\& Rc=(1-Da)Sc+(1-Sa)
Dc\end{align})

除了上面的12种alpha合成模式,PorterDuff类还提供了如下6种合成模式:
13> DARKEN

《Paint 常用方法解析第二篇》

保留Source和Destination像素的最小组合。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Sa+Da-SaDa\& Rc=(1-Da)
Sc+(1-Sa)Dc + min(Sc, Dc)\end{align*})

14> LIGHTEN

《Paint 常用方法解析第二篇》

保留Source和Destination像素的最大组合。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Sa+Da-SaDa\& Rc=(1-Da)
Sc+(1-Sa)Dc + max(Sc, Dc)\end{align*})

15> MULTIPLY

《Paint 常用方法解析第二篇》

将Source和Destination像素相乘。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=SaDa\& Rc=Sc
Dc\end{align})

16> SCREEN

《Paint 常用方法解析第二篇》

添加Source和Destination像素,然后减去与Destination相乘的Source像素。

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Sa+Da – SaDa\& Rc=Sc+Dc-Sc
Dc\end{align})

17> OVERLAY

《Paint 常用方法解析第二篇》

Multiplies or screens the source and destination depending on the destination color.

![](
http://latex.codecogs.com/svg.latex?\begin{align
}&Ra=Sa+Da-SaDa\& Rc=\left{\begin{array}{lcl}2
ScDc&&{2
Dc<Da}\SaDa-2
(Da-Sc)(Sa-Dc)&&{otherwise}\end{array}\right.\end{align*})

18> ADD

《Paint 常用方法解析第二篇》

将Source pixels添加到Destination pixels,并使结果饱和。

《Paint 常用方法解析第二篇》

3.2 PorterDuffXfermode应用举例

对于开发者来说,我们只需要知道这些模式提供什么样的效果,而对于这些模式对应的计算公式就不需要关心了,感兴趣的同学可以参考Alpha compositing,讲了这么多,PorterDuffXfermode到底有什么用呢:

1> 自定义实现不规则形状的View

《Paint 常用方法解析第二篇》

具体实现参考
自定义实现不规则形状的View

2>实现橡皮擦的效果
对于这个效果应该用的比较广泛了,最常见的就是刮奖区的效果,首先给大家一个效果图:

《Paint 常用方法解析第二篇》

运行效果还是很满意的,为什么是个
字呢,这是个秘密,嘿嘿,下面就是实现的源码:

布局代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <com.cytmxk.customview.eraser.EraserView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:eraser_view_bg="@drawable/fourth_pic"
        app:eraser_view_fg="@android:color/darker_gray"/>

</LinearLayout>

//自定义橡皮擦View的代码
public class EraserView extends View {
    private int touchSlop = 8; // 滑动距离阀值:如果在屏幕上滑动距离小于此值则不会绘制
    private int fgColor = 0xFFAAAAAA; // 前景颜色
    private Bitmap fgBitmap = null; // 前景图片
    private Drawable bgDrawable = null; // 背景图片
    private float lastX; // 记录上一个触摸事件的位置坐标
    private float lastY;
    private Path path = null; // 橡皮擦的摩擦路径
    private Paint paint = null; // 模拟橡皮擦的画笔
    private Canvas pathCanvas = null; // 用于绘制橡皮擦路径的canva


    public EraserView(Context context, int bgResId, int fgColorId) {
        super(context);
        if (bgResId < 0) {
            throw new IllegalArgumentException("EraserView args error!");
        }
        Resources resources = context.getResources();
        bgDrawable = resources.getDrawable(bgResId);
        fgColor = resources.getColor(fgColorId);
        if (null == bgDrawable) {
            throw new IllegalArgumentException("EraserView args error!");
        }
        init();
    }

    public EraserView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public EraserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.EraserView);
        bgDrawable = ta.getDrawable(R.styleable.EraserView_eraser_view_bg);
        fgColor = ta.getColor(R.styleable.EraserView_eraser_view_fg, 0xFFAAAAAA);
        ta.recycle();

        if (null == bgDrawable) {
            throw new IllegalArgumentException("EraserView args error!");
        }
        bgDrawable.setCallback(this);
        init();
    }

    private void init() {

        ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
        touchSlop = viewConfiguration.getScaledTouchSlop();

        path = new Path();
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.TRANSPARENT);
        paint.setStrokeWidth(50);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setColor(Color.TRANSPARENT);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (null != bgDrawable) {
            bgDrawable.setBounds(0, 0, getWidth(), getHeight());
        }
        createFgBitmap();
    }

    private void createFgBitmap() {
        fgBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        pathCanvas = new Canvas(fgBitmap);
        pathCanvas.drawColor(fgColor);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                lastX = x;
                lastY = y;
                path.reset();
                path.moveTo(x, y);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                float offsetX = x - lastX;
                float offsetY = y - lastY;
                if (offsetX >= touchSlop || offsetY >= touchSlop) {
                    path.quadTo(lastX, lastY, x, y);
                    lastX = x;
                    lastY = y;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
        }
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        bgDrawable.draw(canvas);
        canvas.drawBitmap(fgBitmap, 0, 0, null);
        pathCanvas.drawPath(path, paint);
    }
}

上面的代码很简单,我就不在解释了,写不动了,我要睡了。

    原文作者:ForeverCy
    原文地址: https://www.jianshu.com/p/b095182097b3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞