自定义控件绘制(Paint之ColorMatrix与滤镜)篇六

内容均来自

  1. https://blog.csdn.net/harvic880925/article/details/51187277

矩阵的概念

略,参考源博客

色彩矩阵

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

色彩的矩阵表示

四阶矩形

一个色彩信息包含R、G、B、Alpha信息,所以,必然要使用一个4阶色彩变换矩阵来修改色彩的每一个分量值(如果不想修改,对应的数值给0):

图片来自源博客

上图中,每个颜色占据一行,即占据一个维度,除了本分量值外,其他为0;
矩阵计算时,每行中的各个数字与另一矩阵中的一列中的各个数字分别相乘,然后相加为新值;

注意:对于色彩变换矩阵,这里的色彩顺序是R、G、B、A而不是A、R、G、B!
将色彩(0,255,0,255)更改为半透明时,可以使用下面的的矩阵运算来表示:

来自源博客(修改A所在行分量)

五阶矩阵

使用四阶矩阵完全可以改变图片的RGBA值了,但考虑一种情况,如果我们只想在原有的R色上增加一些分量呢?
这时,我们就得再多加一阶来表示平移变换
一个既包含线性变换(乘法),又包含平移变换(加法)的组合变换,称为仿射变换。
使用四阶的色彩变换矩阵来修改色彩,只能够对色彩的每一个分量值进行乘(除)运算,如果要对这些分量值进行加减法的运算(平移变换),只能通过五阶矩阵来完成。

比如:
1、红色分量值更改为原来的2倍(线性);
2、绿色分量增加100(平移);
则使用4阶矩阵的乘法无法实现,所以,应该在四阶色彩变换矩阵上增加一个“哑元坐标”,来实现所列的矩阵运算:

来自源博客

Android中的色彩矩阵

色彩变换矩阵的表示形式,是五阶的,在默认情况下,色彩变换矩阵的形式:


copy from 源博客

Android中的色彩矩阵是用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));    

示例代码1(单个颜色通道输出)

val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    setARGB(255, 200, 100, 100)
}

// 绘制原始位图
canvas.drawRect(0f, 0f, width / 2.toFloat(), 600f, paint)
canvas.translate(width / 2.toFloat(), 0f)

// 生成色彩矩阵(把红色和蓝色都去掉,仅显示绿色值)
val colorMatrix = ColorMatrix(floatArrayOf(
        0f, 0f, 0f, 0f, 0f,     // RED
        0f, 1f, 0f, 0f, 0f,     // GREEN
        0f, 0f, 0f, 0f, 0f,     // BLUE
        0f, 0f, 0f, 1f, 0f))    // ALPHA
paint.colorFilter = ColorMatrixColorFilter(colorMatrix)
canvas.drawRect(0f, 0f, width / 2.toFloat(), 600f, paint)
效果-过滤了其他颜色,保留绿色

示例代码2(图片多颜色单个绿色通道输出)

private var bitmap: Bitmap
private var paint: Paint

init {
    bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.chrome)
    paint = Paint(Paint.ANTI_ALIAS_FLAG)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 绘制原始位图
    canvas.drawBitmap(bitmap, null, Rect(0, 0, 250, 250 * bitmap.height / bitmap.width), paint)

    canvas.translate(0f, 260f)
    // 生成色彩矩阵(把红色和蓝色都去掉,仅显示绿色值)
    val colorMatrix = ColorMatrix(floatArrayOf(
            0f, 0f, 0f, 0f, 0f,     // RED
            0f, 1f, 0f, 0f, 0f,     // GREEN
            0f, 0f, 0f, 0f, 0f,     // BLUE
            0f, 0f, 0f, 1f, 0f))    // ALPHA
    paint.colorFilter = ColorMatrixColorFilter(colorMatrix)
    canvas.drawBitmap(bitmap, null, Rect(0, 0, 250, 250 * bitmap.height / bitmap.width), paint)
}
效果图

色彩的几种运算方式

色彩的平移运算

有了上面的铺垫,这个理解起来就简单了;
色彩的平移运算,实际上就是色彩的加法运算。就是在色彩变换矩阵的最后一行加上某个值;这样可以增加特定色彩的饱和度;

图片来自源博客

示例:为上图增大绿色饱和度

// 饱和度绿色
val colorMatrix = ColorMatrix(floatArrayOf(
                1f, 0f, 0f, 0f, 0f,     // RED
                0f, 1f, 0f, 0f, 50f,    // GREEN
                0f, 0f, 1f, 0f, 0f,     // BLUE
                0f, 0f, 0f, 1f, 0f))    // ALPHA
增加绿色饱和度

注意:由于图片是由一个个像素组成的,所以用每个像素所对应的色彩数组,来乘转换矩阵,结果就是转换后的当前点的颜色值;所以,在应用ColorMatrics后,图片中每个像素的绿色值都增加了50;

色彩平移除了增加指定颜色饱和度以外,另一个应用就是色彩反转:

// 色彩反相
val colorMatrix = ColorMatrix(floatArrayOf(
        -1f, 0f, 0f, 0f, 255f,     // RED
        0f, -1f, 0f, 0f, 255f,    // GREEN
        0f, 0f, -1f, 0f, 255f,     // BLUE
        0f, 0f, 0f, 1f, 0f))    // ALPHA
反相对比

色彩的缩放

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


图片来自源博客

可以针对某一个颜色值进行放大缩小运算,但当对R、G、B、A同时进行放大缩小时,就是对亮度进行调节!

