颜色矩阵的使用

前言

昨天在学习Bitmap的相关接口的时候发现了一个很有趣的知识点——颜色矩阵,编写完demo之后赶紧进行一个记录。这次的学习主要是通过下面两篇博客进行高级UI<第十八篇>:图像处理之颜色矩阵 - 简书 (jianshu.com)8.3.9 Paint API之—— ColorFilter(颜色过滤器)(1/3) | 菜鸟教程 (runoob.com)

ColorFilter

安卓开发中Paint类就提供了一个接口进行处理颜色矩阵,使得开发者进行简单的图像处理 的效率提高。paint.setColorFilter(ColorFilter filter),使用上就是这么简单,所以我们后面的文章主要要搞懂的就是这个ColorFilter到底需要怎么去进行设置学习。

我们直接使用最直观的代码去进行ColorFilter的学习,我们使用paint.setColorFilter自然就需要声明一个ColorFilter对象进行实参的传递。

ColorFilter colorFilter = new ColorMatrixColorFilter(colorMatrix);

这里使用的是ColorFilter的继承类进行声明,这时候可以看到就引入了colorMatrix形参,这也是我们的重点知识点。要深刻理解colorMatrix我们需要先了解一下颜色模式。

ColorMatrix

颜色模式

该博客主要是进行总结学习过程,所以我只会重点关注RGB模式以及CMYK模式,说明我对其的理解。如果想要了解其他的模式,可以去我上面的简书链接中进行学习。

RGB颜色模式:我们今天学习的颜色矩阵就是在这个模式基础上进行操作的,这个颜色模式也是最常见的颜色模式,就是我们经常会碰到的三基色模式,因为自然界中所有的颜色都可以用红、绿、蓝(RGB)这三种颜色波长的不同强度组合而得,这就是人们常说的三基色原理。因此,这三种光常被人们称为三基色或三原色。有时候我们亦称这三种基色为添加色(Additive Colors),这是因为当我们把不同光的波长加到一起的时候,得到的将会是更加明亮的颜色。把三种基色交互重叠,就产生了次混合色:青(Cyan)、洋红(Magenta)、黄(Yellow)。

CMYK颜色模式:这个模式在我们日常生活主要是用在印刷的时候去选择的颜色模式,可能各位有学过PS的应该对这些颜色模式还是比较了解的,其中四个字母分别指青(Cyan)、洋红(Magenta)、黄(Yellow)、黑(Black),在印刷中代表四种颜色的油墨。CMYK模式在本质上与RGB模式没有什么区别,只是产生色彩的原理不同,在RGB模式中由光源发出的色光混合生成颜色,而在CMYK模式中由光线照到有不同比例C、M、Y、K油墨的纸上,部分光谱被吸收后,反射到人眼的光产生颜色。

了解完颜色模式之后,我们便可以来看看这个ColorMatrix到底是长什么个样子。

矩阵结构

其实可以到ColorMatrix的源码中进行学习,其对矩阵的各方面都还是很清晰的,我接下来的讲解,主要是自己学习的理解,不一定原创,会有一定的搬运,都是在上面的链接中进行搬运。

颜色矩阵是一个5*4的矩阵

[ a  b  c  d  e
   f  g  h  i  j
   k  l  m  n  o
   p  q  r  s  t ]

每一行表示的意思 分别代表了R、G、B、饱和度通道。有一副图可以很直观的显示出来。

每一张处理的图片都有自己的一个颜色矩阵,只是我们一般进行图像处理都是通过用原本的颜色矩阵与一个经过处理的矩阵进行相乘 。paint.setColorFilter这个方法本质就是这样去进行图像处理。举例说明:

图片有着自己原始的颜色矩阵

 [ R
   G
   B               图像原始矩阵
   A
   1 ]

假设这时候有一个设置好的矩阵A ,我们将其进行相乘操作,最后得到的矩阵我们再将其运用到图片中,便可简单实现图像处理。

 [ a  b  c  d  e                 [ R            
   f  g  h  i  j          乘以      G         =  结果如下
   k  l  m  n  o                   B
   p  q  r  s  t ]                 A
                                   1 ]
 [ aR + bG + cB + dA + e
   fR + gG + hB + iA + j
   kR + lG + mB + nA + o
   pR + qG + rB + sA + t ]

可能各位看到这里对于这个矩阵还是迷迷糊糊的,矩阵相乘学过线性代数的肯定都能够理解的,但是我们更想知道更多是这里面的矩阵每一个单元的数字怎么调整才能做到自己想要的图像效果。接下来我将通过一个demo去进行一个自己浅薄的理解。

我们首先将demo去进行一个讲解。

demo实现

我们先来看看最后实现的一个demo的视觉效果。

 因为是demo咱们注重整体的功能实现,在界面上能看就行,通过修改颜色矩阵的值,点击确认,上面的图片就会显示图像处理后的效果。

