မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမမ်ား မဂၤလာပါဗ်ာ။ ဒီေန႔ေတာ့ မေန႔က က်ေနာ္တို႔ ေလ့လာခဲ့တဲ့ Lockbits( ) and Marshal.Copy( ) Method သင္ခန္းစာေလးကို နည္းနည္းေလး ျပဳျပင္ၿပီး ထပ္ေလ့လာၾကည့္ပါ့မရ္။ Processing အတြက္ကိုေတာ့ ေရွ႕သင္ခန္းစာမွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့ Image Color Channel ခြဲထုတ္တာကိုပဲ သံုးထားပါတရ္။ Processing Time ကေတာ့ ေရွ႕ Version ထက္စာရင္ အနည္းငရ္ ေလွ်ာ့ခ်ေပးႏိုင္ပါတရ္။ ေရွ႕သင္ခန္းစာမွာတုန္းက Image ကို Scan ဖတ္လို႔ရတဲ့ Data ေတြကို byte[ ]Array ထဲထည့္တာဟာ Processing လုပ္ရလြယ္ေအာင္ 2-Dimension ကေန 1-Dimension သို႔ ေျပာင္းလဲတာပါလို႔ ေျပာခဲ့ပါတရ္။ ဒါေပမဲ့ Color Channel ေျပာင္းလဲယူတဲ့ ေနရာမွာေတာ့ 2-Dimension ပံု Stride by Stride အလုပ္လုပ္ၿပီး row ကိုဖမ္းကာ Processing လုပ္တဲ့ ပံုစံနဲ႔ရွင္းခဲ့ပါတရ္။ ဒါကလည္း ပံုနဲ႔အတူ ရွင္းျပထားတဲ့ LockBits( )Method ရဲ႕ 1 Pixel မွာ ARGB အလုပ္လုပ္သြားပံုကို ထင္သာျမင္ေအာင္ ရွိေအာင္ပါ။ ဒါေၾကာင့္ For Loop တစ္ခုအတြက္ Processing Time ပိုၾကာသြားေစပါတရ္။ ဒီေန႔သင္ခန္းစာမွာေတာ့ ဒါေလးကို ျပဳျပင္ၿပီး ထက္ေလ့လာၾကည့္မွာျဖစ္ပါတရ္။ အလုပ္လုပ္ပံု ေက်ာရိုးကေတာ့ အတူတူပါပဲဗ်ာ။
က်ေနာ္ GUI ေတြနဲ႔ Tool properties ေတြအေၾကာင္း မရွင္းျပေတာ့ပါဘူး။ ေရွ႕သင္ခန္းစာအတုိင္းျဖစ္လို႔ ျပန္လည္ ေလ့လာႏိုင္ပါတရ္ဗ်။ Processing Method နဲ႔ Method Calling ပံုစံကိုပဲ ရွင္းျပပါေတာ့မရ္။ ဒါကေတာ့ က်ေနာ္တုိ႔ Project မွာ အသံုးျပဳမရ္႕ Library File ေတြပါ။ ေအာက္က ကုဒ္ေလးကေတာ့ Lockbits( ) and Marshal.Copy( ) Method ကိုအသံုးျပဳၿပီး Color Channel ကို ေျပာင္းလဲယူတဲ့ ကုဒ္ေလးပါ။ ရွင္းျပရမရ္ဆိုရင္ေတာ့...
1. Load Image to Bitmap
Bitmap cloneImg = new Bitmap(inputImg);
ပထမအဆင့္မွာ Image တစ္ခုကို Bitmap ထဲသို႔ ဆြဲတင္တာျဖစ္ပါတရ္။

