天天看點

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

一、前期基礎知識儲備

Bitmap(位圖檔案),擴充名可以是.bmp或者.dib。位圖是Windows标準格式圖形檔案,它将圖像定義為由點(像素)組成,每個點可以由多種色彩表示,包括2、4、8、16、24和32位色彩。想象一下你以前測紅綠色盲的時候,王醫生拿給你看的那張紅紅綠綠小動物數字交通工具的圖,那就是一個位圖圖檔,由一個個像素組成。

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解
Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

一個位圖圖像的一個像素點,用RGBA四個值來描述,具體控制是通過一個4*5的矩陣來控制,每個像素點的RGBA又共同組成了位圖的展現形式,這就意味着隻要我們能控制像素點的RGBA值,就可以改變位圖展示的效果。

基于此,Android中常用的對于圖像色彩處理的常用方式有兩種:

①使用ColorMatrix類來改變RGBA;

②使用專業的像素點算法來定點來改變RGBA。

二、上代碼,具體實作通過ColorMatrix類進行色彩處理

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

我們需要知道在通常的色彩進行中,我們使用以下的專業詞彙來描述一個圖像:

色調——物體傳播的顔色;

飽和度——顔色的純度,從0(灰)到100%(飽和)來進行描述;

亮度——顔色的相對明暗程度;

在Android中,系統使用一個顔色矩陣——ColorMatrix,來處理圖像的這些色彩效果。Android中的顔色矩陣是一個4×5的數字矩陣,它用來對圖檔的色彩進行處理。而對于每個像素點,都有一個顔色分量矩陣用來儲存顔色的RGBA值,如下圖所示:

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

在這個4×5的顔色矩陣中按以下方式劃分:

第一行的abcde值用來決定新的顔色值中的R——紅色

第二行的fghij值用來決定新的顔色值中的G——綠色

第三行的klmno值用來決定新的顔色值中的B——藍色

第四行的pqrst值用來決定新的顔色值中的A——透明度

矩陣A中的第五列——ejot值分别用來決定每個分量重的offset,即偏移量。

1)色彩矩陣變化舉例:

①改變偏移量

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

在這個矩陣中修改了R,G所對應的顔色偏移量,那麼最後的處理結果就是圖像的紅色,綠色分量增加了100。而我們知道,紅色混合綠色會得到黃色,是以使得整個圖像的色調偏黃色。

②改變顔色系數

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

在這個矩陣中,改變了G分量所對應的系數g,這樣的矩陣運算後G分量會變成以前的兩倍,最終效果就是圖像的色調更加偏綠。

2)使用ColorMatrix類來實作色彩處理:

圖像的色調,飽和度,亮度這三個屬性在圖像進行中的使用非常多,是以顔色矩陣中,也封裝了一些API來快速調用這些參數,而不用每次都去計算矩陣的值。

在Android中,系統封裝了一個類——ColorMatrix,也就是說前面的顔色矩陣。通過這個類,可以很友善地改變矩陣值來處理顔色效果。官方文檔中給出了三種ColorMatrix的構造方法:

ColorMatrix()
Create a new colormatrix initialized to identity (as if reset() had been called).
ColorMatrix(float[] src)
Create a new colormatrix initialized with the specified array of values.
ColorMatrix(ColorMatrix src)
Create a new colormatrix initialized with the specified colormatrix.
//通常情況下使用第一種即可,即ColorMatrix colorMatrix = new ColorMatrix();
           

①對于色調的處理,色調是傳播出的顔色,是以RGB三個值都可以進行處理,Android系統提供了setRotate(int axis, float degree)來幫助我們設定顔色的色調。第一個參數,系統分别使用0、1、2來代表Red、Green、Blue三種顔色的處理;而第二個參數,就是需要處理的值,代碼如下:

ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix .setRotate(0,hue0);
hueMatrix .setRotate(1,hue1);
hueMatrix .setRotate(2,hue2);
           

②對于飽和度的處理,Android系統提供了setSaturation(float sat)方法來設定顔色的飽和度,參數代表設定顔色飽和度的值,當飽和度為0時,圖像就變成灰階圖像了,代碼如下:

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

③對于亮度的處理,當三原色以相同的比例進行混合的時候,就會顯示出白色,系統正式使用這個原理來改變一個圖像的亮度的,代碼如下,當亮度為0時,圖像就變成全黑了,處理的代碼如下:

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

④色彩的混合處理,除了單獨使用上面三種方式來進行顔色效果的處理之外,Android系統還封裝了矩陣的乘法運算。它提供了postConcat()方法來将矩陣的作用效果混合,進而疊加處理效果,代碼如下:

//将矩陣的作用效果混合,進而疊加處理效果
ColorMatrix imageMatrix=new ColorMatrix();
imageMatrix.posConcat(hueMatrix);
imageMatrix.posConcat(saturationMatrix);
imageMatrix.posConcat(lumMatrix);

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

最後通過paint.setColorFilter(newColorMatrixColorFilter(imageMatrix))設定給paint,并使用這個畫筆來繪制原來的圖像,進而将顔色矩陣作用到原圖上,代碼如下:

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

下面給出《Android群英傳》中通過SeekBar調整色彩的經典代碼段:

public class MainActivity extends AppCompatActivity  implements SeekBar.OnSeekBarChangeListener{
    private static final int MAX_VALUE = 255;
    private static final int MID_VALUE = 127;

    private ImageView mImageView;
    private float mHue, mSaturation, mLum;
    private Bitmap mBitmap;

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

        mImageView = (ImageView) findViewById(R.id.imageview);
        SeekBar seekBarHue = (SeekBar) findViewById(R.id.seekbarHue);
        SeekBar seekBarSaturation = (SeekBar) findViewById(R.id.seekbarSaturation);
        SeekBar seekBarLum = (SeekBar) findViewById(R.id.seekbarLum);

