顏色矩陣原理
色彩的三要素
1、色相。色相通俗的說就是“顏色”,色相的改變就是顏色的改變,色相的調節伴隨着紅橙黃綠藍紫的變化。2、亮度。明度通俗的說就是“光照度”,明度的改變就是光照在物體上帶來的改變,明度的調節伴隨着越高,光越強,越泛白(就像過曝一樣,往白色上偏離);越低,光越弱,越往黑里偏3、飽和度。飽和度通俗的說就是“色彩的純度”,飽和度的改變會影響顏色的鮮艷程度,以紅色為例子,越高,越接近紅色,越低則越接近灰色(黑白)
在編程中有時候需要對圖片做特殊的處理,比如將圖片做出黑白的,或者老照片的效果,有時候還要對圖片進行變換,以拉伸,扭曲等等。 這些效果在android中有很好的支持,通過顏色矩陣 ColorMatrix 和坐標變換矩陣 Matrix 可以完美的做出上面的所說的效果。
android中可以通過顏色矩陣 ColorMatrix 方便的操作顏色,顏色矩陣是一個5x4 的矩陣
第一行決定紅色、第二行決定綠色、第三行決定藍色、第四行決定了透明度。第五列是顏色的偏移量。 顏色矩陣以一維數組的方式存儲如下: [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ] 他通過RGBA四個通道來直接操作對應顏色,如果會使用Photoshop就會知道有時處理圖片通過控制RGBA各顏色通道來做出特殊的效果。而圖像的RGBA值則存儲在一個5*1的顏色分量矩陣C中,由顏色分量矩陣C可以控制圖像的顏色效果
要想改變一張圖片的顏色效果,只需要改變圖像的顏色分量矩陣即可。通過顏色矩陣可以很方便的修改圖像的顏色分量矩陣。假設修改后的圖像顏色分量矩陣為C1,則有如下圖所示的顏色分量矩陣計算公式。
矩陣的運算規則是:矩陣M的一行乘以矩陣C的一列作為矩陣R的一行。 M是顏色矩陣,C矩陣是圖片中包含的RGBA信息,R矩陣是用M應用於C之后的新的顏色分量。由此可見,通過顏色矩陣修改了原圖像的RGBA值,從而達到了改變圖片顏色效果的目的。並且,通過上圖所示的運算可知,顏色矩陣M的第一行參數abcde決定了圖像的紅色成分,第二行參數fghij決定了圖像的綠色成分,第三行參數klmno決定了圖像的藍色成分,第四行參數pqrst決定了圖像的透明度,第五列參數ejot是顏色的偏移量。
通常,改變顏色分量時可以通過修改第5列的顏色偏移量來實現,如下圖所示的顏色矩陣M1,通過計算后可以得知該顏色矩陣的作用是使圖像的紅色分量和綠色分量均增加100,這樣的效果就是圖片泛黃(因為紅色與綠色混合后得到黃色)。
除此之外,也可以通過直接對顏色值乘以某一系數而達到改變顏色分量的目的。如下圖所示的顏色矩陣M2,將綠色分量放大了2倍,這樣的效果就是圖片泛綠色。
效果演示
調色器代碼
public class MainActivity extends Activity implements OnSeekBarChangeListener, OnClickListener {private ImageView imageView;private SeekBar sb_red, sb_green, sb_blue, sb_brightness, sb_saturation;private Bitmap preBitmap, afterBitmap;private Canvas canvas;private ColorMatrix colorMatrix, rgbMatrix, huduMatrix;private Paint paint;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);imageView = (ImageView) findViewById(R.id.iv);findViewById(R.id.btn_select).setOnClickListener(this);findViewById(R.id.btn_save).setOnClickListener(this);sb_red = (SeekBar) findViewById(R.id.sb_red);sb_green = (SeekBar) findViewById(R.id.sb_green);sb_blue = (SeekBar) findViewById(R.id.sb_blue);sb_brightness = (SeekBar) findViewById(R.id.sb_brightness);sb_saturation = (SeekBar) findViewById(R.id.sb_saturation);sb_red.setOnSeekBarChangeListener(this);sb_green.setOnSeekBarChangeListener(this);sb_blue.setOnSeekBarChangeListener(this);sb_brightness.setOnSeekBarChangeListener(this);sb_saturation.setOnSeekBarChangeListener(this);paint = new Paint();colorMatrix = new ColorMatrix();//5*4的顏色矩陣rgbMatrix = new ColorMatrix();//RGB顏色及亮度huduMatrix = new ColorMatrix();//飽和度或灰度}
@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {//進度改變時調用if (preBitmap != null) {float progressR = sb_red.getProgress() / 128f;//這里處理后值的范圍為[0,2],初始值是1,也即初始時不做任何改變。float progressG = sb_green.getProgress() / 128f;float progressB = sb_blue.getProgress() / 128f;float progressA = sb_brightness.getProgress() / 128f;float progressS = sb_saturation.getProgress() / 128f;float[] values = new float[] { progressR, 0, 0, 0, 0,//0, progressG, 0, 0, 0,//0, 0, progressB, 0, 0,//0, 0, 0, progressA, 0 };////1、改變色相及亮度rgbMatrix.set(values);//2、改變飽和度、透明度、灰度。當飽和度=0時,會變成黑白圖片(有灰度的黑白照片)。huduMatrix.setSaturation(progressS);//3、要想將色彩三元素綜合運用到一張圖片上,需要通過顏色矩陣的postConcat方法將三元素進行連接。colorMatrix.reset();//要重置一下才可以,不然等於是在之前的基礎上進行的更改colorMatrix.postConcat(rgbMatrix); //這里其實是將兩個矩陣進行了運算,熟悉矩陣運算的應該知道,先后順序是有重大影響的colorMatrix.postConcat(huduMatrix);//4、通過顏色濾鏡將顏色矩陣應用於圖片上paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));canvas.drawBitmap(preBitmap, new Matrix(), paint);imageView.setImageBitmap(afterBitmap);} else Toast.makeText(getApplicationContext(), "清先選擇一張圖片再調整顏色", 0).show();}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {//開始拖動時調用}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {//結束拖動時調用}
@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_select: // 激活系統圖庫,選擇一張圖片Intent intent = new Intent();intent.setAction(Intent.ACTION_PICK);intent.setType("image/*");startActivityForResult(intent, 0);break;case R.id.btn_save: //保存bitmap為圖片File file = new File(Environment.getExternalStorageDirectory(), new SimpleDateFormat("yyyy.MM.dd HH-mm-ss", Locale. getDefault ()).format( new Date()) + ".png" );try {FileOutputStream out = new FileOutputStream(file);afterBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);out.close();} catch (Exception e) {Toast.makeText(this, "保存出現異常", Toast.LENGTH_SHORT).show();e.printStackTrace();}break;}}
//返回一張照片后初始化afterBitmap@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (data != null && data.getData() != null) {try {Uri uri = data.getData();// 得到圖片的全路徑。相機目錄下的某個文件的路徑為:content://media/external/images/media/12718Log.i("bqt", "路徑為:" + uri);//SD卡根目錄下的文某個文件的件路徑為:content://media/external/images/media/12758InputStream is = getContentResolver().openInputStream(uri);//獲取文件的流preBitmap = BitmapFactory.decodeStream(is); //通過流加載圖片} catch (FileNotFoundException e) {}// 創建一張可以被修改的空白圖片afterBitmap = Bitmap.createBitmap(preBitmap.getWidth(), preBitmap.getHeight(), preBitmap.getConfig());canvas = new Canvas(afterBitmap);canvas.drawBitmap(preBitmap, new Matrix(), paint);//先把原圖繪制上去,目的僅僅是讓你預覽一下imageView.setImageBitmap(afterBitmap);//注意imageView上顯示的不是我們本地的圖片,而是我們內存中剛剛new的Bitmap}super.onActivityResult(requestCode, resultCode, data);}}
調色器布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical" ><SeekBarandroid:id="@+id/sb_red"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="255"android:progress="128" /><SeekBarandroid:id="@+id/sb_green"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="255"android:progress="128" /><SeekBarandroid:id="@+id/sb_blue"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="255"android:progress="128" /><SeekBarandroid:id="@+id/sb_brightness"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="255"android:progress="128" /><SeekBarandroid:id="@+id/sb_saturation"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="255"android:progress="128" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal" ><Buttonandroid:id="@+id/btn_select"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="選擇一張照片" /><Buttonandroid:id="@+id/btn_save"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="保存圖片到SD卡" /></LinearLayout><ImageViewandroid:id="@+id/iv"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>
顏色矩陣演示
顏色矩陣代碼
public class MainActivity extends Activity implements OnClickListener {private Paint myPaint;private Bitmap bitmap, alterBitmap;private Canvas canvas;private ColorMatrix myColorMatrix;private ImageView iv;private EditText[] editTextArray = new EditText[20];private float[] colorArray = new float[20];private int[] EditTextID = { R.id.Edit1, R.id.Edit2, R.id.Edit3, R.id.Edit4, R.id.Edit5, R.id.Edit6, R.id.Edit7, R.id.Edit8,R.id.Edit9, R.id.Edit10, R.id.Edit11, R.id.Edit12, R.id.Edit13, R.id.Edit14, R.id.Edit15, R.id.Edit16, R.id.Edit17,R.id.Edit18, R.id.Edit19, R.id.Edit20 };@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.Button).setOnClickListener(this);iv = (ImageView) findViewById(R.id.iv);for (int i = 0; i < 20; i++) {editTextArray[i] = (EditText) findViewById(EditTextID[i]);}myPaint = new Paint();myColorMatrix = new ColorMatrix();bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a);iv.setImageBitmap(bitmap);//先用舊圖裝飾一下門面}
@Overridepublic void onClick(View v) {for (int i = 0; i < 20; i++) {String num = editTextArray[i].getText().toString().trim();if (TextUtils.isEmpty(num)) colorArray[i] = 0;else colorArray[i] = Float.valueOf(num);}//每提交一次都要重新設置以下alterBitmap,不然等於是在上次的基礎上修改了alterBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());canvas = new Canvas(alterBitmap);//必須先把原圖畫上去,然后才能通過顏色矩陣修改圖片的顏色canvas.drawBitmap(bitmap, new Matrix(), new Paint());//注意這里的Paint不能直接使用myPaint,否則會重復繪制顏色濾鏡。當然使用之前重置一下就可以用了。//設置顏色矩陣myColorMatrix.set(colorArray);myPaint.setColorFilter(new ColorMatrixColorFilter(myColorMatrix));canvas.drawBitmap(alterBitmap, 0, 0, myPaint);iv.setImageBitmap(alterBitmap);}}
顏色矩陣布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent"android:orientation="vertical" ><ImageViewandroid:id="@+id/iv"android:layout_width="match_parent"android:layout_height="200dp"android:layout_gravity="center" /><Buttonandroid:id="@+id/Button"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="提交" /><LinearLayoutandroid:id="@+id/colorlayout1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="horizontal" ><EditTextandroid:id="@+id/Edit1"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal"android:text="1" /><EditTextandroid:id="@+id/Edit2"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit3"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit4"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit5"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /></LinearLayout><LinearLayoutandroid:id="@+id/colorlayout2"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="horizontal" ><EditTextandroid:id="@+id/Edit6"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit7"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal"android:text="1" /><EditTextandroid:id="@+id/Edit8"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit9"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit10"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /></LinearLayout><LinearLayoutandroid:id="@+id/colorlayout3"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="horizontal" ><EditTextandroid:id="@+id/Edit11"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit12"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit13"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal"android:text="1" /><EditTextandroid:id="@+id/Edit14"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit15"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /></LinearLayout><LinearLayoutandroid:id="@+id/colorlayout4"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="horizontal" ><EditTextandroid:id="@+id/Edit16"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit17"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit18"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /><EditTextandroid:id="@+id/Edit19"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal"android:text="1" /><EditTextandroid:id="@+id/Edit20"android:layout_width="50dp"android:layout_height="40dp"android:layout_weight="1"android:numeric="decimal" /></LinearLayout></LinearLayout>