自定义控件之绘图篇(八) —— Paint之ColorMatrix与滤镜效果

前言

这篇主要讲解ColorMatrix的相关知识,这里将涉及到矩阵乘法的相关知识。所以这篇是比较有难度的。

矩阵概述

定义

这里写图片描述
这是m*n的矩阵

矩阵乘法

这里写图片描述
矩阵乘法其实并不难,它的意思就是将第一个矩阵A的第一行,与第二个矩阵B的第一列的数字分别相乘,得到的结果相加,最终的值做为结果矩阵的第(1, 1)位置的值(即第一行第一列)。

同样,A矩阵的第一行与B矩阵的第二列的数字分别相乘然后相加,结果作为结果矩阵第(1, 2)位置的值(即第一行第二列)。

再如,A矩阵的第二行与B矩阵的第一列的数字分别相乘,然后相加,结果做为结果矩阵的第(2, 1)位置的值(即第二行第一列)。
算法其实并不难,这里要说明一个问题:

  • A矩阵的列数必须与B矩阵的行数相同,才能相乘!因为我们需要把A中的一行中的各个数字与B矩阵中的一列中的各个数字分别相乘,所以A的列数与B的行数必须相同才行!

  • 矩阵A乘以矩阵B和矩阵B乘以矩阵A的结果必然是不一样的。

这里写图片描述

色彩矩阵

对于色彩的存储,Bitmap类使用一个32位的数值来保存。红、绿、蓝及透明度各占8位,每一个色彩分量的取值范围是0-255。透明度为0表示完全透明,为255时,色彩完全可见。下面介绍色彩信息的矩阵表示。

四阶表示

由于一个色彩信息包含R、G、B、Alpha信息,所以,我们必然要使用一个4阶色彩变换矩阵来修改色彩的每一个分量值
这里写图片描述

对于色彩变换矩阵,这里的色彩顺序是R、G、B、A而不是A、R、G、B!!!

如果想将色彩(0, 255, 0, 255)更改为半透明,可以使用下面的的矩阵运算来表示

这里写图片描述

为什么使用五阶矩阵

上面使用四阶矩阵完全可以改变图片的RGBA值了,但考虑一种情况:如果我们只想在原有的R色上增加一些分量呢

这时,我们就得再多加一阶来表示平移变换。所以,一个既包含线性变换,又包含平移变换的组合变换,称为仿射变换。使用四阶的色彩变换矩阵来修改色彩,只能够对色彩的每一个分量值进行乘(除)运算,如果要对这些分量值进行加减法的运算(平移变换),只能通过五阶矩阵来完成。

考虑下面这个变换

  1. 红色分量值更改为原来的2倍

  2. 绿色分量增加100

则使用4阶矩阵的乘法无法实现,所以,应该在四阶色彩变换矩阵上增加一个“哑元坐标”,来实现所列的矩阵运算

这里写图片描述

这个矩阵中,分量值用的是100。

Android中的色彩矩阵

概述

在上面的所有讲解之后,大家也应该看出来了,色彩变换矩阵的表示形式,肯定是五阶的那种,所以大家看一下,在默认情况下,色彩变换矩阵的形式

这里写图片描述

Android中的色彩矩阵是用ColorMatrics类来表示的,使用ColorMatrix的方法如下

// 生成色彩矩阵    
ColorMatrix colorMatrix = new ColorMatrix(new float[]{    
        1, 0, 0, 0, 0,    
        0, 1, 0, 0, 0,    
        0, 0, 1, 0, 0,    
        0, 0, 0, 0.5, 0,    
});    
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

有关setColorFilter()函数的其它用法,下篇文章我们将会详细讲述,这篇我们只知道怎么设置ColorMatrix对象就可以了。

示例1(单个颜色的蓝色通道输出)

下面我们举个例子来简单看一下,我们对一个颜色值进行ColorMatrix操作会怎样

public class MyView extends View {  
    private Paint mPaint = new Paint();  
    private Bitmap bitmap;// 位图  

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

    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        mPaint.setAntiAlias(true);  
        mPaint.setARGB(255,200,100,100);  
        // 绘制原始位图  
        canvas.drawRect(0,0,500,600,mPaint);  

