Android Paint系列之滤镜效果

图像结构

我们首先了解一下图像的构成,例如一张PNG图片:
图片文件头由位固定的字节来描述的,以便向外说明这个文件是一个PNG文件。

十进制数
137 80 78 71 13 10 26 10
十六进制数
89 50 4E 47 0D 0A 1A 0A

用UE打开一个PNG文件的内容为:


image.png

可以看到都为十六进制的数据,我们不知道这些数据是什么,假定第一行的数据是这个PNG的标志,第2-8行可能记录的是解析规则等信息,再之后的则为数据,那么

在一个图像文件中,总体包含的数据分为两部分:
1.文件的标志信息;
2.文件的数据信息;

标志的信息主要用来识别该文件是不是PNG文件,而数据块则储存了图片的图像信息;

PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是必需的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。
每个数据块都由4个域组成:


image.png
关键数据块
image.png
IHDR

文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。

image.png
颜色通道

保存图像颜色信息的通道称为颜色通道。

RGB color有合成意义的变化(color channel),就是人有合成意义地(只用肢体或使用工具)将红蓝绿三种(实色料或颜色)合成出(一个或多个实色料空间显示、阻挡显示、隐藏、消失,从合成后可以赋予意义、认为无意义、没有赋予的意义、没有发现的意义、没有认同的意义等多种意义;一种颜色或多种颜色显示、阻挡显示、隐藏、消失,从合成后可以赋予意义、认为无意义、没有赋予的意义、没有发现的意义、没有认同的意义等多种意义)。

每个图像都有一个或多个颜色通道,图像中默认的颜色通道数取决于其颜色模式,即一个图像的颜色模式将决定其颜色通道的数量。例如,CMYK图像默认有4个通道,分别为青色、洋红、黄色、黑色。在默认情况下,位图模式、灰度、双色调和索引颜色图像只有一个通道。RGB和Lab图像有3个通道,CMYK图像有4个通道。

颜色模式

颜色模式,是将某种颜色表现为数字形式的模型,或者说是一种记录图像颜色的方式。分为:RGB模式、CMYK模式、HSB模式、Lab颜色模式、位图模式、灰度模式、索引颜色模式、双色调模式和多通道模式。

总结

颜色通道取决于颜色模式。

通俗点说,我们要显示图片中的色彩的时候,用数字表示,例如RGB模式,例如R255-G0-B0用来显示当前像素的色彩,在显示的时候为红色,RGB为红绿蓝三种色彩进行混合,其对应颜色数值的大小范围为0~255, 即每个点都是由颜色模式所决定的颜色通道(色彩数值)混合形成我们想要的颜色,而要实现滤镜效果,其实就是对颜色通道值进行处理,在其原本的通道数值进行更改,达到更改图像色彩效果的目的,这就是我们所谓的滤镜。

颜色矩阵

在安卓中,颜色模式为RGBA,即在RGB模式上加了一个A(alpha)透明值,即为一个四通道的模式。

在安卓中有一个类叫ColorMatrix(颜色矩阵),其用一个4x5矩阵来进行描述,用于转换位图的颜色和alpha分量。
矩阵可以作为单个数组传递,并作如下处理:

ColorMatrix类的注解为:

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

当应用到颜色 [R,G,B,A],结果颜色计算为:

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;

由此产生的 [R,A,G,B]的值在0~255之间

我们可以理解为
安卓中的颜色矩阵是一个4×5的数字矩阵:


image.png

会以一维数组的形式来存储:(float[]类型)

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

即
new float[]{
    a, b, c, d, e,
    f, g, h, i, j,
    k, l, m, n, o,
    p, q, r, s, t 
    };
矩阵运算
image.png

当矩阵A的列数等于矩阵B的行数时,A与B可以相乘。
矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。
乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。

颜色矩阵运算

我们可通过ColorMatrix类生成指定的颜色矩阵A,


image.png

与颜色通道的原始色彩矩阵进行矩阵乘法运算。

这里有一个色彩矩阵分量C,代表着我们颜色通道的原始色彩:


image.png

矩阵R则代表通过矩阵乘法运算AC而得到的新的颜色:


image.png
R' = aR + bG + cB + dA + e;
G' = fR + gG + hB + iA + j;
B' = kR + lG + mB + nA + o;
A' = pR + qG + rB + sA + t;

