Android自定义ViewAndroid知识Android开发经验谈

Paint 常用方法解析第一篇

2017-07-13  本文已影响229人  小芸论

1 概述

Android中所有View的绘制工作归根结底都是通过Paint完成的,所以下面就来看看这个东东是怎么用的,在现实世界中,有很多种类的画笔,比如水彩笔、铅笔、毛笔、圆珠笔等等,而Android中的画笔就是Paint,现实世界的画笔拥有的属性都可以通过Paint的set方法设置并且Paint还拥有一些现实世界画笔没有的属性。接下来我们就来看一下Paint中的set方法:



接下来我会依次分析下面列举的几个set方法:
1> public ColorFilter setColorFilter(ColorFilter filter)
2> public MaskFilter setMaskFilter(MaskFilter maskfilter)
3> public PathEffect setPathEffect(PathEffect effect)
4> public Shader setShader(Shader shader)
5> public Xfermode setXfermode(Xfermode xfermode)
其它的set方法有兴趣的同学可以自己去研究一下。

2 setColorFilter

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

/**
 * A color filter can be used with a {@link Paint} to modify the color of
 * each pixel drawn with that paint. This is an abstract class that should
 * never be used directly.
 */
public class ColorFilter {
    /**
     * Holds the pointer to the native SkColorFilter instance.
     *
     * @hide
     */
    public long native_instance;

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

    static native void destroyFilter(long native_instance);
}

从注释中可以得到如下两个信息:
1> ColorFilter是用来修改Paint绘制时每一个像素Color值的。
2> ColorFilter不应该被直接使用,即应该使用ColorFilter的子类。
那就来看看Google为我们准备好的ColorFilter子类:



从上图中可知,Google一共为我们准备了3个ColorFilter子类,由上面注释得到的第一条信息可知这3个ColorFilter子类就是用来提供修改Paint绘制时的每一个像素的规则。

2.1 ColorMatrixColorFilter(色彩矩阵颜色过滤器)

上面说过ColorFilter就是用来提供修改Paint绘制时每一个像素Color值的规则,那么对于ColorMatrixColorFilter来说,ColorMatrixColorFilter提供了通过ColorMatrix来修改Paint绘制时的每一个像素Color值的规则,该规则可用于改变像素Color值的饱和度,从YUV转换为RGB等。
接下来我们就来看看ColorMatrix改变像素Color值的规则:

/**
 * 4x5 matrix for transforming the color and alpha components of a Bitmap.
 * The matrix can be passed as single array, and is treated as follows:
 *
 * <pre>
 *  [ a, b, c, d, e,
 *    f, g, h, i, j,
 *    k, l, m, n, o,
 *    p, q, r, s, t ]</pre>
 *
 * <p>
 * When applied to a color <code>[R, G, B, A]</code>, the resulting color
 * is computed as:
 * </p>
 *
 * <pre>
 *   R’ = a*R + b*G + c*B + d*A + e;
 *   G’ = f*R + g*G + h*B + i*A + j;
 *   B’ = k*R + l*G + m*B + n*A + o;
 *   A’ = p*R + q*G + r*B + s*A + t;</pre>
 *
 * <p>
 * That resulting color <code>[R’, G’, B’, A’]</code>
 * then has each channel clamped to the <code>0</code> to <code>255</code>
 * range.
 * </p>
 *
 * <p>
 * The sample ColorMatrix below inverts incoming colors by scaling each
 * channel by <code>-1</code>, and then shifting the result up by
 * <code>255</code> to remain in the standard color space.
 * </p>
 *
 * <pre>
 *   [ -1, 0, 0, 0, 255,
 *     0, -1, 0, 0, 255,
 *     0, 0, -1, 0, 255,
 *     0, 0, 0, 1, 0 ]</pre>
 */

上面是Google给出的解释,相信大家可以看明白,我就不再赘叙了。
通过ColorMatrix可以对像素Color值进行如下运算:
1> 像素Color值的平移运算
2> 像素Color值的缩放运算
3> 像素Color值的旋转运算
4> 像素Color值的投射运算

2.1.1 像素Color值的平移运算

