一、前期基礎知識儲備
Bitmap(位圖檔案),擴充名可以是.bmp或者.dib。位圖是Windows标準格式圖形檔案,它将圖像定義為由點(像素)組成,每個點可以由多種色彩表示,包括2、4、8、16、24和32位色彩。想象一下你以前測紅綠色盲的時候,王醫生拿給你看的那張紅紅綠綠小動物數字交通工具的圖,那就是一個位圖圖檔,由一個個像素組成。
一個位圖圖像的一個像素點,用RGBA四個值來描述,具體控制是通過一個4*5的矩陣來控制,每個像素點的RGBA又共同組成了位圖的展現形式,這就意味着隻要我們能控制像素點的RGBA值,就可以改變位圖展示的效果。
基于此,Android中常用的對于圖像色彩處理的常用方式有兩種:
①使用ColorMatrix類來改變RGBA;
②使用專業的像素點算法來定點來改變RGBA。
二、上代碼,具體實作通過ColorMatrix類進行色彩處理
我們需要知道在通常的色彩進行中,我們使用以下的專業詞彙來描述一個圖像:
色調——物體傳播的顔色;
飽和度——顔色的純度,從0(灰)到100%(飽和)來進行描述;
亮度——顔色的相對明暗程度;
在Android中,系統使用一個顔色矩陣——ColorMatrix,來處理圖像的這些色彩效果。Android中的顔色矩陣是一個4×5的數字矩陣,它用來對圖檔的色彩進行處理。而對于每個像素點,都有一個顔色分量矩陣用來儲存顔色的RGBA值,如下圖所示:
在這個4×5的顔色矩陣中按以下方式劃分:
第一行的abcde值用來決定新的顔色值中的R——紅色
第二行的fghij值用來決定新的顔色值中的G——綠色
第三行的klmno值用來決定新的顔色值中的B——藍色
第四行的pqrst值用來決定新的顔色值中的A——透明度
矩陣A中的第五列——ejot值分别用來決定每個分量重的offset,即偏移量。
1)色彩矩陣變化舉例:
①改變偏移量
在這個矩陣中修改了R,G所對應的顔色偏移量,那麼最後的處理結果就是圖像的紅色,綠色分量增加了100。而我們知道,紅色混合綠色會得到黃色,是以使得整個圖像的色調偏黃色。
②改變顔色系數
在這個矩陣中,改變了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;
}
}
三、上代碼,具體實作通過像素點算法進行色彩處理
作為更加精确的圖像處理方式,可以通過改變每個像素點的具體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;
}