我们这里可以看到,最后会加上最后一列的e, j, o, t,这是因为在安卓中(这个是安卓的颜色矩阵规则),前4列组成的才是四阶颜色矩阵:

image.png

而最后一列则是代表增量值,即4阶矩阵相乘后再加上这个增量,从而得到最终的颜色值。

颜色矩阵中,

  • 第一行决定红色值
  • 第一行决定绿色值
  • 第一行决定蓝色值
  • 第一行决定透明值

下面这个为原始矩阵,即对原色值不做任何改变:


image.png
下面我们通过代码来进行实现

首先我们先定义主页面

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

        <LinearLayout
            android:gravity="center"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <Button
                android:id="@+id/btn_filter"
                android:text="滤镜"
                android:textSize="22sp"
                android:onClick="onStartFilterActivity"
                android:textAllCaps="false"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <Button
                android:id="@+id/btn_custom"
                android:layout_marginTop="10dp"
                android:text="自定义"
                android:textSize="22sp"
                android:onClick="onStartCustomActivity"
                android:textAllCaps="false"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

</android.support.constraint.ConstraintLayout>

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onStartFilterActivity(View view) {
        startActivity(new Intent(this, FilterActivity.class));
    }

    public void onStartCustomActivity(View view) {
        startActivity(new Intent(this, CustomFilterActivity.class));
    }
}
效果图

实现两种功能,一个是采用固定的滤镜方式,一个是采用自定义的滤镜方式。

固定的滤镜方式

布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_tip1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="原图"
            android:layout_marginLeft="10dp"
            android:textColor="@android:color/holo_red_dark"
            android:textSize="22sp" />

        <ImageView
            android:id="@+id/tv_src"
            android:layout_gravity="center_horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/pic_xxx_01" />

        <TextView
            android:id="@+id/tv_tip2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:text="效果图"
            android:textColor="@android:color/holo_red_dark"
            android:textSize="22sp" />

        <com.iigo.paint.FilterView
            android:id="@+id/fv_filter"
            app:img_id="@mipmap/pic_xxx_01"
            android:layout_gravity="center_horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <LinearLayout
            android:gravity="center"
            android:layout_marginTop="10dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_draw_normal"
                android:textAllCaps="false"
                android:text="原图"
                android:onClick="onDrawNormal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <Button
                android:id="@+id/btn_draw_negative"
                android:textAllCaps="false"
                android:text="底片"
                android:onClick="onDrawNegative"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

        <LinearLayout
            android:gravity="center"
            android:layout_marginTop="10dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_draw_retro"
                android:textAllCaps="false"
                android:text="复古"
                android:onClick="onDrawRetro"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <Button
                android:id="@+id/btn_draw_fair"
                android:textAllCaps="false"
                android:text="美颜"
                android:onClick="onDrawFair"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

        <LinearLayout
            android:gravity="center"
            android:layout_marginTop="10dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_draw_bandw"
                android:textAllCaps="false"
                android:text="黑白"
                android:onClick="onDrawBAndW"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <Button
                android:id="@+id/btn_draw_change"
                android:textAllCaps="false"
                android:text="发色"
                android:onClick="onDrawChange"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

FilterActivity

public class FilterActivity extends AppCompatActivity {
    private FilterView filterView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_filter);
        filterView = findViewById(R.id.fv_filter);
    }

    public void onDrawNormal(View view){
        filterView.drawNormal();
    }

    public void onDrawNegative(View view){
        filterView.drawNegative();
    }

    public void onDrawRetro(View view){
        filterView.drawRetro();
    }

    public void onDrawFair(View view){
        filterView.drawFair();
    }

    public void onDrawBAndW(View view){
        filterView.drawBlackAndWhite();
    }

    public void onDrawChange(View view){
        filterView.drawChange();
    }
}
效果图

点击对应效果的按钮进行效果查看

自定义的滤镜方式

