Introduction to this section:

In the previous section we learned about MaskFilter, using its two subclasses BlurMaskFilter to create a blur effect, and EmbossMaskFilter to create an emboss effect. In this section we will learn about another API——ColorFilter(color filter). Like MaskFilter, we don't use this class directly, but instead use its three subclasses:

Color matrix color filter:ColorMatrixColorFilter

Lighting color filter:LightingColorFilter

Blend color filterPorterDuffColorFilter

In this section we will learn about the usage of the first one, ColorMatrixColorFilter. Open the ColorMatrixColorFilter documentation,

It roughly says: use a 4 x 5 color matrix to transform colors, can modify pixel saturation, convert YUV to RGB, etc. And the ColorMatrix in the constructor is the color matrix, which is also the core of our study. Let me explain it one by one!

PS:ColorMatrix API documentation


1. Popularization of related knowledge:

RGBA model:

I don't know if you've heard of RGBA. You must know red, green and blue, the three primary colors of light, and RGBA adds transparency to these.R (Red),G (Green),B (Blue),A (Alpha transparency); in addition, it should be distinguished from the three primary colors of pigments. The most obvious difference is that the three primary colors of pigments use yellow to replace green from the three primary colors of light. It's good to know this; if interested, you can Baidu it yourself.

Some terms:

  • Tone/Hue— the color an object transmits
  • Saturation— the purity of the color, described from 0 (gray) to 100% (saturated)
  • Brightness/Lightness— the relative lightness or darkness of a color

2. Interpretation of ColorMatrix

As the title suggests, the color matrix (4 * 5) allows us to modify the values in the matrix to achieve effects such as black-and-white photos, yellowish old photos, high contrast, etc. The hand-drawn explanation diagram of the color matrix is as follows:

I don't know if you understand the above diagram. If you've studied advanced mathematics, you must be familiar with this; it's nothing more than matrix cross multiplication. If you haven't studied it, it doesn't matter. The calculation method is the one in the lower right corner,Take each row of the color matrix and multiply it by each column of the color matrix components.!

A very typical example is comparing the results before and after processing. We can also let a certaincolor value be multiplied by a constant, for example, multiply the third row (blue) by 2, and the effect becomes bluish. Of course, we definitely need to write code to verify the above results!


3. Write code to verify the role of ColorMatrix

Here we write a very common example: an ImageView, 4 * 5 EditTexts, a reset button and a generate button. Let's look at the effect diagram:

In order: original image, yellowish, greenish, reddish, high contrast, hue transform, and yellow retro.

Next, let's write code to achieve the above effects:Code implementation:

First, the layout fileactivity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp">

    <ImageView
        android:id="@+id/img_show"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2" />

    <GridLayout
        android:id="@+id/gp_matrix"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3"
        android:columnCount="5"
        android:rowCount="4"></GridLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_reset"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="重置" />
        <Button
            android:id="@+id/btn_Change"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="变换" />
    </LinearLayout>
</LinearLayout>