整体的代码实现不是很难,我就把它pull出来,对某些陌生的点进行一个讲解。

布局文件:

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


    <ImageView
        android:visibility="visible"
        android:layout_weight="2"
        android:id="@+id/srcimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/editfilter" />

    <GridLayout
        android:layout_weight="2"
        android:id="@+id/matrixgridlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:columnCount="5"
        android:rowCount="4"
        android:orientation="horizontal">
    </GridLayout >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:text="重置"
            android:id="@+id/resetbtn"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"/>
        <Button
            android:text="确定"
            android:id="@+id/setfilterbtn"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"/>
    </LinearLayout>

</LinearLayout>

Java代码

package com.yjs.secondpartapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Handler;
import android.text.InputType;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.GridLayout;
import android.widget.ImageView;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type;

/*
 * 图像矩阵处理图片
 * */
public class ColorFilterActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView srcImage;
    private GridLayout matrixGridLayout;
    private EditText[][] editArrays = new EditText[4][5];
    private Handler mainHandle;
    private Button resetBtn;
    private Button setFilterBtn;
    private Bitmap srcbitmap;
    private float[] dstMatrixValue=new float[20];


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_color_filter);
        mainHandle = new Handler(getMainLooper());
        bindAndCreateView();
    }

    private void bindAndCreateView() {
        srcImage = findViewById(R.id.srcimage);
        srcbitmap = BitmapFactory.decodeResource(getResources(), R.drawable.editfilter);
        //压缩Bitmap
        srcbitmap = Bitmap.createScaledBitmap(srcbitmap, srcbitmap.getWidth() / 5, srcbitmap.getHeight() / 5, true);
        srcImage.setImageBitmap(srcbitmap);
        matrixGridLayout = findViewById(R.id.matrixgridlayout);
        resetBtn=findViewById(R.id.resetbtn);
        resetBtn.setOnClickListener(this);
        setFilterBtn=findViewById(R.id.setfilterbtn);
        setFilterBtn.setOnClickListener(this);
        matrixGridLayout.setColumnCount(5);
        matrixGridLayout.setRowCount(4);
        int count = 1;
        //动态生成EditText
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 5; j++) {
                System.out.println("count:" + count);
                EditText btn = new EditText(this);
                btn.setWidth(150);
                if (i == j) {
                    btn.setText(String.valueOf(1));
                } else {
                    btn.setText(String.valueOf(0));
                }
                editArrays[i][j] = btn;
                count++;
                GridLayout.Spec rowSpec = GridLayout.spec(i);     //设置它的行和列
                GridLayout.Spec columnSpec = GridLayout.spec(j);
                GridLayout.LayoutParams params = new GridLayout.LayoutParams(rowSpec, columnSpec);
                params.topMargin = 20;
                matrixGridLayout.addView(btn, params);
            }
        }


    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.resetbtn:
                //首先需要重置数字
                resetFilterMatrixNum();
                //重置完之后进行应用
                setFilter();
                break;
            case R.id.setfilterbtn:
                setFilter();
                break;
        }
    }

    //添加颜色滤镜,重中之重
    private void setFilter() {
        Log.d("yjs",dstMatrixValue.toString());
        getColorMatrix();
        ColorMatrix colorMatrix = new ColorMatrix();
        colorMatrix.set(dstMatrixValue);
        Paint paint = new Paint();
        ColorFilter colorFilter = new ColorMatrixColorFilter(colorMatrix);
        paint.setAntiAlias(true);
        paint.setColorFilter(colorFilter);
        Bitmap bmp = Bitmap.createBitmap(srcbitmap.getWidth(), srcbitmap.getHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmp);
        canvas.drawBitmap(srcbitmap,0,0,paint);
        srcImage.setImageBitmap(bmp);
    }

    private void getColorMatrix() {
        int count=0;
        for (int i=0;i<4;i++){
            for (int j=0;j<5;j++){
                dstMatrixValue[count]=Float.valueOf(editArrays[i][j].getText().toString());
                count++;
            }
        }
    }

    private void resetFilterMatrixNum() {
        for (int i=0;i<4;i++){
            for (int j=0;j<5;j++){
                if (i == j) {
                    editArrays[i][j].setText(String.valueOf(1));
                } else {
                    editArrays[i][j].setText(String.valueOf(0));
                }
            }
        }
        getColorMatrix();
    }
}