布局

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.iigo.paint.FilterView
        android:id="@+id/fv_filter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        app:img_id="@mipmap/pic_xxx_01" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/et_11"
                android:text="1"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_12"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_13"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_14"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_15"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/et_21"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_22"
                android:text="1"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_23"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_24"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_25"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/et_31"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_32"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_33"
                android:text="1"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_34"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />

            <EditText
                android:id="@+id/et_35"
                android:text="0"
                android:inputType="numberSigned|numberDecimal"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:gravity="center" />
        </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:orientation="horizontal">

                <EditText
                    android:id="@+id/et_41"
                    android:text="0"
                    android:inputType="numberSigned|numberDecimal"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:layout_weight="1"
                    android:gravity="center" />

                <EditText
                    android:id="@+id/et_42"
                    android:text="0"
                    android:inputType="numberSigned|numberDecimal"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:layout_weight="1"
                    android:gravity="center" />

                <EditText
                    android:id="@+id/et_43"
                    android:text="0"
                    android:inputType="numberSigned|numberDecimal"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:layout_weight="1"
                    android:gravity="center" />

                <EditText
                    android:id="@+id/et_44"
                    android:text="1"
                    android:inputType="numberSigned|numberDecimal"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:layout_weight="1"
                    android:gravity="center" />

                <EditText
                    android:id="@+id/et_45"
                    android:text="0"
                    android:inputType="numberSigned|numberDecimal"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:layout_weight="1"
                    android:gravity="center" />
            </LinearLayout>

        <Button
            android:id="@+id/btn_draw"
            android:layout_marginTop="10dp"
            android:text="执行"
            android:onClick="onDraw"
            android:layout_gravity="center_horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/btn_recover"
            android:layout_marginTop="10dp"
            android:text="还原为最原始数据"
            android:onClick="onRecover"
            android:layout_gravity="center_horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>
</LinearLayout>

CustomeFilterActivity代码为

public class CustomFilterActivity extends AppCompatActivity {
    /**
     * <pre>
     *  [ a, b, c, d, e,
     *    f, g, h, i, j,
     *    k, l, m, n, o,
     *    p, q, r, s, t ]</pre>
     *
     */
    private EditText et11;
    private EditText et12;
    private EditText et13;
    private EditText et14;
    private EditText et15;

    private EditText et21;
    private EditText et22;
    private EditText et23;
    private EditText et24;
    private EditText et25;

    private EditText et31;
    private EditText et32;
    private EditText et33;
    private EditText et34;
    private EditText et35;

    private EditText et41;
    private EditText et42;
    private EditText et43;
    private EditText et44;
    private EditText et45;