Next isMainActivity.java:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView img_show;
    private GridLayout gp_matrix;
    private Button btn_reset;
    private Button btn_Change;
    private Bitmap mBitmap;
    private int mEtWidth, mEtHeight;
    private EditText[] mEts = new EditText[20];
    private float[] mColorMatrix = new float[20];
    private Context mContext;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        bindViews();


        gp_matrix.post(new Runnable() {
            @Override
            public void run() {
                mEtWidth = gp_matrix.getWidth() / 5;
                mEtHeight = gp_matrix.getHeight() / 4;
                //添加5 * 4个EditText
                for (int i = 0; i < 20; i++) {
                    EditText editText = new EditText(mContext);
                    mEts[i] = editText;
                    gp_matrix.addView(editText, mEtWidth, mEtHeight);
                }
                initMatrix();
            }
        });
    }

    private void bindViews() {
        img_show = (ImageView) findViewById(R.id.img_show);
        gp_matrix = (GridLayout) findViewById(R.id.gp_matrix);
        btn_reset = (Button) findViewById(R.id.btn_reset);
        btn_Change = (Button) findViewById(R.id.btn_Change);

        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_meizi);
        img_show.setImageBitmap(mBitmap);

        btn_reset.setOnClickListener(this);
        btn_Change.setOnClickListener(this);
    }


    //定义一个初始化颜色矩阵的方法
    private void initMatrix() {
        for (int i = 0; i < 20; i++) {
            if (i % 6 == 0) {
                mEts[i].setText(String.valueOf(1));
            } else {
                mEts[i].setText(String.valueOf(0));
            }
        }
    }

    //定义一个获取矩阵值得方法
    private void getMatrix() {
        for (int i = 0; i < 20; i++) {
            mColorMatrix[i] = Float.valueOf(mEts[i].getText().toString());
        }
    }

    //根据颜色矩阵的值来处理图片
    private void setImageMatrix() {
        Bitmap bmp = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(),
                Bitmap.Config.ARGB_8888);
        android.graphics.ColorMatrix colorMatrix = new android.graphics.ColorMatrix();
        colorMatrix.set(mColorMatrix);

        Canvas canvas = new Canvas(bmp);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
        canvas.drawBitmap(mBitmap, 0, 0, paint);
        img_show.setImageBitmap(bmp);
    }
    
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_Change:
                getMatrix();
                setImageMatrix();
                break;
            case R.id.btn_reset:
                initMatrix();
                getMatrix();
                setImageMatrix();
                break;
        }
    }
}

The code is very simple: load the layout, then stuff 5 * 4 EditTexts into the GridLayout. The post() method is used here to ensure that the GridLayout is fully loaded before getting its width and height; otherwise you won't get the values when getting the GridLayout width and height. Next, three methods are defined: initializing the matrix, getting the matrix values, and processing the image based on the matrix values. Isn't it simple?

But at this point you might have a littlequestion:

"Can we only modify the color matrix in this way to process images? Doing it every time is certainly troublesome; who would remember the values that should be filled in the matrix? Is there a simpler way to process images?"

Answer: Of course there is. We can look back at the documentation and find several very commonly used methods:setRotate(int axis, float degrees): set hue

setSaturation(float sat): set saturation

setScale(float rScale, float gScale, float bScale, float aScale): set brightness

Below we'll write an example to try out these three methods!


4. Using three methods of ColorMatrix to process images

Running effect diagram:

Code implementation:

First, let's write an image processing utility class. We pass in a Bitmap, hue, saturation, and brightness; after processing, it returns the processed image:ImageHelper.java:

/**
 * Created by Jay on 2015/10/28 0028.
 */
public class ImageHelper {
    /**
     * 该方法用来处理图像,根据色调,饱和度,亮度来调节
     *
     * @param bm:要处理的图像
     * @param hue:色调
     * @param saturation:饱和度
     * @param lum:亮度
     *
     */
    public static Bitmap handleImageEffect(Bitmap bm, float hue, float saturation, float lum) {
        Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmp);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

        ColorMatrix hueMatrix = new ColorMatrix();
        hueMatrix.setRotate(0, hue);    //0代表R,红色
        hueMatrix.setRotate(1, hue);    //1代表G,绿色
        hueMatrix.setRotate(2, hue);    //2代表B,蓝色

        ColorMatrix saturationMatrix = new ColorMatrix();
        saturationMatrix.setSaturation(saturation);

        ColorMatrix lumMatrix = new ColorMatrix();
        lumMatrix.setScale(lum, lum, lum, 1);


        ColorMatrix imageMatrix = new ColorMatrix();
        imageMatrix.postConcat(hueMatrix);
        imageMatrix.postConcat(saturationMatrix);
        imageMatrix.postConcat(lumMatrix);

        paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
        canvas.drawBitmap(bm, 0, 0, paint);

        return bmp;
    }
}