2. Lock Bitmap in Rectangle by BitmapData
Rectangle ret = new Rectangle(0, 0, cloneImg.Width, cloneImg.Height);
BitmapData bmpData = cloneImg.LockBits(ret, ImageLockMode.ReadWrite, cloneImg.PixelFormat);
ဒီအဆင့္မွာေတာ့ LockBits() Method ကိုအသံုးျပဳၿပီး Bitmap Image တစ္ခုကို Lock ခ်တာျဖစ္ပါတရ္။ စစခ်င္း inputImg ရဲ႕ Width and Height အတိုင္း Rectangle တစ္ခုကို တည္ေဆာက္ပါတရ္။ ေနာက္တစ္ဆင့္မွာေတာ့ ၄င္း Rectangle ထဲသုိ႔ Image ကို Lock ခ်ပါတရ္။ ဒီမွာ LockBits() Method ကို Parameter(၃)ခုသံုးထားပါတရ္။ ပထမ Parameter က Destination Rectangle ျဖစ္ပါတရ္။ ဒုတိယ parameter ကေတာ့ LockMode ပါ။ ဒီမွာ မိတ္ေဆြတို႔အတြက္ ေရြးခ်ယ္စရာ properties ေတြ အမ်ားႀကီးရွိပါတရ္။ ဥပမာအေနနဲ႔ read အတြက္ bitmap တစ္ခု၊ write အတြက္ bitmap တစ္ခုအေနနဲ႔ ခြဲသံုးႏိုင္ပါတရ္။ ဒီေလ့က်င့္ခန္းမွာ က်ေနာ္ကေတာ့ Bitmap တစ္ခုတည္းကိုပဲ Read, Write လုပ္ၿပီး Return ျပန္ေပးမွာျဖစ္လို႔ LockMode ကို ReadWrite သံုးထားတာပါ။ ေနာက္ဆံုး တတိယ parameter ကေတာ့ pixel format သတ္မွတ္ေပးတာပါ။ တနည္းအားျဖင့္ Image တစ္ခုကို 8bpp, 16bpp, 24bpp, 32pbb,... အစရွိတဲ့ Bits Per Pixel သတ္မွတ္တာပါ။ PixelFormat.Format24bppRgb အခုလို ပုံစံမ်ိဳးနဲ႔လည္း မိမိႏွစ္သတ္ရာ Format ကုိ ေရြးခ်ယ္ၿပီး lock လုပ္ႏိုင္ပါတရ္။ ဘာမွ မေရြးခ်ယ္ပဲ inputImg ရဲ႕ Pixel Format အတိုင္း lock လုပ္ခ်င္တရ္ဆိုရင္ေတာ့ inputImg.PixelFormat ဆိုၿပီး ေပးႏိုင္ပါတရ္။

3. Calculate PixelFormat(BytesPerPixel) and TotleByte
int bytePP = Bitmap.GetPixelFormatSize(cloneImg.PixelFormat) /8;
ဒီမွာေတာ့ pixel တစ္ခုစီတိုင္းမွာ အသံုးျပဳမရ္ Byte အေရအတြက္ကို တြက္ယူပါတရ္။ 8 နဲ႔စားထားတာကေတာ့ 8bits = 1byte ျဖစ္လို႔ပါ။ ဥပမာ- က်ေနာ္တို႔က 24bppRgb PixelFormat ကိုေရြးမိရင္ 24/8=3 ျဖစ္တဲ့အတြက္ 1 pixel မွာ 3 byte ေနရာယူ အလုပ္ လုပ္ေနမွာပါ။ Blue = 1byte(8bits), Green = 1byte(8bits) and Red = 1byte(8bits) ကိုယ္စီနဲ႔ အလုပ္ လုပ္မွာပါ။ ဒါမွမဟုတ္ 32bppArgb PixelFormat ကုိ ေရြးမိရင္ေတာ့ 32/8=4 ျဖစ္တဲ့အတြက္ pixel တစ္ကြက္မွာ 4 byte ေနရာယူ ေနမွာပါ။ ဒါဆိုရင္ေတာ့ Alpha = 1byte(8bits), Blue = 1byte(8bits), Green = 1byte(8bits) and Red = 1byte(8bits) ကိုယ္စီနဲ႔ အလုပ္ လုပ္သြားမွာ ျဖစ္ပါတရ္။ နားလည္မရ္လို႔ ထင္ပါတရ္ဗ်။