    private FilterView filterView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom);

        initViews();
    }

    private void initViews() {
        et11 = findViewById(R.id.et_11);
        et12 = findViewById(R.id.et_12);
        et13 = findViewById(R.id.et_13);
        et14 = findViewById(R.id.et_14);
        et15 = findViewById(R.id.et_15);

        et21 = findViewById(R.id.et_21);
        et22 = findViewById(R.id.et_22);
        et23 = findViewById(R.id.et_23);
        et24 = findViewById(R.id.et_24);
        et25 = findViewById(R.id.et_25);

        et31 = findViewById(R.id.et_31);
        et32 = findViewById(R.id.et_32);
        et33 = findViewById(R.id.et_33);
        et34 = findViewById(R.id.et_34);
        et35 = findViewById(R.id.et_35);

        et41 = findViewById(R.id.et_41);
        et42 = findViewById(R.id.et_42);
        et43 = findViewById(R.id.et_43);
        et44 = findViewById(R.id.et_44);
        et45 = findViewById(R.id.et_45);

        et11.addTextChangedListener(new MyTextWatcher(et11, "1"));
        et12.addTextChangedListener(new MyTextWatcher(et12, "0"));
        et13.addTextChangedListener(new MyTextWatcher(et13, "0"));
        et14.addTextChangedListener(new MyTextWatcher(et14, "0"));
        et15.addTextChangedListener(new MyTextWatcher(et15, "0"));

        et21.addTextChangedListener(new MyTextWatcher(et21, "0"));
        et22.addTextChangedListener(new MyTextWatcher(et22, "1"));
        et23.addTextChangedListener(new MyTextWatcher(et23, "0"));
        et24.addTextChangedListener(new MyTextWatcher(et24, "0"));
        et25.addTextChangedListener(new MyTextWatcher(et25, "0"));

        et31.addTextChangedListener(new MyTextWatcher(et31, "0"));
        et32.addTextChangedListener(new MyTextWatcher(et32, "0"));
        et33.addTextChangedListener(new MyTextWatcher(et33, "1"));
        et34.addTextChangedListener(new MyTextWatcher(et34, "0"));
        et35.addTextChangedListener(new MyTextWatcher(et35, "0"));

        et41.addTextChangedListener(new MyTextWatcher(et41, "0"));
        et42.addTextChangedListener(new MyTextWatcher(et42, "0"));
        et43.addTextChangedListener(new MyTextWatcher(et43, "0"));
        et44.addTextChangedListener(new MyTextWatcher(et44, "1"));
        et45.addTextChangedListener(new MyTextWatcher(et45, "0"));

        filterView = findViewById(R.id.fv_filter);
    }

    public void onRecover(View view){
        et11.setText("1");
        et12.setText("0");
        et13.setText("0");
        et14.setText("0");
        et15.setText("0");

        et21.setText("0");
        et22.setText("1");
        et23.setText("0");
        et24.setText("0");
        et25.setText("0");

        et31.setText("0");
        et32.setText("0");
        et33.setText("1");
        et34.setText("0");
        et35.setText("0");

        et41.setText("0");
        et42.setText("0");
        et43.setText("0");
        et44.setText("1");
        et45.setText("0");
    }

    public void onDraw(View view){
        //若EditText为空,默认值为0

        int  n11 = Integer.valueOf(et11.getText().toString());
        int  n12 = Integer.valueOf(et12.getText().toString());
        int  n13 = Integer.valueOf(et13.getText().toString());
        int  n14 = Integer.valueOf(et14.getText().toString());
        int  n15 = Integer.valueOf(et15.getText().toString());

        int  n21 = Integer.valueOf(et21.getText().toString());
        int  n22 = Integer.valueOf(et22.getText().toString());
        int  n23 = Integer.valueOf(et23.getText().toString());
        int  n24 = Integer.valueOf(et24.getText().toString());
        int  n25 = Integer.valueOf(et25.getText().toString());

        int  n31 = Integer.valueOf(et31.getText().toString());
        int  n32 = Integer.valueOf(et32.getText().toString());
        int  n33 = Integer.valueOf(et33.getText().toString());
        int  n34 = Integer.valueOf(et34.getText().toString());
        int  n35 = Integer.valueOf(et35.getText().toString());

        int  n41 = Integer.valueOf(et41.getText().toString());
        int  n42 = Integer.valueOf(et42.getText().toString());
        int  n43 = Integer.valueOf(et43.getText().toString());
        int  n44 = Integer.valueOf(et44.getText().toString());
        int  n45 = Integer.valueOf(et45.getText().toString());

        ColorMatrix colorMatrix = new ColorMatrix(new float[]{
                n11, n12, n13, n14, n15,
                n21, n22, n23, n24, n25,
                n31, n32, n33, n34, n35,
                n41, n42, n43, n44, n45,
        });

        filterView.drawCustom(colorMatrix);
    }

    private class MyTextWatcher implements TextWatcher {
        private EditText editText;
        private String defaultValue;

        public MyTextWatcher(EditText editText, String defaultValue){
            this.editText = editText;
            this.defaultValue = defaultValue;
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (TextUtils.isEmpty(s.toString())){
                editText.setText(defaultValue);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    }
}

点击执行按钮进行绘制,点击还原按钮还原为原始颜色矩阵数据。


效果图

最后自定义FilterView的代码为:

public class FilterView extends View {
    private int imageId = -1;
    private Paint paint;
    private Bitmap bitmap;
    private ColorMatrix cColorMatrix;

    public enum TYPE{
        NORMAL, //正常图片
        NEGATIVE, //底片
        RETRO, //复古
        FAIR, //美颜
        BAW,//黑白(black and white)
        CHANGE, //发色,改变颜色
        CUSTOM,//自定义
    }

    private TYPE currentType = TYPE.NORMAL;

    public FilterView(Context context, int imageId) {
        super(context);

        this.imageId = imageId;
        init(null);
    }

    public FilterView(Context context) {
        super(context);

        init(null);
    }

    public FilterView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        init(attrs);
    }


    public FilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init(attrs);
    }

    @SuppressLint("NewApi")
    public FilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        init(attrs);
    }

    private void init(AttributeSet attrs) {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);

        if (attrs == null){
            return;
        }

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FilterView);

        int count = a.getIndexCount();
        for (int i = 0; i < count; i++){
            int attr = a.getIndex(i);
            switch (attr){
                case R.styleable.FilterView_img_id:
                    imageId = a.getResourceId(attr, 0);
                    break;
            }
        }
        a.recycle();

        if (imageId == -1){
            return;
        }

        try{
            bitmap = BitmapFactory.decodeResource(getResources(), imageId);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (bitmap == null){
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        //根据图片大小设置当前view的大小
        super.onMeasure(MeasureSpec.makeMeasureSpec(bitmap.getWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(bitmap.getHeight(), MeasureSpec.EXACTLY));

        //若要实现图片的自适应view的大小,可自行实现..
        //...
        //
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (bitmap == null){
            return;
        }

        setLayerType(View.LAYER_TYPE_SOFTWARE,null);

        paint.reset();

        ColorMatrix colorMatrix = null;
        RectF rectF = new RectF(0,0, bitmap.getWidth(), bitmap.getHeight());
        paint.reset();

        switch (currentType){
            case NORMAL:
                break;

            case NEGATIVE:
                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,
                });
                break;

            case RETRO:
                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,
                });
                break;

            case FAIR:
                colorMatrix = new ColorMatrix(new float[]{
                        1.25f, 0,0,0,0,
                        0,1.25f,0,0,0,
                        0,0,1.25f,0,0,
                        0,0,0,1.25f,0,
                });
                break;

            case BAW:
                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,
                });
                break;

            case CHANGE:
                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,
                });
                break;

            case CUSTOM:
                colorMatrix = cColorMatrix;
                break;

                default: break;
        }

        if (colorMatrix != null){
            paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
        }

        canvas.drawBitmap(bitmap,null, rectF, paint);
    }

    private void performDraw(){
        if (bitmap == null){
            return;
        }

        invalidate();
    }

    /**
     * 显示正常图片
     * */
    public void drawNormal(){
        currentType = TYPE.NORMAL;
        performDraw();
    }

    /**
     * 显示底片效果
     * */
    public void drawNegative(){
        currentType = TYPE.NEGATIVE;
        performDraw();
    }

    /**
     * 显示复古效果
     * */
    public void drawRetro(){
        currentType = TYPE.RETRO;
        performDraw();
    }

    /**
     * 显示美颜效果
     * */
    public void drawFair(){
        currentType = TYPE.FAIR;
        performDraw();
    }

    /**
     * 显示黑白效果
     * */
    public void drawBlackAndWhite(){
        currentType = TYPE.BAW;
        performDraw();
    }

    /**
     * 显示发色效果,这里为红色和绿色交换
     * */
    public void drawChange(){
        currentType = TYPE.CHANGE;
        performDraw();
    }

    /**
     * 显示自定义效果
     *
     * @param colorMatrix 指定的滤镜效果
     * */
    public void drawCustom(ColorMatrix colorMatrix){
        currentType = TYPE.CUSTOM;
        cColorMatrix = colorMatrix;
        performDraw();
    }
}
滤镜实现

我们只需要调用接口

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,
                });
paint.setColorFilter(new ColorMatrixColorFilter(colorMartrix));

便可实现滤镜效果

我们将R即红色增加1倍,即

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

即可看到图片变为红色,


效果图

将G即绿色增加1倍,即

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

即可看到图片变为绿色,


效果图

将R即红色和G即绿色同时增加1倍,即

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

即可看到图片颜色混合变为黄色,


效果图

我们还可以指定类似于底片的颜色,即

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

底片效果其实就是反相效果,将RGB的值取反,例如RGB的值分别为r,g,b,反相的意思就是取
255 - r, 255 - g, 255 - b的值。

效果图

黑白照片效果,即

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

是将我们的三通道变为单通道的灰度模式,去色原理:只要把R G B 三通道的色彩信息设置成一样,那么图像就会变成灰色, 同时为了保证图像亮度不变,同一个通道里的R+G+B =1。


image.png

demo地址为:https://github.com/samlss/Paint

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容