其实整体代码都是比较简单的,我们只需要额外关注一下setFilter方法即可,是整个代码demo的核心。

ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.set(dstMatrixValue);
Paint paint = new Paint();
ColorFilter colorFilter = new ColorMatrixColorFilter(colorMatrix);
paint.setAntiAlias(true);
paint.setColorFilter(colorFilter);
Bitmap bmp = Bitmap.createBitmap(srcbitmap.getWidth(), srcbitmap.getHeight(),
        Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
canvas.drawBitmap(srcbitmap,0,0,paint); 

demo效果 

在实现demo之后,我们便可以来看看各种设置颜色矩阵的不同单元会产生什么效果。

调节亮度

 [ 1  0  0  0  N
   0  1  0  0  N
   0  0  1  0  N
   0  0  0  1  0 ]

N就是调整三通道的亮度,我们只需要设置一下RGB的色彩偏移就能调节其亮度。下面是将N设置为30的效果图,可以看到很大程度上进行了提亮操作,如果觉得不是很明显,可以将N设置为100,这时候的效果将会得到显著的提高,但是需要注意的是,这个值是有上限的,其实可以想象一下,如果一个东西一直增加亮度,是不是最后都会变成白色,这里的图片处理也是如此,将N设置为255的时候,这时候图片就会变成全是白色的效果。

 

 颜色反向(底片效果)

 [ -1  0  0  0  255
   0  -1  0  0  255
   0  0  -1  0  255
   0  0  0   1  0 ]

把RGB通道的原通道乘数设为-1,然后再把色彩偏移量设为255。其实仔细思考一下也能很好明白为什么这样设置能出现底片的效果。在RGB通道上面将原本的1变成-1,呈现出来的自然就是黑的变成白的,完全相反的样子。值得注意的是最后一行的饱和度通道的1一定不能修改,当饱和度从原本的1变成-1,自然图片就会变成透明。其次看最后的一列的偏移度的矩阵,刚刚说过黑的变成白的,也就是说这时候如果不设置这一列,按照原本的1数值的话就会使整个图片全黑。与之类比的就是当RGB通道都是1的时候,把偏移那一列的数值变成255就会使整个图片全白。感觉这个东西就是凭感觉去感受,实现这个demo瞎改改数值应该能更好的理解。

颜色去色 

 [ 0.3086  0.6094  0.0820  0  0
   0.3086  0.6094  0.0820  0  0
   0.3086  0.6094  0.0820  0  0
   0  0  0  1  0 ]

只要把RGB三通道的色彩信息设置成一样;即:R=G=B,那么图像就变成了灰色,并且,为了保证图像亮度不变,同一个通道中的R+G+B=1。 饱和度通道可以在0-1这个区间进行设置。

 

 

对比度 

[N,0,0,0,128*(1-N)
0,N,0,0,128*(1-N)
0,0,N,0,128*(1-N)
0,0,0,1,0]

N的取值是0-10,对应的通道进行修改,其实就是使红的更加红,蓝的更加蓝,绿的更加绿 

 

由于篇幅原因就只举例这几个矩阵样式,我这边搬运一下其他效果的矩阵,供大家实现demo后进行尝试学习理解。

例子六: 阈值

所谓阈值,就是以一个色度值为基准对图像作非黑即白的处理(注意没有灰色),由于不去除了彩色属性,因此,也离不开0.3086, 0.6094, 0.0820这三组神奇的数字。

下面的256也可以改成255

0.3086*256,0.6094*256,0.0820*256,0,-256*N
0.3086*256,0.6094*256,0.0820*256,0,-256*N
0.3086*256,0.6094*256,0.0820*256,0,-256*N
0, 0, 0, 1, 0

例子七: 色彩旋转

所谓色彩旋转就是让某一个通道的色彩信息让另一个通道去显示;比如,R显示G的信息,G显示B的信息,B显示R的信息,也可以只拿出一部份信息让给别的通道去显示,至于参数的瓜分可以平分。不必太讲究,但是,始终要坚持的一个原则就是每一个通道中的RGB信息量之和一定要为1,不然将会生偏色,如果您要制作偏色效果又另当别论;

0,1,0,0,0
0,0,1,0,0
1,0,0,0,0
0,0,0,1,0

或者

0,0,1,0,0
1,0,0,0,0
0,1,0,0,0
0,0,0,1,0

比如:发色效果

        0,1,0,0,0,
        1, 0,0,0,0,
        0,0,1,0,0,
        0,0,0,1,0,

例子八: 只显示某个通道

1,0,0,0,0

例子九: 颜色增强

 [ 1.2  0  0  0  N
   0  1.4  0  0  N
   0  0  1.1  0  N
   0  0  0  1  0 ]

例子九: 复古效果

        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,

例子十: 颜色通道过滤

        1, 0,0,0,0,
        0,0,0,0,0,
        0,0,0,0,0,
        0,0,0,1,0,

这个比较简单,原本是1倍颜色,现在改成了:红色通道增强到1.2倍、红色通道增强到1.2倍、红色通道增强到1.2倍、绿色通道增强到1.4倍,蓝色通道增强到1.1倍。



作者:NoBugException
链接:https://www.jianshu.com/p/d183b4edeb56
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

总结

颜色矩阵虽然在这里的使用很简单,但是我觉得他将成为我的图像处理的开门之作。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值