        canvas.translate(550,0);  
        // 生成色彩矩阵  
        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,  
        });  
        mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));  
        canvas.drawRect(0,0,500,600,mPaint);  
    }  
}

在上面中,我们先将图笔颜色值设为(255, 200, 100, 100),然后对其进行ColorMatrix颜色值运算,把红色和绿色都去掉,仅显示蓝色值;只显示蓝色值的效果在PhotoShop中叫做蓝色通道。效果图如下(左侧是原图,右侧是该图对应的蓝色通道)

这里写图片描述

这里只是对一个颜色值,而ColorMatrics的最厉害的地方在于,能够很批量地改变图像中的所有颜色值。下面我们就对图像应用ColorMatrics的例子来看看,如果只显示图像中的蓝色通道会怎样。

示例2(图片多颜色的蓝色通道输出)

下面我们就举个给Bitmap应用ColorMatrix的例子

public class MyView extends View {  
    private Paint mPaint = new Paint();  
    private Bitmap bitmap;// 位图  

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

        mPaint.setAntiAlias(true);  
        // 获取位图  
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.dog);  
    }  

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

        // 绘制原始位图  
        canvas.drawBitmap(bitmap, null, new Rect(0, 0, 500, 500 * bitmap.getHeight() / bitmap.getWidth()), mPaint);  

        canvas.translate(510, 0);  
        // 生成色彩矩阵  
        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,  
        });  
        mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));  
        canvas.drawBitmap(bitmap, null, new Rect(0, 0, 500, 500 * bitmap.getHeight() / bitmap.getWidth()), mPaint);  
    }  
}

这里分两次绘制了一个Bitmap,第一次绘制了一个原始图像,然后利用ColorMatrix生成了一个仅包含蓝色的图像,用过PhotoShop的同学应该很清楚这个跟PhotoShop中的蓝色通道的效果是一致的,效果图如下

这里写图片描述

大家注意哦,不要在onDraw()new Paint()对象,上节中我为了省事就直接在onDraw()函数中直接new Paint()对象了,由于onDraw()函数在刷新时会连续调用多次,所以如果在其中不断的new对象,会造成程序不断的GC(内存回收),是会严重影响性能的!在程序中,我有时会为了方便理解,就直接在onDraw()中创建对象了,大家在实际应用中一定要杜绝这种应用哦。

色彩的几种运算方式

在简单理解了ColorMatrics的使用方式后,我们来详细看看色彩的几种运算方式。

色彩的平移运算

色彩的平移运算,实际上就是色彩的加法运算,其实就是在色彩变换矩阵的最后一行加上某个值,这样可以增加特定色彩的饱和度。

这里写图片描述

比如,同样是上面的图片,我们给它应用下面的色彩值

ColorMatrix colorMatrix = new ColorMatrix(new float[]{  
        1, 0, 0, 0, 0, 
        0, 1, 0, 0, 50,  
        0, 0, 1, 0, 0,  
        0, 0, 0, 1, 0,  
});

在绿色值上添加增量50,即增大绿色的饱和度,效果图如下

这里写图片描述

同样,左侧是原图,右侧是增大绿色饱和度后的效果。大家要特别注意的是,由于图片是由一个个像素组成的,所以用每个像素所对应的色彩数组来乘以转换矩阵,结果就是转换后的当前点的颜色值。所以,在应用ColorMatrics后,图片中每个像素的绿色值都增加了50,从小狗脸上也可以看出来,狗脸也变绿了(它可能看到他女朋友跟人家跑了,哈哈)!

色彩平移除了增加指定颜色饱和度以外,另一个应用就是色彩反转(PhotoShop中的反相功能) 。

色彩反转/反相功能

色彩反转就是求出每个色彩的补值来做为目标图像的对应颜色值

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

换成矩阵图示如下

这里写图片描述

效果图如下

这里写图片描述

色彩的缩放运算

色彩的缩放运算其实就是色彩的乘法运算。在色彩矩阵对角线上的分别代表R、G、B、A的几个值,将其分别乘以指定的值。这就是所谓的缩放变换。

这里写图片描述

我们可以针对某一个颜色值进行放大缩小运算,但当对R、G、B、A同时进行放大缩小时,就是对亮度进行调节,看下面的将亮度增大1.2倍的代码

