C# - Crop Image in a Ellipse Shape using Graphics

Posted by ေတဇာလင္း Tuesday, 7 February 2017 0 comments

မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမမ်ား အားလံုးပဲ မဂၤလာပါဗ်ာ။ ဒီေန႔ေတာ့ က်ေနာ္တို႔ Visual Studio C# ကို အသံုးျပဳၿပီး Image တစ္ခုကို မိမိႏွစ္သက္ရာေနရာအား Ellipse ပံုစံျဖစ္ ဘရ္လိုျဖတ္ယူႏိုင္မလဲဆိုတာေလးကို ေလ့လာၾကည့္ပါ့မရ္။ နည္းလမ္းေပါင္းစံု ရွိတဲ့အထဲကေနၿပီ အလြရ္ကူဆံုးနည္းလမ္းျဖစ္တဲ့ Graphic Library ေအာက္က DrawImage() Method ကို အသံုးျပဳၿပီး ေလ့လာၾကည့္ပါ့မရ္။ က်ေနာ့္ ေလ့က်င့္ခန္းေတြကေတာ့ စတင္ ေလ့လာသူမ်ား အတြက္သာ ရည္ရြယ္ပါတရ္။ ဒါေၾကာင့္ သိေနၿပီးသား သူမ်ားအေနနဲ႔ နားလည္ေပးၾကဖို႔နဲ႔ က်ေနာ္ရဲ႕ တင္ျပပံု မွားယြင္းတာမ်ားရွိခဲ့ရင္ ျပန္လည္ေထာက္ျပေပးႏိုင္ဖို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။
ဒါကေတာ့ က်ေနာ္တို႔ Project မွာ အသံုးျပဳမရ္ Library File ေတြပါ။ က်ေနာ္ကေတာ့ ေအာက္ကပံုအတိုင္း ပံုစံခ်ထားပါတရ္ဗ်ာ။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ GUI ကို ႀကိဳက္သလို ပံုစံေဖာ္ႏိုင္ပါတရ္။
က်ေနာ္ကေတာ့ ေအာက္ကပံုအတိုင္း ပံုစံခ်ထားပါတရ္ဗ်ာ။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ GUI ကို ႀကိဳက္သလို ပံုစံေဖာ္ႏိုင္ပါတရ္။
ဒီေန႔ ေလ့က်င့္ခန္းမွာေတာ့ Crop Processing အတြက္ သီးသန္႔ Button မထည့္ေတာ့ပဲ picOriginalImg ရဲ႕ Event ေတြကပဲ Processing လွမ္းလုပ္တဲ့ ပံုစံေလးနဲ႔ ေလ့လာၾကည့္သြားပါ့မရ္။ အသံုးျပဳသြားမရ္႕ Event ေတြကေတာ့ MouseDown, MouseMove and MouseUp တို႔ပဲ ျဖစ္ပါတရ္။ MouseDown Event ထဲမွာေတာ့ PictureBox ေပၚသို႔ Left_Click ျဖင့္ Mouse Point ခ် Down/Click ေပးလိုက္တဲ့အခ်ိန္မွာ လုပ္ေစခ်င္တဲ့ အလုပ္ေတြကို ခိုင္းေစႏိုင္ပါတရ္။ MouseMove Event ကေတာ့ Left_Clik ေပးၿပီး Drag ဆြဲတဲ့အခ်ိန္မွာ လုပ္ေစခ်င္တဲ့ အလုပ္ေတြကို ခိုင္းဖို႔ပါ။ MouseUp မွာေတာ့ Mouse Down and Move အလုပ္ ႏွစ္ခုလုပ္ၿပီး ျပန္လႊတ္လိုက္တဲ့အခ်ိန္မွာ လုပ္ေစခ်င္တဲ့ အလုပ္ေတြကို ခိုင္းေစႏိုင္ပါတရ္။ ဒီေန႔ ေလ့က်င့္ခန္းမွာေတာ့ Event သံုးခုလံုးၿပီးမွ Processing တစ္ခု လုပ္တဲ့ပံုစံနဲ႔ ခ်ဥ္းကပ္ထားပါတရ္။
Event တစ္ခုခ်င္းစီထဲက Code ကို အေသးစိတ္ေလ့လာၾကည့္ၾကရေအာင္...။ picOriginalImg ရဲ႕ MouseDown_Event ထဲသို႔ ၀င္၍ ေအာက္ပါ Coding ကိုေရးယူလုိက္ပါဗ်ာ။ Method ရဲ႕ အျပင္ဘက္မွာ Global Variable အျဖစ္ x, y, cropWidth, cropHeight ဆိုၿပီး ေလးခုေၾကျငာထားပါတရ္။ ၄င္း variable ေတြကို ဒီ Event တစ္ခုတည္းကတင္ မဟုတ္ဘဲ အျခားေသာ Event ေတြကပါ ေခၚသံုးႏိုင္ေအာင္လို႔ Global ေၾကျငာထားျခင္းျဖစ္ပါတရ္။ MouseDown_Event ထဲမွာ ခိုင္းထားတဲ့အလုပ္ကေတာ့ ရိုးရွင္းပါတရ္။ စစခ်င္းမွာ if()Statement နဲ႔ Mouse ရဲ႕ Event(Action)ကိုဖမ္းၿပီး Left Click ျဖစ္လား/မျဖစ္လားဆိုၿပီး စစ္ထားပါတရ္။ Left_Click Action ျဖစ္ခဲ့ရင္ေတာ့ က်ေနာ္တို႔ Global ေၾကျငာထားေသာ x, y variable ထဲကို မိတ္ေဆြကို Mouse Down/Click ေပးလိုက္ေသာ X,Y Coordinate ကို လွမ္းထည့္လိုက္ပါတရ္။ ဆိုလိုတာကေတာ့ မိတ္ေဆြတို႔ Crop ရဲ႕ Image ဟာ ဒီ X, Y Coordinate က စပါ့မရ္လို႔ မွတ္ထားခိုင္းလိုက္တာပဲျဖစ္ပါတရ္။ ကာဆာကို Cross သံုးထားတာကေတာ့ Mouse Down and Drag လုပ္ေနတဲ့အခ်ိန္မွာ ကာဆာကို Cross ပံုစံေလး ျပေနေစခ်င္တဲ့အတြက္ေၾကာင့္ပါ။ ဒီေလာက္ဆိုရင္ေတာ့ MouseDown_Event မွာ ခိုင္းေစထားတဲ့အခ်ိန္ေတြကို သိၿပီလို႔ ထင္ပါတရ္ဗ်ာ။
picOriginalImg ရဲ႕ MouseMove_Event ထဲသို႔၀င္ၿပီး ေအာက္ပါ Coding ကိုထပ္ေရးယူလိုက္ပါဗ်ာ။ ဒီမွာလည္း ခက္ခဲတာ ဘာမွမပါ ပါဘူးဗ်ာ။ Coding စစခ်င္းမွာ က်ေနာ္တို႔ Crop လုပ္ခ်င္တဲ့ PictureBox ထဲမွာ Image Load ဆြဲတင္ၿပီး သားရွိ/မရွိကို အရင္စစ္ထားပါတရ္။ မရွိရင္ေတာ့ MouseMove_Event အလုပ္လုပ္မွာမဟုတ္ပါဘူး။ ရွိခဲ့ရင္ေတာ့ ဒုတိယ if()statement နဲ႔ Mouse ရဲ႕ Action ကိုဖမ္းၿပီး Left-Click ဖိၿပီး Drag(Move) လုပ္တာ ဟုတ္/မဟုတ္ ထပ္စစ္ထားပါတရ္။ ဟုတ္ခဲ့ရင္ေတာ့ ဘရ္ေလာက္ Width, ဘရ္ေလာက္ Height ေလာက္ထိ Crop လုပ္မရ္ဆုိတဲ့ မိတ္ေဆြတို႔ Drag ဆြဲသြားေသာ Width နဲ႔ Height ကိုမွတ္ပါတရ္။ ရလာတဲ့ တန္ဖိုးေတြကိုေတာ့ အေပၚမွာ global ေၾကျငာခဲ့တဲ့ cropWidth နဲ႔ cropHeight ထဲကို သြားသိမ္းထားပါတရ္။ ဒီမွာ ေနာက္ဆံုး MouseMove ရဲ႕ ဆံုးမွတ္ျဖစ္တဲ့ X,Y Coordinate ေတြထဲက စမွတ္(X,Y coordinate) (e.X - x နဲ႔ e.Y - y) ကိုျပန္ႏုတ္ထားတာကေတာ့ မိတ္ေဆြတို႔ Drag ဆြဲယူလိုက္တဲ့ Width and Height ကို အတိအက်လိုခ်င္လို႔ပါ။ နားမလည္ခဲ့ရင္ေတာ့ X,Y Coordinate မ်ဥ္းဆြဲၿပီး Trace လုပ္ၾကည့္ႏိုင္ပါတရ္။ ဒီ crop Image ရဲ႕ Widht and Height ဖမ္းႏိုင္တဲ့အဆင့္နဲ႔တင္ တကယ္ဆိုရင္ MouseMove_Event ရဲ႕ အလုပ္က ၿပီးပါတရ္။ ဒါကိုမွကြန္႔ၿပီး Selected Area ကို ထင္သာျမင္သာရွိေအာင္ Rectangle and Ellipse ပံုစံေလးဆြဲၿပီး ျပေစခ်င္လို႔သာ ေအာက္က သံုးေၾကာင္းက ထပ္ေရးရတာ ျဖစ္ပါတရ္။ ေအာက္က သံုးေၾကာင္းမပါလည္း အလုပ္ျဖစ္ပါတရ္။
Rectangle and Ellipse ဆြဲဖို႔အတြက္ DrawRectangle() နဲ႔ DrawEllipse() Method ေတြကိုသံုးထားပါတရ္။ Parameter ငါးခုပါၿပီး ပထမ parameter ကေတာ့ Graphic ကိုဆြဲခ်င္တဲ့ Pen Class ပါ။ ဒီမွာေတာ့ Pen ရဲ႕ Color ကို Assign လုပ္ေပးထားပါတရ္။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ ႏွစ္သက္သလို အျခား properties ေတြကို ထည့္သြင္းအသံုးျပဳႏုိင္ပါတရ္။ ေနာက္ x,y Paramenter ႏွစ္ခုကေတာ့ Graphic ရဲ႕ စမွတ္ Coordinate အမွတ္ျဖစ္ပါတရ္။ ေနာက္ဆံုး Parameter ႏွစ္ခုကေတာ့ ဆြဲယူမရ္႕ Graphic ရဲ႕ Width and Height ကို သတ္မွတ္ေပးလိုက္တာပါ။ ဒီမွာ က်ေနာ္ Graphic လုပ္ငန္းစဥ္ကို ႏွစ္ေၾကာင္းကိုတစ္ေၾကာင္းတည္းျဖစ္ေအာင္ ေပါင္းေရးထားတာပါ။ နားလည္ရ ခက္သြားမရ္ဆိုရင္ေတာ့ ...
Graphics gfx = picOriginalImg.CreateGraphics();
gfx.DrawRectangle(Pens.Gold, x, y, cropWidth, cropHeight);
gfx. DrawEllipse(Pens.DarkGoldenrod, x, y, cropWidth, cropHeight);
အခုလိုပံုစံ ခြဲေရးလို႔ရႏိုင္ပါတရ္။ ပထမ တစ္ေၾကာင္းက Graphic ဆြဲယူမရ္႕ Container ကိုသတ္မွတ္ေပးတာပါ။ က်ေနာ္တို႔က picOriginalImg အေပၚမွာဆြဲမွာျဖစ္လို႔ ၄င္း PictureBox ကိုညႊန္းေပးထားတာပဲျဖစ္ပါတရ္။ ဒုတိယ ႏွစ္ေၾကာင္းကေတာ့ သတ္မွတ္ထားတဲ့ Container ေပၚမွာ မိမိႏွစ္သက္ရာ Method ကိုအသံုးျပဳၿပီး Graphic တစ္ခုဆြဲယူ လိုက္တာပဲျဖစ္ပါတရ္။ နားလည္ၾကမရ္လို႔ ထင္ပါတရ္။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ ႏွစ္ေၾကာင္းတစ္ေၾကာင္းေပါင္းၿပီးျဖစ္ေစ၊ ႏွစ္ေၾကာင္းခြဲေရးၿပီးျဖစ္ေစ မိမိအားသာရာကို အသံုးျပဳႏိုင္ပါတရ္။
picOriginalImg.Refresh(); 
PictureBox ကို အၿမဲ Refresh ေပးထားတာကေတာ့ MouseMove Event အတြင္း ေရႊ႕သြားေသာ Pixel အတိုင္း ဆြဲယူသြားေသာ Graphic ေတြကို ျပန္ ျပန္ဖ်တ္ထုတ္ခ်င္လို႔ပါ။ မိတ္ေဆြကို သေဘာတရားကို နားလည္ေစခ်င္ရင္ေတာ့ ၄င္း cmd line ကို off ၿပီး run ၾကည့္ႏိုင္ပါတရ္။
ေနာက္ဆံုး Event တစ္ခုျဖစ္တဲ့ MouseUp_Event ကိုဆက္ေလ့လာၾကည့္မရ္။ ဒါေၾကာင့္ picOriginalImg ရဲ႕ MouseUp Event ထဲသို႔၀င္၍ ေအာက္ပါ Coding ေလးကို ၀င္ေရးယူလိုက္ပါဗ်ာ။ Coding စစခ်င္းမွာ crop Area သတ္မွတ္ေနတဲ့အခ်ိန္ Cross ေပးထားေသာ ကာဆာကို မူလပံုစံျဖစ္တဲ့(မွ်ားေခါင္းပံု) Default ျပန္ေပးလိုက္ပါတရ္။ if()statemet ကေတာ့ crop Area ရွိ/မရွိ စစ္ထားတာပါ။ မိတ္ေဆြတို႔ crop လုပ္မရ္႕ Area ဟာ 1 pixel ထက္နည္းေနရင္ အလုပ္လုပ္မွာ မဟုတ္ပါဘူးဗ်ာ။ ေနာက္တစ္ေၾကာင္းကေတာ့ Crop မလုပ္ရေသးတဲ့ Original Image ကို Bitmap အျဖစ္ Clone ပြားယူလိုက္တာပဲျဖစ္ပါတရ္။ ေနာက္တစ္ေၾကာင္းကေတာ့ Crop လုပ္ယူလိုက္တဲ့ Image ကို သိုေလွာင္ဖို႔အတြက္ Bitmap အသစ္တစ္ခု တည္ေဆာက္လိုက္တာပါ။ ၿပီးရင္ေတာ့ ၄င္း Bitmap ကို Container အျဖစ္ထားၿပီး Graphic ဆြဲယူပါ့မရ္လို႔ ခိုင္းပါတရ္။ SmoothingMode သံုးထားတာကေတာ့ မိတ္ေဆြတို႔ ဖန္တီးယူလိုက္တဲ့ Graphic ကို Smooth ျဖစ္ေစခ်င္လို႔ပါ။ ၿပီးရင္ က်ေနာ္တို႔က Crop Image ကို Ellipse ပံုစံ ျဖတ္ယူမွာျဖစ္လို႔ Ellipse အတြက္ Path ဆြဲႏိုင္ေအာင္ GraphicsPath Object တစ္ခုေဆာက္ပါတရ္။ ေနာက္ ၄င္း GraphicsPath ထဲကိုမွ က်ေနာ္တို႔ MouseDown and MouseMove Method တို႔နဲ႔ Crop လုပ္ယူလိုက္ေသာ Ellipse Graphic Path ကို Add ယူပါတရ္။ Graphic ကို Clear လုပ္ၿပီး အျဖဴေရာင္ ကာလာ Assign လုပ္ထားတာကေတာ့ က်ေနာ္တို႔ Crop ယူလိုက္ေသာ Image မွလြဲ၍ က်န္ Image Background ေနရာမ်ားကို အျဖဴေရာင္ ထားေစခ်င္လို႔ပဲ ျဖစ္ပါတရ္။ ေနာက္တစ္ေၾကာင္းမွာေတာ့ SetClip()Method ကိုအသံုးျပဳၿပီး က်ေနာ္တို႔ သတ္မွတ္ေပးၿပီးသားျဖစ္တဲ့ Ellipse Graphic Path ႀကီးတစ္ခုလံုးကို Graphic Object ထဲ Assign လုပ္ေပးၿပီး Graphic ဆြဲယူရန္အသင့္လို႔ ေျပာပါတရ္။ ေနာက္တစ္ေၾကာင္းကေတာ့ DrawImage() Method ကိုသံုးၿပီး အမွန္တကရ္ Crop Graphic ဆြဲယူတဲ့အဆင့္ပါ။ Parameter ငါးခု passing လုပ္ထားပါတရ္။ ပထမ parameter ကေတာ့ Soucre Image သို႔မဟုတ္ Original Image ပါ။ ေနာက္ parameter ႏွစ္ခုကေတာ့ စမွတ္(X,Y coordinate) သတ္မွတ္ေပးတာပါ။ ေနာက္ parameter ကေတာ့ Selected area ပါ။ Rectangle ကိုသံုးထားပါတရ္။ ေနာက္ဆံုး parameter ကေတာ့ Graphics ရဲ႕ Unit ကို သတ္မွတ္ေပးတာပဲ ျဖစ္ပါတရ္။ က်ေနာ္ကေတာ့ Pixel ကိုေရြးေပးထားပါတရ္။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ လက္မ၊ မီလီမီတာ၊ point စတဲ့ ႏွစ္သက္ရာ Unit တစ္ခုခုနဲ႔ ေျပာင္းလဲအသံုးျပဳႏိုင္ပါတရ္။ အႏွစ္ခ်ဳပ္ခိုင္းသြားတဲ့ သေဘာကိုေျပာရရင္ Original ထဲက Select လုပ္ထားေသာ Area ကို Pixel Unit ကိုအသံုးျပဳၿပီး cropBmp ထဲသို႔ စမွတ္(0,0 Coordinate) မွ Graphic ဖန္တီးေပးပါလို႔ ခိုင္းလိုက္တာပါ။ ေနာက္ Crop Image ကို White Color ေပးၿပီး Background ကာလာကို Transparent ေျပာင္းယူတာပါ(Background Color နဲ႔ Transparent Color ညီမွ Background Transparent ျဖစ္မွာျဖစ္ပါတရ္)။ .Png Type နဲ႔ Save ရင္ေတာ့ Background Transparent Color ရၿပီး မည္သည့္ object နဲ႔မဆို တြဲဘက္အသံုးျပဳႏိုင္မွာ ျဖစ္ပါတရ္။ အျခား .Jpg တို႔နဲ႔ Save ရင္ေတာ့ Background Color ကို Black နဲ႔ သိမ္းေပးမွာျဖစ္ပါတရ္။ ေနာက္ဆံုးရလာတဲ့ cropBmp သို႔မဟုတ္ Crop Image ကိုေတာ့ Preview အျဖစ္ picResultImg PictureBox ေပၚသို႔ ဆြဲတင္ေပးလိုက္တာပဲ ျဖစ္ပါတရ္ဗ်ာ။ နားလည္ႏိုင္ၾကလိမ့္မရ္လို႔ ေမွ်ာ္လင့္ပါတရ္။
Coding တစ္ေၾကာင္းခ်င္းစီကိုေတာ့ အေသးစိတ္ ရွင္းျပခဲ့ပါတရ္။ ပိုၿပီး နားလည္ေစခ်င္တရ္ ဆိုရင္ေတာ့ Coding အျပည့္အစံုကို ေအာက္မွာ ေလ့လာၾကည့္ႏိုင္ပါတရ္ဗ်။ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမအားလံုး ေလ့လာျခင္းျဖင့္ ေက်နပ္ႏိုင္ၾကပါေစ။

မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမမ်ား အားလံုးပဲ မဂၤလာပါဗ်ာ။ ဒီေန႔ေတာ့ က်ေနာ္တို႔ Visual Studio C# ကို အသံုးျပဳၿပီး Image တစ္ခုကို မိမိႏွစ္သက္ရာေနရာအား Rectangle ပံုစံျဖစ္ ဘရ္လိုျဖတ္ယူႏိုင္မလဲဆိုတာေလးကို ေလ့လာၾကည့္ပါ့မရ္။ နည္းလမ္းေပါင္းစံု ရွိတဲ့အထဲကေနၿပီ အလြရ္ကူဆံုးနည္းလမ္းျဖစ္တဲ့ Graphic Library ေအာက္က DrawImage() Method ကို အသံုးျပဳၿပီး ေလ့လာၾကည့္ပါ့မရ္။ က်ေနာ့္ ေလ့က်င့္ခန္းေတြကေတာ့ စတင္ ေလ့လာသူမ်ားအတြက္သာ ရည္ရြယ္ပါတရ္။ ဒါေၾကာင့္ သိေနၿပီးသား သူမ်ားအေနနဲ႔ နားလည္ေပးၾကဖို႔နဲ႔ က်ေနာ္ရဲ႕ တင္ျပပံု မွားယြင္းတာမ်ားရွိခဲ့ရင္ ျပန္လည္ေထာက္ျပေပးႏိုင္ဖို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။
ဒါကေတာ့ က်ေနာ္တို႔ Project မွာ အသံုးျပဳမရ္ Library File ေတြပါ။ ဒီေန႔ ေလ့က်င့္ခန္းအတြက္ GUI တည္ေဆာက္ျခင္းအပိုင္းမွာေတာ့ က်ေနာ္ Button(2)ခုနဲ႔ PictureBox(2)ခုကို အသံုးျပဳထားပါတရ္။ Tools ေတြရဲ႕ Properties ေတြကိုေတာ့ ေအာက္က Table အတိုင္း ျပင္ယူလိုက္ၾကပါဗ်ာ။
က်ေနာ္ကေတာ့ ေအာက္ကပံုအတိုင္း ပံုစံခ်ထားပါတရ္ဗ်ာ။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ GUI ကို ႀကိဳက္သလို ပံုစံေဖာ္ႏိုင္ပါတရ္။
ဒီေန႔ ေလ့က်င့္ခန္းမွာေတာ့ Crop Processing အတြက္ သီးသန္႔ Button မထည့္ေတာ့ပဲ picOriginalImg ရဲ႕ Event ေတြကပဲ Processing လွမ္းလုပ္တဲ့ ပံုစံေလးနဲ႔ ေလ့လာၾကည့္သြားပါ့မရ္။ အသံုးျပဳသြားမရ္႕ Event ေတြကေတာ့ MouseDown, MouseMove and MouseUp တို႔ပဲ ျဖစ္ပါတရ္။ MouseDown Event ထဲမွာေတာ့ PictureBox ေပၚသို႔ Left_Click ျဖင့္ Mouse Point ခ် Down/Click ေပးလိုက္တဲ့အခ်ိန္မွာ လုပ္ေစခ်င္တဲ့ အလုပ္ေတြကို ခိုင္းေစႏိုင္ပါတရ္။ MouseMove Event ကေတာ့ Left_Clik ေပးၿပီး Drag ဆြဲတဲ့အခ်ိန္မွာ လုပ္ေစခ်င္တဲ့ အလုပ္ေတြကို ခိုင္းဖို႔ပါ။ MouseUp မွာေတာ့ Mouse Down and Move အလုပ္ ႏွစ္ခုလုပ္ၿပီး ျပန္လႊတ္လိုက္တဲ့အခ်ိန္မွာ လုပ္ေစခ်င္တဲ့ အလုပ္ေတြကို ခိုင္းေစႏိုင္ပါတရ္။ ဒီေန႔ ေလ့က်င့္ခန္းမွာေတာ့ Event သံုးခုလံုးၿပီးမွ Processing တစ္ခု လုပ္တဲ့ပံုစံနဲ႔ ခ်ဥ္းကပ္ထားပါတရ္။
Event တစ္ခုခ်င္းစီထဲက Code ကို အေသးစိတ္ေလ့လာၾကည့္ၾကရေအာင္...။ picOriginalImg ရဲ႕ MouseDown_Event ထဲသို႔ ၀င္၍ ေအာက္ပါ Coding ကိုေရးယူလုိက္ပါဗ်ာ။ Method ရဲ႕ အျပင္ဘက္မွာ Global Variable အျဖစ္ x, y, cropWidth, cropHeight ဆိုၿပီး ေလးခုေၾကျငာထားပါတရ္။ ၄င္း variable ေတြကို ဒီ Event တစ္ခုတည္းကတင္ မဟုတ္ဘဲ အျခားေသာ Event ေတြကပါ ေခၚသံုးႏိုင္ေအာင္လို႔ Global ေၾကျငာထားျခင္းျဖစ္ပါတရ္။ MouseDown_Event ထဲမွာ ခိုင္းထားတဲ့အလုပ္ကေတာ့ ရိုးရွင္းပါတရ္။ စစခ်င္းမွာ if()Statement နဲ႔ Mouse ရဲ႕ Event(Action)ကိုဖမ္းၿပီး Left Click ျဖစ္လား/မျဖစ္လားဆိုၿပီး စစ္ထားပါတရ္။ Left_Click Action ျဖစ္ခဲ့ရင္ေတာ့ က်ေနာ္တို႔ Global ေၾကျငာထားေသာ x, y variable ထဲကို မိတ္ေဆြကို Mouse Down/Click ေပးလိုက္ေသာ X,Y Coordinate ကို လွမ္းထည့္လိုက္ပါတရ္။ ဆိုလိုတာကေတာ့ မိတ္ေဆြတို႔ Crop ရဲ႕ Image ဟာ ဒီ X, Y Coordinate က စပါ့မရ္လို႔ မွတ္ထားခိုင္းလိုက္တာပဲျဖစ္ပါတရ္။ ကာဆာကို Cross သံုးထားတာကေတာ့ Mouse Down and Drag လုပ္ေနတဲ့အခ်ိန္မွာ ကာဆာကို Cross ပံုစံေလး ျပေနေစခ်င္တဲ့အတြက္ေၾကာင့္ပါ။ ဒီေလာက္ဆိုရင္ေတာ့ MouseDown_Event မွာ ခိုင္းေစထားတဲ့အခ်ိန္ေတြကို သိၿပီလို႔ ထင္ပါတရ္ဗ်ာ။
picOriginalImg ရဲ႕ MouseMove_Event ထဲသို႔၀င္ၿပီး ေအာက္ပါ Coding ကိုထပ္ေရးယူလိုက္ပါဗ်ာ။ ဒီမွာလည္း ခက္ခဲတာ ဘာမွမပါ ပါဘူးဗ်ာ။ Coding စစခ်င္းမွာ က်ေနာ္တို႔ Crop လုပ္ခ်င္တဲ့ PictureBox ထဲမွာ Image Load ဆြဲတင္ၿပီး သားရွိ/မရွိကို အရင္စစ္ထားပါတရ္။ မရွိရင္ေတာ့ MouseMove_Event အလုပ္လုပ္မွာမဟုတ္ပါဘူး။ ရွိခဲ့ရင္ေတာ့ ဒုတိယ if()statement နဲ႔ Mouse ရဲ႕ Action ကိုဖမ္းၿပီး Left-Click ဖိၿပီး Drag(Move) လုပ္တာ ဟုတ္/မဟုတ္ ထပ္စစ္ထားပါတရ္။ ဟုတ္ခဲ့ရင္ေတာ့ ဘရ္ေလာက္ Width, ဘရ္ေလာက္ Height ေလာက္ထိ Crop လုပ္မရ္ဆုိတဲ့ မိတ္ေဆြတို႔ Drag ဆြဲသြားေသာ Width နဲ႔ Height ကိုမွတ္ပါတရ္။ ရလာတဲ့ တန္ဖိုးေတြကိုေတာ့ အေပၚမွာ global ေၾကျငာခဲ့တဲ့ cropWidth နဲ႔ cropHeight ထဲကို သြားသိမ္းထားပါတရ္။ ဒီမွာ ေနာက္ဆံုး MouseMove ရဲ႕ ဆံုးမွတ္ျဖစ္တဲ့ X,Y Coordinate ေတြထဲက စမွတ္(X,Y coordinate) (e.X - x နဲ႔ e.Y - y) ကိုျပန္ႏုတ္ထားတာကေတာ့ မိတ္ေဆြတို႔ Drag ဆြဲယူလိုက္တဲ့ Width and Height ကို အတိအက်လိုခ်င္လို႔ပါ။ နားမလည္ခဲ့ရင္ေတာ့ X,Y Coordinate မ်ဥ္းဆြဲၿပီး Trace လုပ္ၾကည့္ႏိုင္ပါတရ္။ ဒီ crop Image ရဲ႕ Widht and Height ဖမ္းႏိုင္တဲ့အဆင့္နဲ႔တင္ တကယ္ဆိုရင္ MouseMove_Event ရဲ႕ အလုပ္က ၿပီးပါတရ္။ ဒါကိုမွကြန္႔ၿပီး Selected Area ကို ထင္သာျမင္သာရွိေအာင္ Rectangle ေလးဆြဲၿပီး ျပေစခ်င္လို႔သာ ေအာက္က ႏွစ္ေၾကာင္းက ထပ္ေရးရတာ ျဖစ္ပါတရ္။ ေအာက္က ႏွစ္ေၾကာင္းမပါလည္း အလုပ္ျဖစ္ပါတရ္။
Rectangle ဆြဲဖို႔အတြက္ DrawRectangle() Method ကိုသံုးထားပါတရ္။ Parameter ငါးခုပါၿပီး ပထမ parameter ကေတာ့ Rectangle ကိုဆြဲခ်င္တဲ့ Pen Class ပါ။ ဒီမွာေတာ့ Pen ရဲ႕ Color ကို Assign လုပ္ေပးထားပါတရ္။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ ႏွစ္သက္သလို အျခား properties ေတြကို ထည့္သြင္းအသံုးျပဳႏုိင္ပါတရ္။ ေနာက္ x,y Paramenter ႏွစ္ခုကေတာ့ Rectangle ရဲ႕ စမွတ္ Coordinate အမွတ္ျဖစ္ပါတရ္။ ေနာက္ဆံုး Parameter ႏွစ္ခုကေတာ့ ဆြဲယူမရ္႕ Rectangle ရဲ႕ Width and Height ကို သတ္မွတ္ေပးလိုက္တာပါ။ ဒီမွာ က်ေနာ္ Graphic လုပ္ငန္းစဥ္ကို ႏွစ္ေၾကာင္းကိုတစ္ေၾကာင္းတည္းျဖစ္ေအာင္ ေပါင္းေရးထားတာပါ။ နားလည္ရ ခက္သြားမရ္ဆိုရင္ေတာ့ ...
Graphics gfx = picOriginalImg.CreateGraphics();
gfx.DrawRectangle(Pens.Gold, x, y, cropWidth, cropHeight);
အခုလိုပံုစံ ခြဲေရးလို႔ရႏိုင္ပါတရ္။ ပထမ တစ္ေၾကာင္းက Graphic ဆြဲယူမရ္႕ Container ကိုသတ္မွတ္ေပးတာပါ။ က်ေနာ္တို႔က picOriginalImg အေပၚမွာဆြဲမွာျဖစ္လို႔ ၄င္း PictureBox ကိုညႊန္းေပးထားတာပဲျဖစ္ပါတရ္။ ဒုတိယ တစ္ေၾကာင္းကေတာ့ သတ္မွတ္ထားတဲ့ Container ေပၚမွာ DrawRectangle()Method ကိုအသံုးျပဳၿပီး Rectangle Graphic တစ္ခုဆြဲယူလိုက္တာပဲျဖစ္ပါတရ္။ နားလည္ၾကမရ္လို႔ ထင္ပါတရ္။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ ႏွစ္ေၾကာင္းတစ္ေၾကာင္းေပါင္းၿပီးျဖစ္ေစ၊ ႏွစ္ေၾကာင္းခြဲေရးၿပီးျဖစ္ေစ မိမိအားသာရာကို အသံုးျပဳႏိုင္ပါတရ္။
picOriginalImg.Refresh(); 
PictureBox ကို အၿမဲ Refresh ေပးထားတာကေတာ့ MouseMove Event အတြင္း ေရႊ႕သြားေသာ Pixel အတိုင္း ဆြဲယူသြားေသာ Rectangle ေတြကို ျပန္ ျပန္ဖ်တ္ထုတ္ခ်င္လို႔ပါ။ မိတ္ေဆြကို သေဘာတရားကို နားလည္ေစခ်င္ရင္ေတာ့ ၄င္း cmd line ကို off ၿပီး run ၾကည့္ႏိုင္ပါတရ္။
ေနာက္ဆံုး Event တစ္ခုျဖစ္တဲ့ MouseUp_Event ကိုဆက္ေလ့လာၾကည့္မရ္။ ဒါေၾကာင့္ picOriginalImg ရဲ႕ MouseUp Event ထဲသို႔၀င္၍ ေအာက္ပါ Coding ေလးကို ၀င္ေရးယူလိုက္ပါဗ်ာ။ Coding စစခ်င္းမွာ crop Area သတ္မွတ္ေနတဲ့အခ်ိန္ Cross ေပးထားေသာ ကာဆာကို မူလပံုစံျဖစ္တဲ့(မွ်ားေခါင္းပံု) Default ျပန္ေပးလိုက္ပါတရ္။ if()statemet ကေတာ့ crop Area ရွိ/မရွိ စစ္ထားတာပါ။ မိတ္ေဆြတို႔ crop လုပ္မရ္႕ Area ဟာ 1 pixel ထက္နည္းေနရင္ အလုပ္လုပ္မွာ မဟုတ္ပါဘူးဗ်ာ။ ေနာက္တစ္ေၾကာင္းကေတာ့ Crop မလုပ္ရေသးတဲ့ Original Image ကို Bitmap အျဖစ္ Clone ပြားယူလိုက္တာပဲျဖစ္ပါတရ္။ ေနာက္တစ္ေၾကာင္းကေတာ့ Crop လုပ္ယူလိုက္တဲ့ Image ကို သိုေလွာင္ဖို႔အတြက္ Bitmap အသစ္တစ္ခု တည္ေဆာက္လိုက္တာပါ။ ၿပီးရင္ေတာ့ ၄င္း Bitmap ကို Container အျဖစ္ထားၿပီး Graphic ဆြဲယူပါ့မရ္လို႔ ခိုင္းပါတရ္။ ဒီမွာေတာ့ DrawImage() Method ကိုသံုးထားပါတရ္။ Parameter ငါးခု passing လုပ္ထားပါတရ္။ ပထမ parameter ကေတာ့ Soucre Image သို႔မဟုတ္ Original Image ပါ။ ေနာက္ parameter ႏွစ္ခုကေတာ့ စမွတ္(X,Y coordinate) သတ္မွတ္ေပးတာပါ။ ေနာက္ parameter ကေတာ့ Selected area ပါ။ Rectangle ကိုသံုးထားပါတရ္။ ေနာက္ဆံုး parameter ကေတာ့ Graphics ရဲ႕ Unit ကို သတ္မွတ္ေပးတာပဲျဖစ္ပါတရ္။ က်ေနာ္ကေတာ့ Pixel ကိုေရြးေပးထားပါတရ္။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ လက္မ၊ မီလီမီတာ၊ point စတဲ့ ႏွစ္သက္ရာ Unit တစ္ခုခုနဲ႔ ေျပာင္းလဲအသံုးျပဳႏိုင္ပါတရ္။ အႏွစ္ခ်ဳပ္ခိုင္းသြားတဲ့ သေဘာကိုေျပာရရင္ Original ထဲက Select လုပ္ထားေသာ Area ကို Pixel Unit ကိုအသံုးျပဳၿပီး cropBmp ထဲသို႔ စမွတ္(0,0 Coordinate) မွ Graphic ဖန္တီးေပးပါလို႔ ခိုင္းလိုက္တာပါ။ ေနာက္ဆံုးရလာတဲ့ cropBmp သို႔မဟုတ္ Crop Image ကိုေတာ့ Preview အျဖစ္ picResultImg PictureBox ေပၚသို႔ ဆြဲတင္ေပးလိုက္တာပဲ ျဖစ္ပါတရ္ဗ်ာ။ နားလည္ႏိုင္ၾကလိမ့္မရ္လို႔ ေမွ်ာ္လင့္ပါတရ္။
Coding တစ္ေၾကာင္းခ်င္းစီကိုေတာ့ အေသးစိတ္ ရွင္းျပခဲ့ပါတရ္။ ပိုၿပီး နားလည္ေစခ်င္တရ္ ဆိုရင္ေတာ့ Coding အျပည့္အစံုကို ေအာက္မွာ ေလ့လာၾကည့္ႏိုင္ပါတရ္ဗ်။ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမအားလံုး ေလ့လာျခင္းျဖင့္ ေက်နပ္ႏိုင္ၾကပါေစ။