        seekBarHue.setOnSeekBarChangeListener(this);
        seekBarSaturation.setOnSeekBarChangeListener(this);
        seekBarLum.setOnSeekBarChangeListener(this);

        //設定最大值
        seekBarHue.setMax(MAX_VALUE);
        seekBarSaturation.setMax(MAX_VALUE);
        seekBarLum.setMax(MAX_VALUE);

        //設定進度
        seekBarHue.setProgress(MID_VALUE);
        seekBarSaturation.setProgress(MID_VALUE);
        seekBarLum.setProgress(MID_VALUE);

        mImageView.setImageBitmap(mBitmap);

    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        switch (seekBar.getId()) {
            case R.id.seekbarHue://色調
                mHue = (progress - MID_VALUE) * 1.0f / MID_VALUE * 180;
                break;
            case R.id.seekbarSaturation://飽和度
                mSaturation = progress * 1.0f / MID_VALUE;
                break;
            case R.id.seekbarLum://亮度
                mLum = progress * 1.0f / MID_VALUE;
                break;
        }
        mImageView.setImageBitmap(ImageHelper.handleImageEffect(mBitmap, mHue, mSaturation, mLum));

    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
}
           
public class ImageHelper {
    public static Bitmap handleImageEffect(Bitmap bm, float hue, float saturation, float lum) {
        //設定顔色的色調
        ColorMatrix hueMatrix = new ColorMatrix();
        //第一個參數,系統分别使用0、1、2來代表Red、Green、Blue三種顔色的處理;而第二個參數,就是需要處理的值
        hueMatrix.setRotate(0, hue);
        hueMatrix.setRotate(1, hue);
        hueMatrix.setRotate(2, hue);

        //設定顔色的飽和度
        ColorMatrix saturationMatrix = new ColorMatrix();
        //saturation參數即代表設定顔色的飽和度的值,當飽和度為0時,圖像就變成灰階圖像了
        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()方法,将通過imageMatrix構造的
         * ColorMatrixColorFilter對象傳遞進去,并使用這個畫筆來繪制原來的圖像,進而将顔色矩陣作用到原圖中
         */
        Paint paint = new Paint();
        paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
        /**
         * Android系統也不允許直接修改原圖,類似Photoshop中的鎖定,必須通過原圖建立一個同樣大小的Bitmap,并将
         * 原圖繪制到該Bitmap中,以一個副本的形式來修改圖像。
         */
        Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(bm, 0, 0 ,paint);
        return bitmap;
    }
}
           

三、上代碼,具體實作通過像素點算法進行色彩處理

Android色彩特效處理之色調、飽和度、亮度、ColorMatrix精煉詳解

作為更加精确的圖像處理方式,可以通過改變每個像素點的具體RGBA值,來達到處理一張圖像效果的目的。這裡要注意的是傳遞進來的原始圖檔是不能修改的,一般根據原始圖檔生成一張新的圖檔來進行修改。

在Android中,系統提供了Bitmap.getPixels()方法來幫我們提取整個Bitmap中的像素點,并儲存到一個數組中:

bitmap.getPixels(pixels,offset,stride,x,y,width,height);
           

這幾個參數的含義如下:

pixels——接收位圖顔色值的數組

offset——寫入到pixels[]中的第一個像素索引值

stride——pixels[]中的行間距

x——從位圖中讀取的第一個像素的x坐标值

y——從位圖中讀取的第一個像素的y坐标值

width——從每一行中讀取的像素寬度

height——讀取的行數

通常情況下,可以使用如下代碼:

bitmap.getPixels(oldPx,0,bm.getWidth(),0,0,width,height);
           

接下來就可以擷取每個像素具體的ARGB了,如下:

color=oldPx[i];
r=Color.red(color);
g=Color.green(color);
b=Color.blue(color);
a=Color.alpha(color);
           

當擷取到具體的顔色值後,就可以通過相應的算法來修改它的ARGB值。如下老照片效果效果:

r1=(int)(0.393*r+0.769*g+0.189*b);
g1=(int)(0.349*r+0.686*g+0.168*b);
b1=(int)(0.272*r+0.534*g+0.131*b);
           

再通過如下代碼将新的RGBA值合成像素點:

newPx[i]=Color.argb(a,r1,g1,b1);
           

最後通過如下代碼,将處理後的像素點數組重新set給我們的Bitmap,進而達到圖像處理的目的:

bitmap.setPixels(newPx,0,width,0,0,width,height);
           

下面以《Android群英傳》中底片效果為例,具體實作:

public static Bitmap handleImageNegative(Bitmap bm){
    int width = bm.getWidth();
    int height - bm.getHeight();
    int color;
    int r,g,b,a;

    Bitmap bmp=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);

    int[]oldPx=new int[width * height];
    int[]newPx=new int[width * height];
    bm.getPixels(oldPx,0,width,0,0,width,height);

    for(int i=0;i<width * height;i++){
        color=oldPx[i];
        r=Color.red(color);
        g=Color.green(color);
        b=Color.blue(color);
        a=Color.alpha(color);
        //
        r=255-r;
        g=255-g;
        b=255-b;

        if(r>255){
            r=255;
        }else if(r<0){
            r=0;
        }   
        if(g>255){
            g=255;
        }else if(g<0){
            g=0;
        }
        if(b>255){
            b=255;
        }else if(b<0){
            b=0;
        }
        newPx[i]=Color.argb(a,r,g,b);
    }
    bmp.setPixels(newPx,0,width,0,0,widht,height);
    return bmp;
}
           

繼續閱讀