ColorMatrix colorMatrix = new ColorMatrix(new float[]{  
        1.2f, 0, 0, 0, 0,  
        0, 1.2f, 0, 0, 50,  
        0, 0, 1.2f, 0, 0,  
        0, 0, 0, 1.2f, 0,  
});

效果图如下

这里写图片描述

缩放变换的特殊应用(通道输出)

由于在色彩变换矩阵中,对角线上数的取值范围是从0-1的,所以当取0时,这个色彩就完全不显示,所以当我们R、G都取0,而独有B取1时,就只显示了蓝色,所形成的图像也就是我们通常说的蓝色通道,看下几个通道输出的效果图

这里写图片描述

红色通道矩阵

ColorMatrix colorMatrix = new ColorMatrix(new float[]{  
       1, 0, 0, 0, 0,  
       0, 0, 0, 0, 0,  
       0, 0, 0, 0, 0,  
       0, 0, 0, 1, 0,  
});  

绿色通道矩阵

ColorMatrix colorMatrix2 = new ColorMatrix(new float[]{  
        0, 0, 0, 0, 0,  
        0, 1, 0, 0, 0,  
        0, 0, 0, 0, 0,  
        0, 0, 0, 1, 0,  
});  

蓝色通道矩阵

ColorMatrix colorMatrix3 = new ColorMatrix(new float[]{  
        0, 0, 0, 0, 0,  
        0, 0, 0, 0, 0,  
        0, 0, 1, 0, 0,  
        0, 0, 0, 1, 0,  
});  

色彩的旋转运算

RGB色是如何旋转的呢,首先用R、G、B三色建立立体坐标系

这里写图片描述

所以,我们可以把一个色彩值看成三维空间里的一个点,色彩值的三个分量可以看成该点的坐标(三维坐标)。我们先不考虑,在三个维度综合情况下是怎么旋转的,我们先看看,在某个轴做为Z轴,在另两个轴形成的平面上旋转的情况,下图分析了,在将蓝色轴做为Z轴,仅在红—绿平面上旋转a度的情况

这里写图片描述

在图中,我们可以看到,在旋转后,原R在R轴的分量变为原R*cosa,但原G分量在旋转后,在R轴上也有了分量,但分量落在了负轴上,所以我们要减去这部分分量,所以最终的结果是最终的R=原R*cosa-原G*sina。

下面就看下关于几种旋转计算及结果矩阵,注意:这几个图只标记了原X轴色彩分量的旋转,没有把Y轴色彩分量的旋转标记出来。

绕蓝色轴旋转a度

这里写图片描述

对应的色彩变换矩阵是

这里写图片描述

分解如下

这里写图片描述

绕红色轴旋转a度

这里写图片描述

对应的色彩变换矩阵是

这里写图片描述

绕绿色轴旋转a度

这里写图片描述

对应的色彩变换矩阵是

这里写图片描述

当围绕红色轴进行色彩旋转时,由于当前红色轴的色彩是不变的,而仅利用三角函数来动态的变更绿色和蓝色的颜色值,这种改变就叫做色相调节!当围绕红色轴旋转时,是对图片就行红色色相的调节;同理,当围绕蓝色颜色轴旋转时,就是对图片就行蓝色色相调节;当然,当围绕绿色轴旋转时,就是对图片进行绿色色相的调节。

下面我们做一个动态的调节,针对红 色色相。

这里写图片描述

这个效果图表示的是,在滚轮正中间位置时表示旋转角度为0度,最右侧位置表示向正方向旋转180,左侧到底表示负方向旋转180。

同理可以得到围绕绿色轴旋转的效果图

这里写图片描述

最后是,围绕蓝色轴旋转的效果图

这里写图片描述

下面我们会再次讲到ColorMatrics的色彩旋转函数,这里先理解原理和效果,代码后面会给出。

色彩的投射运算

我们再回过头来看看色彩矩阵运算的公式:

其中我把红色运算给单独拉了出来,红色标记的那几个元素a12,a13,a14,在运算中,是利用G、B、A的颜色值的分量来增加红色值的。
来看具体的运算:

注意:最终结果的220=0.2*100+1*200,可见绿色分量在原有绿色分量的基础上,增加了红色分量值的0.2倍;利用其它色彩分量的倍数来更改自己色彩分量的值,这种运算就叫投射运算。
下图阴影部分;对这些值进行修改时,修改所使用的增加值来自于其它色彩分量的信息。

色彩投射的一个最简单应用就是变为黑白图片:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
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,
});
效果图:

首先了解一下去色原理:只要把RGB三通道的色彩信息设置成一样;即:R=G=B,那么图像就变成了灰色,并且,为了保证图像亮度不变,同一个通道中的R+G+B=1:如:0.213+0.715+0.072=1;
三个数字的由来:0.213, 0.715, 0.072;
按理说应该把RGB平分,都是0.3333333。三个数字应该是根据色彩光波频率及色彩心理学计算出来的(本人是这么认为,当然也查询了一些资料,目前尚未找到准确答案)。
在作用于人眼的光线中,彩色光要明显强于无色光。对一个图像按RGB平分理论给图像去色的话,人眼就会明显感觉到图像变暗了(当然可能有心理上的原因,也有光波的科学依据)另外,在彩色图像中能识别的一下细节也可能会丢失。
所以google最终给我们的颜色值就是上面的比例:0.213, 0.715, 0.072;
所以,在给图像去色时我们保留了大量的G通道信息,使得图像不至于变暗或者绿色信息不至于丢失(我猜想)。
投射运算的另一个应用是:色彩反色
当我们利用色彩矩阵将两个颜色反转,这种操作就叫做色彩反色
比如,下面的的将红色和绿色反色(红绿反色)

[java] view plain copy 在CODE上查看代码片派生到我的代码片
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
0,1,0,0,0,
1,0,0,0,0,
0,0,1,0,0,
0,0,0,1,0
});
效果图如下:

左侧的图为原图,右边为红绿反色以后的效果图;
从矩阵中可以看出红绿反色的关键在于,第一行用绿色来代替了红色,第二行用红色代替了绿色。
类似可以有红蓝反色,绿蓝反色等,对应矩阵难度不大,就不再细讲了。
变旧照片
投射运算的另一个应用是照片变旧,对应矩阵如下:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
1/2f,1/2f,1/2f,0,0,
1/3f,1/3f,1/3f,0,0,
1/4f,1/4f,1/4f,0,0,
0,0,0,1,0
});

五、ColorMatrix函数
上面讲了利用色彩矩阵的来做一些运算,但这些都是需要特定的色彩设计基础的,Android中ColorMatrix自带了一些函数来帮我们完成一些调整饱和度、色彩旋转等操作的函数,我们就一一来看看
1、构造函数
ColorMatrix共有三个构造函数:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
ColorMatrix()
ColorMatrix(float[] src)
ColorMatrix(ColorMatrix src)
这三个构造函数中,上面我们已经使用过第二个构造函数了,第三个构造函数,就是利用另一个ColorMatrix实例来复制一个一样的ColorMatrix对象。
2、设置、重置函数
第一个构造函数ColorMatrix(),需要与其它函数共用才行:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public void set(ColorMatrix src)
public void set(float[] src)
public void reset()
这里是设置和重置函数,重置后,对应的数组为:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
/**
* Set this colormatrix to identity:
* [ 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
*/
这些函数难度都不大,就不再讲了
3、setSaturation——设置饱和度
上面我们讲过,我们可以通过色彩的平移运算单独增强R,G,B其中一个的饱和度,但当我们需要整体增强图像的饱和度时需要如何来做呢?ColorMatrics给我们提供了一个方法来整体增强图像的饱和度,函数如下:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
//整体增强颜色饱和度,即同时增强R,G,B的色彩饱和度
public void setSaturation(float sat)
其中:
参数float sat:表示把当前色彩饱和度放大的倍数。取值为0表示完全无色彩,即灰度图像(黑白图像);取值为1时,表示色彩不变动;当取值大于1时,显示色彩过度饱和
我们来看个例子:

滑块默认在一倍的位置,向左到底是0,向右到底是20(即饱和度放大20倍)
下面来看看代码:
先来看看布局代码:(main.xml)

[html] view plain copy 在CODE上查看代码片派生到我的代码片

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值