像素Color值的平移运算其实就是在ColorMatrix的最后一列设置某个值;这样可以增加或者减少指定通道(像素Color值包含RGBA四个通道)的饱和度。
举个例子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_marginTop="50dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:src="@drawable/second_pic"/>

    <ImageView
        android:id="@+id/imageview_test_color_fliter"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_marginTop="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:src="@drawable/second_pic"/>

</LinearLayout>

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        1, 0, 0, 0, 50,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

为了简单起见,上面就直接调用了ImageView的setColorFilter方法,该方法最终会将colorMatrixColorFilter设置给绘制图片的Paint,那么图片中的每个像素在绘制之前都会被colorMatrix处理。上面的代码在R通道上添加50,即增大R通道的饱和度,那么图片就会偏红。运行截图如下(第一张图片为原图,下同):


像素Color值反转
利用ColorMatrix求出像素Color值每个通道值的补值作为将要被绘制像素Color值对应的通道值。

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        -1, 0, 0, 0, 255,
        0, -1, 0, 0, 255,
        0, 0, -1, 0, 255,
        0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

运行截图如下(第一张图片为原图,下同):


2.1.2 像素Color值的缩放运算

像素Color值缩放运算就是对RGBA四个通道值进行缩放,并且当对R、G、B三个通道址同时进行放大缩小时,就是对亮度进行调节。 举例如下:

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        2, 0, 0, 0, 0,
        0, 2, 0, 0, 0,
        0, 0, 2, 0, 0,
        0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

在绘制图片之前,上面的代码中的colorMatrix将图片中所有像素Color值RGB三个通道值都增大了一倍,从而使图片的亮度增大两倍。运行截图如下:


上面是调高亮度,下面就来降低亮度:

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0.7f, 0, 0, 0, 0,
        0, 0.7f, 0, 0, 0,
        0, 0, 0.7f, 0, 0,
        0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

在绘制图片之前,上面的代码中的colorMatrix将图片中所有像素Color值RGB三个通道值都降低了30%,从而使图片的亮度降低了30%。运行截图如下:



大家有没有遇到在图片背景上显示文本的需求,这时为了让文本不被背景图片干扰,就需要降低图片背景的亮度,很多人的做法就是在背景图片上覆盖一层遮罩,这种做法既多了一层遮罩布局又占用内存,所以通过上面colorMatrix降低图片亮度才是解决这个问题更好的方案。

像素Color值的缩放运算的应用 通道输出
利用ColorMatrix移除像素Color值RGB中的指定的通道值。

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

在绘制图片之前,上面的代码中的colorMatrix移除了图片中所有像素Color值RG两个通道值,从而变成只包含蓝色的图片。运行截图如下:


有些同学可能会说,这样搞还是有点麻烦,有没有好点的办法,因此ColorMatrix提供了setScale方法:

/**
 * Set this colormatrix to identity:
 * <pre>
 * [ 1 0 0 0 0   - red vector
 *   0 1 0 0 0   - green vector
 *   0 0 1 0 0   - blue vector
 *   0 0 0 1 0 ] - alpha vector
 * </pre>
 */
public void reset() {
    final float[] a = mArray;
    Arrays.fill(a, 0);
    a[0] = a[6] = a[12] = a[18] = 1;
}

/**
 * Android中像素是由4个通道(RGBA)组成,该方法就是用来偏移4个通道的值(0表示该通道的值降到0,
 * 即抹去像素中该通道的值,大于0小于1表示降低像素中该通道的值,1表示不变,大于1表示增大像素中该通道的值)。
 */
public void setScale(float rScale, float gScale, float bScale,
                     float aScale) {
    final float[] a = mArray;

    for (int i = 19; i > 0; --i) {
        a[i] = 0;
    }
    a[0] = rScale;
    a[6] = gScale;
    a[12] = bScale;
    a[18] = aScale;
}

reset方法会在ColorMatrix的构造方法中被调用,因此ColorMatrix的初始值是:



上图中的矩阵下面统称为初始矩阵,接下来看看setScale方法的源码,setScale方法操作初始矩阵后得到的矩阵如下:



由上面的矩阵可知,该方法就是用来设置4个通道(RGBA)的取值比例,因此该方法可以用于像素Color值的缩放,下面就利用setScale方法实现上面三个例子相同的效果:
布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setScale(2, 2, 2, 1);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setScale(0.7f, 0.7f, 0.7f, 1);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setScale(0, 0, 1, 1);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

运行结果和上面例子相同,这里就不在赘叙了。

2.1.3 像素Color值的旋转运算

ColorMatrix提供了setRotate方法来实现像素Color值的旋转运算,源码如下:

/**
 * Set the rotation on a color axis by the specified values.
 * <p>
 * <code>axis=0</code> correspond to a rotation around the RED color
 * <code>axis=1</code> correspond to a rotation around the GREEN color
 * <code>axis=2</code> correspond to a rotation around the BLUE color
 * </p>
 */
public void setRotate(int axis, float degrees) {
    reset();
    double radians = degrees * Math.PI / 180d;
    float cosine = (float) Math.cos(radians);
    float sine = (float) Math.sin(radians);
    switch (axis) {
    // Rotation around the red color
    case 0:
        mArray[6] = mArray[12] = cosine;
        mArray[7] = sine;
        mArray[11] = -sine;
        break;
    // Rotation around the green color
    case 1:
        mArray[0] = mArray[12] = cosine;
        mArray[2] = -sine;
        mArray[10] = sine;
        break;
    // Rotation around the blue color
    case 2:
        mArray[0] = mArray[6] = cosine;
        mArray[1] = sine;
        mArray[5] = -sine;
        break;
    default:
        throw new RuntimeException();
    }
}

上面setRotate方法的源码看起来有点蒙,这是个什么东西,如果将RGB看做坐标系就很好理解了,如下所示:



该方法的第一个参数axis取值为0时表示绕着Red轴进行选择,取值为1时表示绕着Green轴旋转,取值为2时表示绕着Blue轴旋转,那我们就以绕着Blue轴旋转a举例:



那么在初始矩阵基础上做上面的操作后得到的矩阵如下:

同理绕着Red和Green轴旋转得到的矩阵如下:




当围绕Red轴进行旋转时,由于当前Red通道的值是不变的,而仅利用三角函数来动态的变更绿色和蓝色通道的值,这种改变就叫做色相调节,当围绕Red轴旋转时,是对像素进行红色色相的调节;同理,当围绕Blue轴旋转时,就是对像素进行蓝色色相调节。
举个例子:
布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setRotate(0, 180f);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

在绘制图片之前,colorMatrix会将图片中的所有像素都绕着Red轴旋转180度,这个时候Green和Blue轴是上的值就会变成负数,由于RGB通道的值取值范围是0到255,所以Green和Blue轴上的值会被设置为0,因此此时图片只有红色,运行截图如下:


2.1.4 像素Color值的投射运算

利用其它通道分量的倍数来更改该通道分量的值,这种运算就叫像素Color值的投射运算。

像素Color值的投射运算的应用
1> 像素Color值灰度化
若要将像素灰度化,就需要将RGB三通道的色彩信息设置成一样;即:R=G=B,并且,为了保证图像亮度不变,同一个通道中的R+G+B应该接近1。
在matlab中按照 0.2989 R,0.5870 G 和 0.1140 B 的比例构成像素灰度值。
在OpenCV中按照 0.299 R, 0.587 G 和 0.114 B 的比例构成像素灰度值。
在Android中按照0.213 R,0.715 G 和 0.072 B 的比例构成像素灰度值。
这些比例主要是根据人眼中三种不同的感光细胞的感光强度比例分配的,因此并没有一个确切值,不同工具调试出来的效果也不尽相同。

下面就用Android中的比例将图片灰度化:

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0.213f, 0.715f, 0.072f, 0, 0,
        0.213f, 0.715f, 0.072f, 0, 0,
        0.213f, 0.715f, 0.072f, 0, 0,
        0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

运行截图如下:


2> 像素Color值反色
通过ColorMatrix将像素Color值的两个通道值对调,这种操作就叫做像素Color值的反色。
下面我们就将GB两个通道值进行对调:

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        1, 0, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);

运行截图如下:


ColorMatrix还提供了修改像素Color值饱和度的方法setSaturation,上面我们讲过,通过像素Color值的平移运算可以增加或者减少指定通道(像素Color值包含RGBA四个通道)的饱和度,但当我们需要整体增强饱和度时就可以使用setSaturation方法,该方法源码如下:

/**
 * 用来改变像素饱和度(0表示灰度化,大于0小于1表示降低饱和度,1表示不变,大于1表示增大饱和度)。
 */
public void setSaturation(float sat) {
    reset();
    float[] m = mArray;

    final float invSat = 1 - sat;
    final float R = 0.213f * invSat;
    final float G = 0.715f * invSat;
    final float B = 0.072f * invSat;

    m[0] = R + sat; m[1] = G;       m[2] = B;
    m[5] = R;       m[6] = G + sat; m[7] = B;
    m[10] = R;      m[11] = G;      m[12] = B + sat;
}

根据setSaturation源码可知当参数为0时,初始矩阵就会变成:



上面的矩阵就是上面说的将像素Color值灰度化的ColorMatrix,运行结果与上面灰度化的例子的运行结果相同,就不在赘叙了。
下面看一下饱和度为0.5和2时的运行截图:



2.2 LightingColorFilter(光照颜色过滤器)

上面说过ColorFilter就是用来提供修改Paint绘制时每一个像素Color值的规则,那么对于LightingColorFilter来说,LightingColorFilter提供了如下规则:

/**
 * A color filter that can be used to simulate simple lighting effects.
 * A <code>LightingColorFilter</code> is defined by two parameters, one
 * used to multiply the source color (called <code>colorMultiply</code>)
 * and one used to add to the source color (called <code>colorAdd</code>).
 * The alpha channel is left untouched by this color filter.
 *
 * Given a source color RGB, the resulting R'G'B' color is computed thusly:
 * <pre>
 * R' = R * colorMultiply.R + colorAdd.R
 * G' = G * colorMultiply.G + colorAdd.G
 * B' = B * colorMultiply.B + colorAdd.B
 * </pre>
 * The result is pinned to the <code>[0..255]</code> range for each channel.
 */

//mul和add的格式是0xAARRGGBB
//当mul = 0xFFFFFFFF, add = 0x00000000时,像素Color值保持不变
public LightingColorFilter(int mul, int add) {
    mMul = mul;
    mAdd = add;
    update();
}

上面的注释已经很详细了,我就不再解释了。
LightingColorFilter的作用就是增加或者减少指定通道(像素Color值包含RGBA四个通道)的饱和度,下面将像素Color值的B通道值抹去(即将B通道的饱和度设置为0):

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
LightingColorFilter lightingColorFilter = new LightingColorFilter(0xFFFFFF00, 0x00000000);
testColorFilterIV.setColorFilter(lightingColorFilter);

运行截图如下:


2.3 PorterDuffColorFilter(混合颜色过滤器)

上面说过ColorFilter就是用来提供修改Paint绘制时每一个像素Color值的规则,那么对于PorterDuffColorFilter来说,PorterDuffColorFilter提供了如下规则:

/**
 * A color filter that can be used to tint the source pixels using a single
 * color and a specific {@link PorterDuff Porter-Duff composite mode}.
 */

/**
 * Create a color filter that uses the specified color and Porter-Duff mode.
 *
 * @param color The ARGB source color used with the specified Porter-Duff mode
 * @param mode The porter-duff mode that is applied
 *
 * @see Color
 * @see #setColor(int)
 * @see #setMode(android.graphics.PorterDuff.Mode)
 */
public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
    mColor = color;
    mMode = mode;
    update();
}

上面注释的就是通过PorterDuff.Mode将两个Color值进行混合处理,先来通过下图理解一下PorterDuff.Mode的作用:



那么对于PorterDuffColorFilter,src和dst对应的是什么呢?
src:构造方法的第一个参数
dst:将要被绘制的像素的Color值
下面在图片上面加个遮罩:

布局代码同上

testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(0x67000000, PorterDuff.Mode.DARKEN);
testColorFilterIV.setColorFilter(porterDuffColorFilter);

上面的代码在图片上添加了一个Color值为0x67000000的遮罩,运行截图如下:


上一篇下一篇

猜你喜欢

热点阅读