美文网首页
自定义控件绘制(Paint之ColorMatrix与滤镜)篇六

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

作者: zhaoyubetter | 来源:发表于2018-04-09 21:24 被阅读29次

    内容均来自

    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与滤镜)篇六

          本文链接:https://www.haomeiwen.com/subject/elyshftx.html