4. Calculate totalBytes and Create byte Array for Image's Pixel
int totleByte = bmpData.Stride * bmpData.Height;
byte[] pixels = new byte[totleByte];
ပထမကေတာ့ Processing လုပ္မရ္႕ Image တစ္ခုလံုးမွာရွိတဲ့ totalByte ကိုရွာတာပါ။ ဒီမွာေတာ့ Stride ဆိုတာကို သိဖို႔လိုလာပါၿပီ။ အေပၚပံုမွာ ျမင္ရတဲ့အတိုင္း row တစ္ row လံုးမွာ ရွိတဲ့ byte အရည္အတြက္ဟာ Stride ပါပဲ။ Stride = bytePP * inputImg.Width; ပါ။ bytePP က Step:3 မွာရွာခဲ့ၿပီးပါၿပီ 4 ဆိုပါစို႔။ ဥပမာ- က်ေနာ္တို႔ Processing လုပ္ေနတဲ့ Image က 5x5 resolution ပါ။ ဒီေတာ့ width = 5; height = 5; ေပါ။ ဒါဆို Stride = 4*5 = 20bytes ရရွိမွာပါ။ ဒါဆို Stride တစ္ခု (သို႔မဟုတ္) row တစ္ေၾကာင္းဟာ 20bytes ရွိေနတရ္လို႔ သိရပါ့မရ္။ ဒီေတာ့ Image တစ္ခုလံုးမွာ ရွိေနတဲ့ totalByte ကို သိဖို႔ မခက္ခဲေတာ့ပါဘူး။ ၄င္း Stride နဲ႔ Image ရဲ႕ Heigh နဲ႔ကို ေျမွာက္လိုက္ယံုပါပဲ။ ဒါဆို totalByte = 20 * 5 = 100bytes ပါ။ ဒုတိယကေတာ့ အေပၚမွာ က်ေနာ္တို႔ Processing လုပ္ဖို႔အတြက္ Image တစ္ခုလံုးမွာရွိတဲ့ Byte အေရအတြက္ကို ရၿပီဆိုရင္ေတာ့ ဒီအဆင့္မွာ ၄င္း Length အတိုင္း byte[ ] Array တစ္ခုကို ေဆာက္ယူတာျဖစ္ပါတရ္။ ေနာက္ပိုင္းမွာ ဒီ byte[] array ထဲကို Image ရဲ႕ Byte ေတြကို တန္းစီထည့္ပါ့မရ္။

5. Store Scaning Data in Pointer Memory
IntPtr ptrFirstPixel = bmpData.Scan0;
ဒီေကာင္ကေတာ့ lock ခ်ထားတဲ့ Image ႀကီးတစ္ခုလံုးကို Scan ဖတ္လိုက္တာပါ။ Scan0 ရဲ႕ သေဘာကေတာ့ Target Image ရဲ႕ left-top corner ကေနၿပီး right-buttom အထိ တစ္ဆင့္ခ်င္းစီ Scan ဖတ္သြားမွာပါ။ Direct Memory Access သေဘာ အလုပ္ လုပ္မွာ ျဖစ္တဲ့အတြက္ Memory Address ကိုဖတ္ကာ Int Pointer ထဲကို ေခတၱသိမ္းထားပါတရ္။ ေနာက္တစ္ဆင့္မွာေတာ့ Scan ဖတ္လို႔ရတဲ့ Data ေတြကို Processing လုပ္ဖို႔အတြက္ Step:4 မွာ ဖန္တီးခဲ့တဲ့ byte[] array ထဲကုိ ကူးထည့္ပါ့မရ္။