C# - Easy Way Re-size Image using DrawImage() Graphic

Posted by ေတဇာလင္း Saturday, 4 February 2017 0 comments

မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမမ်ား အားလံုးပဲ မဂၤလာပါဗ်ာ။ ဒီေန႔ေတာ့ က်ေနာ္တို႔ Visual Studio C# ကို အသံုးျပဳၿပီး Image တစ္ခုကို မိမိႏွစ္သက္ရာ Width, Height အတိုင္း ဘရ္လို Re-size လုပ္ၾကည့္ႏိုင္မလဲဆိုတာေလးကို ေလ့လာၾကည့္ပါ့မရ္။ နည္းလမ္းေပါင္းစံု ရွိတဲ့အထဲကေနၿပီ အလြရ္ကူဆံုးနည္းလမ္းျဖစ္တဲ့ Graphic Library ေအာက္က DrawImage() Method ကို အသံုးျပဳၿပီး ေလ့လာၾကည့္ပါ့မရ္။ က်ေနာ့္ ေလ့က်င့္ခန္းေတြကေတာ့ စတင္ ေလ့လာသူမ်ားအတြက္သာ ရည္ရြယ္ပါတရ္။ ဒါေၾကာင့္ သိေနၿပီးသား သူမ်ားအေနနဲ႔ နားလည္ေပးၾကဖို႔နဲ႔ က်ေနာ္ရဲ႕ တင္ျပပံု မွားယြင္းတာမ်ားရွိခဲ့ရင္ ျပန္လည္ေထာက္ျပေပးႏိုင္ဖို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။
ဒါကေတာ့ က်ေနာ္တို႔ Project မွာ အသံုးျပဳမရ္ Library File ေတြပါ။ DrawImage() Method ကို အသံုးျပဳမရ္ဆိုရင္ေတာ့ System.Drawin; နဲ႔ System.Drawing.Imaging; ဆိုတဲ့ Library ႏွစ္ခုကိုေတာ့ မျဖစ္မေန ေၾကျငာေပးရပါ့မရ္။ DrawImage()Method မွာ အသံုးျပဳႏိုင္တဲ့ Function Overload ပံုစံေတြ အမ်ားႀကီးပါ။ ဒီအမ်ားႀကီးထဲကမွ Image တစ္ခုကို မိမိႏွစ္သက္ရာ Width, Height အတိုင္း Graphic ျပန္ဆြဲႏိုင္မရ္႕ အလြရ္ကူဆံုး နည္းလမ္းေလးတစ္ခုကို ေလ့လာၾကည့္မရ္။ ဒါကေတာ့...
DrawImage(Image image, int x, int y, int width, int height);
အခုလို ပံုစံပါ။ Parameter Passing ငါးခုေတြ႔ရပါလိမ့္မရ္။ ပထမ parameter ကေတာ့ Source Image ပါ။ ဒီေနရာမွာ မိမိ Re-size လုပ္ခ်င္ေသာ Image ကိုေရြးခ်ယ္ေပးရပါ့မရ္။ ဒုတိယ parameter ကေတာ့ Drawing Object အတြက္ စမွတ္(x coordinate of the upper-left corner)ျဖစ္ပါတရ္။ တတိယ parameter ကေတာ့ Drawing Object ရဲ႕ စမွတ္(y-coordinate of the upper-left corner)ပဲျဖစ္ပါတရ္။ ဆိုလိုတဲ့သေဘာကေတာ့ Drawing Objecte ကို ဒုတိယနဲ႔တတိယ parameter ျဖစ္တဲ့ x, y Coordinate ကေန စဆြဲပါလို႔ သတ္မွတ္လို္က္တာပါ။ စမွတ္ေတာ့ရၿပီ ဘရ္ေလာက္အက်ယ္၊ ဘရ္ေလာက္အျမင့္ ဆြဲယူမွာလည္းဆိုတာကို သတ္မွတ္ေပးဖို႔ လိုပါေသးတရ္။ ဒါေၾကာင့္ ေနာက္ဆံုး parameter ႏွစ္ခုမွာ Drawing Object ရဲ႕ Width နဲ႔ Height ကို သတ္မွတ္ေပးလိုက္တာပဲျဖစ္ပါတရ္။ ဒီေလာက္ဆိုရင္ေတာ့ မိတ္ေဆြတို႔အေနန႔ဲ က်ေနာ္တုိ႔အသံုးျပဳမရ္႕ DrawImage()Method ရဲ႕ အလုပ္လုပ္ပံုကို နားလည္ၾကၿပီလို႔ ထင္ပါတရ္။
ဒီေန႔ ေလ့က်င့္ခန္းအတြက္ GUI တည္ေဆာက္ျခင္းအပိုင္းမွာေတာ့ က်ေနာ္ Button(3)ခု၊ PictureBox(2)ခု၊ Panel(1)ခု၊ Label(2)ခုနဲ႔ TextBox(2)ခုကို အသံုးျပဳထားပါတရ္။ Tools ေတြရဲ႕ Properties ေတြကိုေတာ့ ေအာက္က Table အတိုင္း ျပင္ယူလိုက္ၾကပါဗ်ာ။
က်ေနာ္ကေတာ့ ေအာက္ကပံုအတိုင္း ပံုစံခ်ထားပါတရ္ဗ်ာ။ မိတ္ေဆြတို႔အေနနဲ႔ေတာ့ GUI ကို ႀကိဳက္သလို ပံုစံေဖာ္ႏိုင္ပါတရ္။
DrawImage()Method ရဲ႕ အလုပ္လုပ္ပံုကိုလည္းသိၿပီ၊ GUI လည္းတည္ေဆာက္ၿပီးၿပီဆိုရင္ေတာ့ က်ေနာ္တုိ႔ ဘရ္လို Re-size လုပ္ႏိုင္မလဲဆိုတာေလးကို ဆက္ေလ့လာၾကည့္ပါ့မရ္။ ResizeImage()ဆိုတဲ့ Method တစ္ခုတည္ေအာက္ၿပီး ေအာက္ပါ Coding ေလးကို ေရးယူလိုက္ပါဗ်ာ။ အလုပ္လုပ္သြားပံုကေတာ့ ရိုးရိုးရွင္းရွင္းေလးပါပဲ။ Parameter အေနနဲ႔ Source Image ရယ္၊ Re-size Width and Height ကို Passing လုပ္ထားပါတရ္။ Result အေနန႔ဲေတာ့ Source Image ကို သတ္မွတ္ေပးလိုက္ေသာ Width and Height အတိုင္း Processing လုပ္ၿပီး ျပန္လည္ ထုတ္ေပးမွာျဖစ္ပါတရ္။ Coding စစခ်င္းမွာ User သတ္မွတ္လိုက္ေသာ Width and Height အတိုင္း Bitmap Image တစ္ခု ဖန္တီးယူပါတရ္။ ဒုတိယ cmd line မွာေတာ့ Graphic Object တည္ေဆာက္ပါတရ္။ Graphic ရဲ႕ Drawing Area ကိုေတာ့ Bitmap ပါလို႔ သတ္မွတ္ေပးလိုက္ပါတရ္။ ဒီမွာ using Keyword သုံးထားတာကေတာ့ Graphic Object ရဲ႕ scope ကို သတ္မွတ္လိုက္တာပါ။ Scope လြန္ၿပီဆိုတာနဲ႔ Graphic Object ကို Auto Dispose လုပ္ေပးပါတရ္။ using မသံုးခဲ့ဘူးဆိုရင္ေတာ့ Graphic ကို သံုးၿပီးတဲ့အခ်ိန္မွာ Dispose() Method ကိုေခၚၿပီး Graphic လုပ္ငန္းၿပီးဆံုးေၾကာင္း သတ္မွတ္ေပးရပါတရ္။ တတိယ cmd line ကေတာ့ က်ေနာ္တို႔ သတ္မွတ္ထားတဲ့ Graphic Area ထဲမွာ DrawImage() Method ကိုအသံုးျပဳၿပီး Graphic တစ္ခု ဆြဲယူလိုက္တာပဲျဖစ္ပါတရ္ဗ်ာ (ရွင္းလင္းခ်က္ အေပၚမွာၾကည့္ပါ)။ ေနာက္ဆံုး cmd line ကေတာ့ က်ေနာ္တို႔ လုပ္ေနၾကအတုိင္း Image ကို Return ျပန္ေပးတာပဲျဖစ္ပါတရ္။ Image အေနနဲ႔ Return ျပန္ေပးတာျဖစ္တဲ့အတြက္ Calling Method က Image အေနနဲ႔ ျပန္ဖမ္းရမရ္ဆိုတာေတာ့ သတိထားရမွာျဖစ္ပါတရ္။ Result ကုိေတာ့ ေအာက္မွာ ၾကည့္လိုက္ပါဗ်ာ။
ဒီမွာေတာ့ က်ေနာ္ Re-size Image ကို Runtime မွာ ၾကည့္ခ်င္တဲ့အတြက္ က်ေနာ္တို႔ Result PictureBox ကို PanelBox ထဲမွာ ထည့္ထားတာပဲျဖစ္ပါတရ္ဗ်ာ။ Panel ကို AutoScroll = True ေပးထားတာကေတာ့ က်ေနာ္တို႔ Result PictureBox ဘရ္ေလာက္ပဲ Size ႀကီးႀကီး PanelBox ရဲ႕ Width and Height အထဲမွာပဲ ေဖာ္ျပေစၿပီး Scroll ဆြဲၾကည့္ႏိုင္ေအာင္ပါ။ နားလည္ၾကမရ္လို႔ ေမ်ာ္လင့္ပါတရ္ဗ်။
ဆက္လက္ၿပီး Calling Method ကို ေလ့လာၾကည့္ပါ့မရ္။ btnProcessing Button ကို D_Click ေပးၿပီး ေအာက္ပါ Coding ကို ေရးယူလိုက္ပါဗ်ာ။ အနည္းငရ္ ရွင္းျပပါ့မရ္။ စစခ်င္းမွာ TextBox ေတြက ရိုက္ထည့္လာတဲ့ ကိန္းဂဏန္းေတြကို int အျဖစ္ဖမ္းပါတရ္။ ဘာလို႔လည္းဆိုေတာ့ TextBox Input မွန္သမွ်ကို VS C# က Default String အေနနဲ႔ သတ္မွတ္ထားလို႔ပါ။ ေနာက္ Result ထုတ္ျပေပးမရ္႕ PictureBox ရဲ႕ Width and Height ကို User ရိုက္ထည့္လိုက္တဲ့ Width and Height အတိုင္း Assign လုပ္လိုက္ပါတရ္။ ေနာက္တစ္ဆင့္မွာေတာ့ ResizeImage() Method ကုိေခၚၿပီး Image ကုိ Re-size လုပ္ယူပါတရ္။ ၿပီးရင္ေတာ့ PictureBox ေပၚမွာ တခါတည္း ေဖာ္ျပလိုက္တာပဲ ျဖစ္ပါတရ္။ ေနာက္ဆံုးတစ္ေၾကာင္းကေတာ့ က်ေနာ္တို႔ ေျပာင္းလဲယူလိုက္တဲ့ Image Resolution ကို သိႏိုင္ေအာင္ Display ျပထားတာပါ။ Coding ခက္ခက္ခဲခဲေတြ မပါလို႔ နားလည္ၾကလိမ့္မရ္လို႔ ထင္ပါတရ္ဗ်။ Coding အျပည့္အစံုကို ေအာက္မွာ ၀င္ေရာက္ေလ့လာႏိုင္ပါတရ္ဗ်ာ။ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမအားလံုး ေလ့လာျခင္းျဖင့္ ေက်နပ္ႏိုင္ၾကပါေစ။

မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမမ်ား အားလံုးပဲ မဂၤလာပါဗ်ာ။ ဒီေန႔ေတာ့ က်ေနာ္တို႔ Visual Studio C# ကို အသံုးျပဳၿပီး Image Processing အတြက္ pixel နဲ႔ Color တန္ဖိုးေတြကို လ်င္လ်င္ျမန္ျမန္ Detect လုပ္ႏိုင္မရ္႕ Lockbits( ), Unsafe context and Parallel.For Mehtod အေၾကာင္းေလးကို ေလ့လာၾကည့္ပါ့မရ္။ Processing အတြက္ကိုေတာ့ ေရွ႕သင္ခန္းစာမွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့ Image Color Channel ခြဲထုတ္တာကိုပဲ သံုးထားပါတရ္။ ဒီေကာင္ကေတာ့ က်ေနာ္တို႔ ေလ့လာၾကည့္မရ္႕ Fast Image Porcessing Method ေတြက ေလးခုေျမာက္ Method ျဖစ္ပါတရ္။ ေရွ႕မွာ Method ႏွစ္ခု ေလ့လာခဲ့ၿပီးျဖစ္လို႔ ဒီသင္ခန္းစာက နားလည္ရ လြယ္သြားမရ္လို႔ ထင္ပါတရ္။ ေရွ႕မွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့...
  • (၁) Processing using GetPixel and SetPixel
  • (၂) Processing using Bitmap.Lockbits and Marshal.Copy
  • (၃) Processing using Bitmap.Lockbits and Direct Memory Accress in Unsafe context Method
