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
下面我们通过代码来进行实现

首先我们先定义主页面




        

            

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));
    }
}
效果图

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

固定的滤镜方式

布局




    

        

        

        

        

        

            

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();
    }
}
效果图

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

自定义的滤镜方式

布局




    

    

        

            

            

            

            

            
        

        

            

            

            

            

            
        

        

            

            

            

            

            
        

            

                

                

                

                

                
            

        

CustomeFilterActivity代码为

public class CustomFilterActivity extends AppCompatActivity {
    /**
     * 
     *  [ a, b, c, d, e,
     *    f, g, h, i, j,
     *    k, l, m, n, o,
     *    p, q, r, s, t ]
* */ 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

你可能感兴趣的:(Android Paint系列之滤镜效果)