詳細(xì)介紹C# BitmapData
最近要轉(zhuǎn)開發(fā)平臺(tái),正研究C#。C#好是好,不過(guò)處理圖片時(shí)一個(gè)像素一個(gè)像素的操作像素不是一般的慢。其實(shí)Delphi也一樣,但好在Delphi的Bitmap類提供了ScanLines,可以一行一行的讀圖,效率比較高。C#應(yīng)該也有類似的東東。經(jīng)過(guò)一番搜索,終于發(fā)現(xiàn)了C# BitmapData類。
先看個(gè)例子,這是對(duì)一張位圖的每個(gè)像素按FF取補(bǔ),然后輸出到一個(gè)新圖(代碼有點(diǎn)啰嗦,不過(guò)應(yīng)該可以說(shuō)明問(wèn)題了)。
- int h = m_Bmp.Height;
- int w = m_Bmp.Width;
- Bitmap bmpOut = new Bitmap(w, h, PixelFormat.Format24bppRgb);
- BitmapData dataIn = m_Bmp.LockBits(new Rectangle(0,0,w,h),
ImageLockMode.ReadOnly,PixelFormat.Format24bppRgb);- BitmapData dataOut = bmpOut.LockBits(new Rectangle(0, 0, w, h),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);- unsafe
- {
- byte* pIn = (byte*)(dataIn.Scan0.ToPointer());
- byte * pOut = (byte*)(dataOut.Scan0.ToPointer());
- for (int y = 0; y < dataIn.Height; y++)
- {
- for (int x = 0; x < dataIn.Width; x++)
- {
- pOut[0] = (byte)(255 - pIn[0]);
- pOut[1] = (byte)(255 - pIn[1]);
- pOut[2] = (byte)(255 - pIn[2]);
- pIn += 3;
- pOut += 3;
- }
- pIn += dataIn.Stride - dataIn.Width * 3;
- pOut += dataOut.Stride - dataOut.Width * 3;
- }
- }
- bmpOut.UnlockBits(dataOut);
- m_Bmp.UnlockBits(dataIn);
貌似比Delphi復(fù)雜得多,難道我真的天生對(duì)指針過(guò)敏?還是Delphi的比較好理解,就是掃描每一行,然后對(duì)當(dāng)前像素點(diǎn)的三個(gè)分量做處理,非常方便。而且C#代碼中的Stride是個(gè)什么東東?
查找了不少資料,現(xiàn)在我是這么理解的:假設(shè)有一張圖片寬度為6,因?yàn)槭荈ormat24bppRgb格式(每像素3字節(jié)。在以下的討論中,除非特別說(shuō)明,否則Bitmap都被認(rèn)為是24位RGB)的,顯然,每一行需要6*3=18個(gè)字節(jié)存儲(chǔ)。對(duì)于Bitmap就是如此。但對(duì)于C# BitmapData,雖然BitmapData.Width還是等于Bitmap.Width,但大概是出于顯示性能的考慮,每行的實(shí)際的字節(jié)數(shù)將變成大于等于它的那個(gè)離它最近的4的整倍數(shù),此時(shí)的實(shí)際字節(jié)數(shù)就是Stride.就此例而言,18不是4的整倍數(shù),而比18大的離18最近的4的倍數(shù)是20,所以這個(gè)BitmapData.Stride = 20.顯然,當(dāng)寬度本身就是4的倍數(shù)時(shí),BitmapData.Stride = Bitmap.Width * 3.畫個(gè)圖可能更好理解。R、G、B 分別代表3個(gè)原色分量字節(jié),BGR就表示一個(gè)像素。為了看起來(lái)方便我在每個(gè)像素之間插了個(gè)空格,實(shí)際上是沒有的。X表示補(bǔ)足4的倍數(shù)而自動(dòng)插入的字節(jié)。為了符合人類的閱讀習(xí)慣我分行了,其實(shí)在計(jì)算機(jī)內(nèi)存中應(yīng)該看成連續(xù)的一大段。
現(xiàn)在應(yīng)該很好理解了。首先用 BitmapData.Scan0找到第0個(gè)像素的第0個(gè)分量的地址。
這個(gè)地址指向的是個(gè)byte類型,所以當(dāng)時(shí)定義為byte* pIn。
行掃描時(shí),在當(dāng)前指針位置(不妨看成當(dāng)前像素的第0個(gè)顏色分量)連續(xù)取出三個(gè)值(3個(gè)原色分量。注意,0 1 2代表的次序是B G R。在取指針指向的值時(shí),貌似p[n]和p += n再取p[0]是等價(jià)的),然后下移3個(gè)位置(pIn += 3,看成指到下一個(gè)像素的第0個(gè)顏色分量)。做過(guò)Bitmap.Width次操作后,就到達(dá)了Bitmap.Width * 3的位置,應(yīng)該要跳過(guò)圖中標(biāo)記為X的字節(jié)了(共有Stride - Width * 3個(gè)字節(jié)),代碼中就是 pIn += dataIn.Stride - dataIn.Width * 3;
跳過(guò)以后指針就到達(dá)下行的第0個(gè)像素了。按照此算法,一共需要做Bitmap.Height次行掃描(代碼就是 for (int y = 0; y < dataIn.Height; y++))。
另外,因?yàn)槭褂昧藆nsafe,所以編譯的時(shí)候需要設(shè)置“允許不安全的代碼”。以上介紹C# BitmapData
【編輯推薦】

