6. Copy Scaning Data to byte[ ] pixels
//Marshal.Copy(IntPtr source, byte[] destination, int startIndex, int length);
Marshal.Copy(ptrFirstPixel, pixels, 0, pixels.Length);
ဒီအဆင့္မွာေတာ့ Marshal.Copy()Method ကို အသံုးျပဳၿပီး Pointer ထဲက Scan ဖတ္ထားတဲ့ Data ေတြကို Processing လုပ္ဖို႔အတြက္ byte[] array ထဲကို ထည့္ပါတရ္။ Parameter ေလးခုကို Description ေတြနဲ႔အတူ က်ေနာ္ ျပထားပါတရ္။ ပထမ parameter က source ျဖစ္လို႔ Scan ဖတ္ထားတဲ့ Pointer Data ကိုေရြးေပးထားပါတရ္။ ဒုတိယ parameter က destination ပါ။ ဒါေၾကာင့္ သြားသိမ္းခ်င္တဲ့ byte[] array ကို ညႊန္းေပးထားပါတရ္။ တတိယ parameter ကေတာ့ byte[]array ထဲကို ဘရ္ Index က စထည့္မွာလဲဆိုတဲ့ Index ရဲ႕ ညႊန္းခ်က္ပါ။ က်ေနာ္တို႔ကေတာ့ Index 0 to Length အထိ ထည့္မွာျဖစ္လို႔ စမွတ္ကို 0 လို႔ ေပးထားပါတရ္။ ေနာက္ဆံုး parameter ကေတာ့ byte[]array ရဲ႕ length ပါ။ byte[]array ထဲကို Data ေတြသိမ္းၿပီး Processing လုပ္တာကေတာ့ ယခင္ 2-Dimension အလုပ္လုပ္ေနရာကေန ပိုမိုလြယ္ကူသြားေအာင္ 1-Dimension ေျပာင္းလိုက္တာပါ။ ဒီေတာ့ Scan ဖတ္ထားတဲ့ Data ေတြကို
byte[] pixels = byte[100];
pixels = {0, 1, 2,.......,100};
ပံုစံမ်ိဳး အလုပ္လုပ္သြားမွာပါ။ နားလည္မရ္လို႔ ထင္ပါတရ္ဗ်ာ။

7. Pre-Processing Get Pixel
for (int x = 0; x < pixels.Length; x = x + bytePP)
{
   //Drag and Mark old RGB Color
       int oldBlue = pixels[x];
          int oldGreen = pixels[x + 1];
   int oldRed = pixels[x + 2];

   // Processing Color Channel 
   // ---------             
}
Processing လုပ္သြားတဲ့ ပံုစံကေတာ့ က်ေနာ္တို႔ Data ေတြအားလံုး Store လုပ္ထားတဲ့ byte[ ]array ရဲ႕ first Index ကစၿပီး Last Index အထိ Loop ပါတ္ထားပါတရ္။ Loop တစ္ခါပါတ္တိုင္း BGRA ေလးလံုး တစ္ Group ၿပီး တစ္ Group ခုန္ကာ Color Detection လုပ္သြားတာပါ။ RGBA Color ေတြကို ဖမ္းသြားတာကလည္း ေရွ႕သင္ခန္းစာထက္စာရင္ ပိုလြယ္ကူသြားမရ္လို႔ ထင္ပါတရ္။ byte[]array ရဲ႕ Index ကိုပဲ Loop ဆံုးသည့္တိုင္ေအာင္ တိုက္ရိုက္ဆြဲတင္လိုက္တာပါ။ ဒီေလ့က်င့္ခန္းမွာေတာ့ Alpha တန္ဖိုးကို က်ေနာ္ ခ်န္လွန္ထားခဲ့ပါတရ္။ မိတ္ေဆြတို႔ Trace လုိက္ၾကည့္ရင္ေတာ့ Coding Flow ရဲ႕ အလုပ္လုပ္သြားပံုကို ေသခ်ာနားလည္ သြားမွာပါ။