Next, let's also create the layout,activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp">

    <ImageView
        android:id="@+id/img_meizi"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="24dp"
        android:layout_marginTop="24dp" />


    <TextView
        android:id="@+id/txt_hue"
        android:layout_width="wrap_content"
        android:layout_height="32dp"
        android:layout_below="@id/img_meizi"
        android:gravity="center"
        android:text="色调    :"
        android:textSize="18sp" />

    <SeekBar
        android:id="@+id/sb_hue"
        android:layout_width="match_parent"
        android:layout_height="32dp"
        android:layout_below="@id/img_meizi"
        android:layout_toRightOf="@id/txt_hue" />


    <TextView
        android:id="@+id/txt_saturation"
        android:layout_width="wrap_content"
        android:layout_height="32dp"
        android:layout_below="@id/txt_hue"
        android:gravity="center"
        android:text="饱和度:"
        android:textSize="18sp" />

    <SeekBar
        android:id="@+id/sb_saturation"
        android:layout_width="match_parent"
        android:layout_height="32dp"
        android:layout_below="@id/sb_hue"
        android:layout_toRightOf="@id/txt_saturation" />


    <TextView
        android:id="@+id/txt_lun"
        android:layout_width="wrap_content"
        android:layout_height="32dp"
        android:layout_below="@id/txt_saturation"
        android:gravity="center"
        android:text="亮度    :"
        android:textSize="18sp" />

    <SeekBar
        android:id="@+id/sb_lum"
        android:layout_width="match_parent"
        android:layout_height="32dp"
        android:layout_below="@id/sb_saturation"
        android:layout_toRightOf="@id/txt_lun" />

</RelativeLayout>

Finally, ourMainActivity.java:

public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener{

    private ImageView img_meizi;
    private SeekBar sb_hue;
    private SeekBar sb_saturation;
    private SeekBar sb_lum;
    private final static int MAX_VALUE = 255;
    private final static int MID_VALUE = 127;
    private float mHue = 0.0f;
    private float mStauration = 1.0f;
    private float mLum = 1.0f;
    private Bitmap mBitmap;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_meizi);
        bindViews();
    }

    private void bindViews() {
        img_meizi = (ImageView) findViewById(R.id.img_meizi);
        sb_hue = (SeekBar) findViewById(R.id.sb_hue);
        sb_saturation = (SeekBar) findViewById(R.id.sb_saturation);
        sb_lum = (SeekBar) findViewById(R.id.sb_lum);

        img_meizi.setImageBitmap(mBitmap);
        sb_hue.setMax(MAX_VALUE);
        sb_hue.setProgress(MID_VALUE);
        sb_saturation.setMax(MAX_VALUE);
        sb_saturation.setProgress(MID_VALUE);
        sb_lum.setMax(MAX_VALUE);
        sb_lum.setProgress(MID_VALUE);

        sb_hue.setOnSeekBarChangeListener(this);
        sb_saturation.setOnSeekBarChangeListener(this);
        sb_lum.setOnSeekBarChangeListener(this);

    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        switch (seekBar.getId()) {
            case R.id.sb_hue:
                mHue = (progress - MID_VALUE) * 1.0F / MID_VALUE * 180;
                break;
            case R.id.sb_saturation:
                mStauration = progress * 1.0F / MID_VALUE;
                break;
            case R.id.sb_lum:
                mLum = progress * 1.0F / MID_VALUE;
                break;
        }
        img_meizi.setImageBitmap(ImageHelper.handleImageEffect(mBitmap, mHue, mStauration, mLum));
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {}

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {}
}

The code is also very simple, so we won't explain it here.


5. Download code examples for this section:

ColorMatrixDemo.zip

ColorMatrixDemo2.zip


Summary of this section:

Well, in this section we introducedColorFilterthe first one inColorMatrixColorFilter, the color matrix filter. Actually the core is still ColorMatrix. With this class, we can process images by setting the values of the 4*5 matrix ourselves, or directly call the methods provided by ColorMatrix to set hue, saturation, and brightness. Image processing is basically like this; there is another form that modifies pixel points, which will be covered later. This section's content is referenced from Doctor (Xu Yisheng)'s Muke.com video:Android Image Processing - Starting from It to Build Meitu Xiuxiu, if you don't want to read the text, you can watch the video; it's quite good.