// 色彩缩放,(全部为增加亮度)
val colorMatrix = ColorMatrix(floatArrayOf(
        1.2f, 0f, 0f, 0f, 0f,     // RED
        0f, 1.2f, 0f, 0f, 0f,    // GREEN
        0f, 0f, 1.2f, 0f, 0f,     // BLUE
        0f, 0f, 0f, 1.2f, 0f))    // ALPHA
色彩缩放-亮度放大1.2倍

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

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

各颜色通道下的效果

色彩的旋转运算

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

图片来自源博客

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

图片来自源博客

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

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

绕蓝色轴旋转a°

图片来自源博客

对应的色彩变换矩阵是:
图片来自源博客

绕红色轴旋转a度

image.png

对应的色彩变换矩阵是:
image.png

绕绿色轴旋转a度

image.png

对应的色彩变换矩阵是:
image.png

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

色彩的投射运算

色彩矩阵运算的公式:

源博客中的图片

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

比如:


源博客中的图片

注意:最终结果的220=0.2*100+1*200,可见绿色分量在原有绿色分量的基础上,增加了红色分量值的0.2倍;利用其它色彩分量的倍数来更改自己色彩分量的值,这种运算就叫投射运算

下图阴影部分;对这些值进行修改时,修改所使用的增加值来自于其它色彩分量的信息。

源博客中的图片

色彩投射的应用之 - 黑白照片

// 黑白
colorMatrix = ColorMatrix(floatArrayOf(
      0.213f, 0.715f, 0.072f, 0f, 0f,     // RED
      0.213f, 0.715f, 0.072f, 0f, 0f,     // GREEN
      0.213f, 0.715f, 0.072f, 0f, 0f,    // BLUE
      0f, 0f, 0f, 1f, 0f))    // ALPHA
黑白

彩色转黑白原理:

  1. 只要把RGB三通道的色彩信息设置成一样;即:R=G=B,那么图像就变成了灰色;
  2. 为了保证图像亮度不变,同一个通道中的R+G+B=1:如:0.213+0.715+0.072=1;

三个数字的由来:0.213, 0.715, 0.072;
按理说应该把RGB平分,都是0.3333333,可以试试0.33,原因还是看源博客吧;

投射运算之色彩反色
利用色彩矩阵将两个颜色反转,这种操作就叫做色彩反色 (如下:红绿色反转)

colorMatrix = ColorMatrix(floatArrayOf(
        0f, 1f, 0f, 0f, 0f,     // RED, 由绿色替换
        1f, 0f, 0f, 0f, 0f,     // GREEN,由红色替换
        0f, 0f, 1f, 0f, 0f,     // BLUE
        0f, 0f, 0f, 1f, 0f))    // ALPHA
红绿色反转-左为原图

投射运算之颜色变旧

        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自带了一些函数来帮我们完成一些调整饱和度、色彩旋转等操作;

ColorMatrix的构造函数:

ColorMatrix()  // new 一个新的
ColorMatrix(float[] src)   // 指定创建
ColorMatrix(ColorMatrix src)   // 以src为源copy 一个

setSaturation - 设置饱和度

通过色彩的平移运算,可单独增加R、G、B的饱和度,但如果整体呢,就需要用到 setSaturation函数了;

// 整体增强颜色饱和度,即同时增强R,G,B的色彩饱和度  
public void setSaturation(float sat) 

参数float sat:表示把当前色彩饱和度放大的倍数。取值

  • 0表示完全无色彩,即灰度图像(黑白图像;
  • 1时,表示色彩不变动;
  • 大于1时,显示色彩过度饱和,饱和度放大多少倍;
色彩饱和过渡效果

setScale - 色彩缩放

// 分别对应R,G,B,A颜色值的缩放倍数
public void setScale(float rScale, float gScale, float bScale,float aScale)  

效果如下:


色彩缩放

setRotate-色彩旋转

参考源博客,上面介绍了色彩旋转的原理,涉及到正余弦函数,同时Android封装好了色彩旋转的函数:

/** 
 * 将旋转围绕某一个颜色轴旋转 degrees 度
 * axis=0 围绕红色轴旋转 
 * axis=1 围绕绿色轴旋转 
 * axis=2 围绕蓝色轴旋转 
 */  
public void setRotate(int axis, float degrees);  

示例代码

seekBar {
      id = android.R.id.text1
      max = 360   // 整个旋转度数的范围是-180到180,360为一个周期
      progress = 180
      onSeekBarChangeListener {
              onProgressChanged { _, progress, _ ->
                      view7.colorMatrix.setRotate(0,progress-180*.10f)
                      view7.postInvalidate()
              }
     }
}.lparams(width = ViewGroup.LayoutParams.MATCH_PARENT)
色彩rotate

ColorMatrix相乘

涉及到矩阵相乘;ColorMatrix矩阵相乘涉及到3个方法:

// matA*matB,然后将结果做为当前ColorMatrix的值(覆盖当前)
public void setConcat(ColorMatrix matA, ColorMatrix matB)  

// 就是将当前的矩阵乘以 prematrix
public void preConcat(ColorMatrix prematrix)

// postmatrix*当前矩阵,上面为前乘,这里为后乘
public void postConcat(ColorMatrix postmatrix)

矩阵的前乘 与 后乘 的结果是不一样的;

setConcat()

下面两个颜色矩阵相乘:


图片来自源博客

因为矩阵相乘的原则是:第一个矩阵的列数需等于第2个矩阵的行数,明显这里不是这样的,那如何相乘呢?
Android提供了一个方案,让这两个矩阵相乘,就是把第一个矩阵的最后一列单独拿出来,另外加到结果上;


图片来自源博客

参考源博客

你可能感兴趣的:(自定义控件绘制(Paint之ColorMatrix与滤镜)篇六)