Method သံုးခုထက္စာရင္ Processing Time ပိုျမန္ပါတရ္ဗ်။ က်ေနာ့္ ေလ့က်င့္ခန္းေတြကေတာ့ စတင္ ေလ့လာသူမ်ားအတြက္သာ ရည္ရြယ္ပါတရ္။ ဒါေၾကာင့္ သိေနၿပီးသား သူမ်ားအေနနဲ႔ က်ေနာ္ရဲ႕ တင္ျပပံုမွားယြင္းတာမ်ား ရွိခဲ့ရင္ နားလည္ေပးၾကဖို႔နဲ႔ ျပန္လည္ေထာက္ျပေပးႏိုင္ဖို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။
ဒီသင္ခန္းစာကေတာ့ ေရွ႕မွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့ Lockbits() and Unsafe context Method ကို Paraller.For() Method နဲ႔ တြဲဘက္အသံုးျပဳၿပီး ေလ့လာမွာျဖစ္ပါတရ္။ Unsafe နဲ႔ တြဲသံုးမွာျဖစ္လို႔ အရင္သင္ခန္းစာမွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့ Unsafe Configuration ကို လုပ္ဖို႔ေတာ့ လိုပါလိမ့္မရ္ဗ်။ ဒီမွာ Processing Method ကို မေလ့လာမွီ က်ေနာ္အေနနဲ႔ Parallel.For() Method နဲ႔ ပါတ္သတ္ၿပီး အနည္းငရ္ ေဆြးေႏြးေပးစရာေလး ရွိေနပါတရ္။ Parallel.For ဆိုတာကေတာ့ For Loop အစားအသံုးျပဳႏိုင္မရ္႕ Threading Library ေအာက္က Parallel Method တစ္ခုျဖစ္ပါတရ္။ သူ႔ကို အသံုးျပဳမရ္ဆိုရင္ေတာ့ using System.Threading.Tasks; ဆိုတဲ့ Library File ကို ေၾကျငာေပးရပါလိမ့္မရ္။ Parallel.For အတြက္ အသံုးျပဳႏိုင္မရ္႕ Function Overload ပံုစံေတြအမ်ားႀကီးပါ။ ဒီထဲကမွ က်ေနာ္တို႔ ေလ့လာၾကည့္မရ္႕ ပံုစံကေတာ့...
Parallel.For(Int32, Int32, Action)
အခုလိုပံုစံ ျဖစ္ပါတရ္။ ပထမ Parameter ကေတာ့ Loop အတြက္ စမွတ္(First Index)ပါ။ ဒုတိယကေတာ့ ဆံုးမွတ္(Last Index)ျဖစ္ပါတရ္။ ေနာက္ဆံုး parameter ကေတာ့ Parallel.For အတြက္ အေရးအႀကီးဆံုး Processing ေတြလုပ္မရ္႕ Action ျဖစ္ပါတရ္။ Action ကို Lambda Expressions ပံုစံနဲ႔ သံုးပါတရ္။ ဥပမာ က်ေနာ္တို႔ ကိန္းစဥ္တန္းတစ္ခုကို ေပါင္းတာနဲ႔ ေလ့လာၾကည့္ပါ့မရ္။ ေအာက္ကေတာ့ က်ေနာ္တို႔ သမာရိုးက် For Loop အသံုးပါ။ ဒါကို က်ေနာ္တို႔ Parallel.For() ကို အသံုးျပဳၿပီး ေရးမရ္ဆိုရင္ေတာ့... Parallel.For ရဲ႕ Loop Step ကလည္း For မွာ i++ ေပးသလို 1 step ပါပဲ။ ရရွိလာတဲ့ Result တူေနေပမဲ့ Processing Time ကိုၾကည့္မရ္ဆိုရင္ေတာ့ အရမ္းႀကီး ကြာဟေနမွာ ေတြ႔ရပါလိမ့္မရ္။ ဒါက အလြယ္ကူဆံုး ဥပမာေပးတာပါ။ ဒီေလာက္ Processing အတြက္ကိုေတာ့ Parallel ကို သံုးစရာမလိုပါဘူး။ မလိုအပ္ပဲ Parallel သံုးျဖစ္အားျဖစ္ CPU instruction ကိုရႈပ္ေထြးသြားေစႏိုင္ပါတရ္။ သိန္း၊ သန္း မက်ေသာ Items Processing ေတြအတြက္ ရည္ရြယ္တဲ့အတြက္ Big Data Processing ေတြမွာ အရမ္းကို အသံုး၀င္ပါတရ္ဗ်။ ကဲ... ဒီေလာက္ဆိုရင္ေတာ့ Parallel.For() Method ရဲ႕ အလုပ္လုပ္ပံုကို နားလည္သြားၿပီလို႔ ထင္ပါတရ္။
ဒီေန႔ ေလ့က်င့္ခန္းအတြက္ GUI နဲ႔ Tools ေတြရဲ႕ Properties ေတြကိုေတာ့ အရင္အတိုင္းပဲ ျပန္သံုးထားပါတရ္။ cbProcessingMethod မွာ Lockbits(), Unsafe and Parallel.For Method အတြက္ Item တိုးေပးဖို႔နဲ႔ Calling Method က Index No ကို ျပန္ေခၚယံုပါပဲဗ်ာ။ အခုမွ စတင္ေလ့လာမိသူမ်ားအေနနဲ႔ ေရွ႕က သင္ခန္းစာေတြကို ျပန္ဖတ္ေစခ်င္ပါတရ္။ ဒါကေတာ့ က်ေနာ္တို႔ Project မွာ အသံုးျပဳမရ္႕ Library File ေတြပါ။ ကဲ... ေအာက္မွာ Lockbits(), Unsafe and Parallel.For Method ေလးကို ေလ့လာၾကည့္လိုက္ၾကပါအံုးဗ်ာ။ ရွင္းျပရမရ္ဆိုရင္ေတာ့... Coding စစခ်င္းမွာ Unsafe keyword ကို “{....}” နဲ႔ စထားပါတရ္။ Processing လုပ္မရ္႕ Coding ေတြထဲမွာ Unsafe context ေတြကို သံုးပါ့မရ္ဆိုတဲ့ သေဘာပါ။
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. Store Scaning Data in Pointer Direct Memory
byte* ptrFirstPixel = (byte*)bmpData.Scan0;
ဒီေကာင္ကေတာ့ lock ခ်ထားတဲ့ Image ႀကီးတစ္ခုလံုးကို Scan ဖတ္လိုက္တာပါ။ Scan0 ရဲ႕ သေဘာကေတာ့ Target Image ရဲ႕ left-top corner ကေနၿပီး right-buttom အထိ တစ္ဆင့္ခ်င္းစီ Scan ဖတ္သြားမွာပါ။ ေရွ႕သင္ခန္းစာမွာတုန္းက byte[]array ကိုအသံုးျပဳၿပီး 2-Dimension ကို 1-Dimension ေျပာင္းယူပါတရ္။ ဒီသင္ခန္းစာမွာေတာ့ byte* Pointer ကို အသံုးျပဳၿပီးေတာ့ Memory Address ထဲကို တိုက္ရိုက္သြားသိမ္းပါတရ္ဗ်။ ေနာက္တစ္ဆင့္မွာေတာ့ မိမိလိုခ်င္တဲ့ Memory Address ကုိ တိုက္ရိုက္ညႊန္းၿပီး Processing လုပ္ယူမွာ ျဖစ္ပါတရ္ဗ်ာ။