8. Processing Color Channel
// Processing Color Channel 
switch (channel) {
     case "Red":
         pixels[x] = 0;
         pixels[x + 1] = 0;
         pixels[x + 2] = (byte)oldRed;
         break;
     case "Green":
         pixels[x] = 0;
         pixels[x + 1] = (byte)oldGreen;
         pixels[x + 2] = 0;
         break;
     case "Blue":
         pixels[x] = (byte)oldBlue;
         pixels[x + 1] = 0;
         pixels[x + 2] = 0;
         break;
     default:
         break;
}
ဒီေကာင္ကေတာ့ Color Channel အတြက္ Processing လုပ္ထားတာပါ။ ရိုးရိုးရွင္းရွင္းပါပဲ။ မိမိ ေျပာင္းလဲခ်င္ေသာ Color Channel ကိုခ်န္ၿပီး က်န္ Channel ေတြကို သုည(0) assign လုပ္လိုက္တာပါ။ နားလည္မရ္လို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။

9. Re-Enter modified bytes to Image's Pixels
//Marshal.Copy(byte[] source, int startIndex, IntPtr destination, int length);
Marshal.Copy(pixels, 0, ptrFirstPixel, pixels.Length);
ဒီေကာင္ကေတာ့ processing လုပ္ၿပီးသား Data ကို မူလေနရာျဖစ္တဲ့ Pointer ထဲကို ျပန္လည္ ထည့္သြင္းဖို႔အတြက္ သံုးထားတာပါ။ Description ေတြ အေပၚမွာ ေဖာ္ျပထားတာျဖစ္တဲ့အတြက္ ၾကည့္တာနဲ႔ နားလည္ႏိုင္မွာပါ။
10. Unlock bitmap
cloneImg.UnlockBits(bmpData);
ဒါကေတာ့ Processing လုပ္ငန္းစဥ္ အားလံုးၿပီးသြားၿပီျဖစ္တဲ့အတြက္ Lock ခ်ထားတဲ့ Image ကို unLock ျပန္လုပ္တာပါ။

11. Return Bitmap Image
return cloneImg;
ေနာက္ဆံုးအဆင့္ကေတာ့ Processing လုပ္ၿပီးသား Image တစ္ခုကို Method သို႔ ျပန္လည္အသံုးျပဳ/ေဖာ္ျပႏိုင္ရန္ Return ျပန္ေပးထားတာျဖစ္ပါတရ္။ Coding အလုပ္ လုပ္သြားပံုကိုေတာ့ က်ေနာ္ တတ္ႏိုင္သမွ် အေသးစိတ္ရွင္းထားပါတရ္။ က်ေနာ္ တင္ျပပံု မွားယြင္းခဲ့တာ ရွိခဲ့ရင္လည္း နားလည္ေပးၾကဖို႔ ေတာင္းပန္ပါရေစဗ်ာ။
အခု ဆက္လက္ၿပီး Calling Method ကို ေလ့လာၾကည့္ၾကပါ့မရ္။ btnProcessing Button ကို D_Click ေပးၿပီး ေအာက္ပါ Coding ကို ေရးယူလိုက္ပါဗ်ာ။ Coding မွာ ခက္ခဲတာ ဘာမွမပါ ပါဘူးဗ်ာ။ Stopwatch သံုးထားတာကေတာ့ Processing Time ကို မွတ္ထားေစခ်င္လို႔ပါ။ cbProcessingMethod မွ ေရြးခ်ယ္လိုက္ေသာ Index အေပၚမူတည္ၿပီး Processing Method ေတြကို ေခၚယူမွာ ျဖစ္ပါတရ္။ ဒီေလာက္ဆိုရင္ေတာ့ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမတို႔အေနနဲ႔ Fast Image Processing အတြက္ Lockbits() and Marshal.Copy ကို အသံုးျပဳၿပီး Processing လုပ္တတ္ၿပီလို႔ ထင္ပါတရ္ဗ်ာ။ က်န္ရွိေနေသးတဲ့ Fast Image Processing Method ႏွစ္ခုကိုေတာ့ ေနာက္ သင္ခန္းစာမွာ ဆက္လက္ ေဖာ္ျပေပးသြားပါ့မရ္။ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမအားလံုး ေလ့လာျခင္းျဖင့္ ေက်နပ္ႏိုင္ၾကပါေစ။

0 Responses so far.

Post a Comment