5. Pre-Processing Get Pixel Processing လုပ္သြားတဲ့ ပံုစံကေတာ့ Stride by Stride အလုပ္ လုပ္သြားတာပါ။ ပထမ loop က Stride (or) Row ကို မွတ္သားခ်င္တဲ့အတြက္ပါ။ ဒီမွာ Parallel.For() Loop ကို အသံုးျပဳထားပါတရ္။ အလုပ္လုပ္သြားတဲ့ ပံုစံကိုေတာ့ အေပၚမွာ ဥပမာနဲ႔ ရွင္းျပခဲ့ၿပီးျဖစ္လို႔ နားလည္ၾကလိမ့္မရ္လို႔ ထင္ပါတရ္။ ဒီမွာ ptrFirstPixel + (y * bmpData.Stride)လုပ္ၿပီး row ဖတ္ထားတာကေတာ့ Stride တစ္ေၾကာင္း Processing လုပ္ၿပီးတိုင္း ေနာက္တစ္ၾကာင္းကို ကူးႏိုင္ရန္ Memory Address ေနရာကို ညႊန္းယူတာပါ။ Pointer နဲ႔ Memory Address ေတြျဖစ္လို႔ Trace လိုက္ၿပီး ရွင္းဖိ႔ုမလြယ္ပါဘူး။ ပံုေသနည္းလို႔ မွန္ထားရင္လည္း အဆင္ေျပပါတရ္။ အၾကမ္းဖ်ဥ္း ေျပာရရင္ေတာ့ Stride တစ္ေၾကာင္းလံုးမွာရွိတဲ့ Pixel and Color Value ေတြကို Address တစ္ခုစီမွာ သိမ္းပါတရ္။ ဥပမာ- Stride 5 ေၾကာင္းရွိရင္ Address ငါးခုရွိတရ္ဆိုတဲ့ သေဘာပါ။ Stride ဆိုတာကေတာ့ row တစ္ row စီမွာရွိတဲ့ byte အေရအတြက္ပါ။ Stride = bytesPerPixel * inputImg.Height; လို႔ ေရွ႕သင္ခန္းစာမွာ ရွင္းျပခဲ့ၿပီးပါၿပီ။ ဒုတိယ loop ကေတာ့ Stride ရဲ႕ byte Index ေတြထဲမွာရွိတဲ့ BGRA တန္ဖိုးေတြကို ဆြဲထုတ္ႏိုင္ေအာင္ပါ။ currentAddress ဆိုတာကေတာ့ Memory Address ပါ။ ဒါေၾကာင့္ loop တခါပါတ္တိုင္း Memory Address ထဲကို တိုက္ရိုက္သြားဖတ္ၿပီး BGRA တန္ဖိုးေတြကို ဆြဲထုတ္ယူႏိုင္တာပဲ ျဖစ္ပါတရ္။ x = x + bytePP ေပးထားတာကေတာ့ BGRA ေလးလံုး တစ္ Group ျဖစ္တဲ့အတြက္ Group ခုန္သြားႏိုင္ေအာင္ပါ။ currentAddress[x]; currentAddress[x + 1]; currentAddress[x + 2]; ဒီေကာင္ေတြကေတာ့ Group တစ္ခါခုန္တိုင္း BGRA တန္ဖိုးေတြကို asign လုပ္ႏိုင္ေအာင္ပါ။ ဒီေလ့က်င့္ခန္းမွာေတာ့ Alpha တန္ဖိုးကို က်ေနာ္ ခ်န္လွန္ထားခဲ့ပါတရ္။ စားေရးၿပီး ရွင္းျပရ တာေတာ့ အနည္းငယ္ ခဲယဥ္းပါတရ္ဗ်ာ။ မိတ္ေဆြတို႔ Trace လုိက္ၾကည့္ရင္ေတာ့ Coding Flow ရဲ႕ အလုပ္ လုပ္သြားပံုကို ေသခ်ာနားလည္သြားမွာပါ။

6. Processing Color Channel ဒီေကာင္ကေတာ့ Color Channel အတြက္ Processing လုပ္ထားတာပါ။ ရိုးရိုးရွင္းရွင္းပါပဲ။ မိမိ ေျပာင္းလဲ ခ်င္ေသာ Color Channel ကိုခ်န္ၿပီး က်န္ Channel ေတြကို သုည(0) assign လုပ္လိုက္တာပါ။ နားလည္မရ္လို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။

7. Unlock bitmap
cloneImg.UnlockBits(bmpData);
ဒါကေတာ့ Processing လုပ္ငန္းစဥ္ အားလံုးၿပီးသြားၿပီျဖစ္တဲ့အတြက္ Lock ခ်ထားတဲ့ Image ကို unLock ျပန္လုပ္တာပါ။

8. 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(), Unsafe context and Parallel.For Method ကို အသံုးျပဳၿပီး Processing လုပ္တတ္ၿပီလို႔ ထင္ပါတရ္ဗ်ာ။ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမအားလံုး ေလ့လာျခင္းျဖင့္ ေက်နပ္ႏိုင္ၾကပါေစ။