1. 程式人生 > >C# Bitmap與BitmapData理解

C# Bitmap與BitmapData理解

使用lockbits方法處理影象

許多影象處理任務即時是最簡單的檔案型別轉換,例如從32位深度到8位深度的格式轉化,直接獲得畫素陣列要比使用GetPixel和SetPixel等方法的效率高得多。

你可能會發現DotNet採用託管機制,大多數情況下微軟會推薦你使用託管程式碼,理由是便捷和安全。實際應用中,直接操作記憶體中的資料塊是很少見的,儘管如此,影象處理恰恰是這類為數不多的情況之一,因為使用託管程式碼的效率低的難以忍受,特別是對巨幅影象來說,在此,我們討論一下一種新的方法。

如何使用非託管程式碼是因語言而異的,在C#中我們可以通過unsafe關鍵字來呼叫指標,從而直接操作記憶體中的點陣圖資料;VB則使用Marshal類中的方法,它會導致一部分的效能損失,因此效率不如前者。

鎖定位元流

Bitmap類使用LockBits和UnLockBits方法來將點陣圖的資料矩陣儲存在記憶體中直接對它進行操作最後用修改後的資料代替點陣圖中的原始資料。LockBits返回以各BitmapData的類用已描述資料在已鎖定的矩陣中的位置和分佈。

BitmapData類包括以下幾個重要的屬性:

  • Scan0:資料矩陣在記憶體中的地址。
  • Stride:資料矩陣中的行寬,以byte為單位。可能會擴充套件幾個Byte,後面會介紹。
  • PixelFormat:畫素格式,這對矩陣中位元組的定位很重要。
  • Width:點陣圖的寬度。
  • Height:點陣圖的高度。 具體關係見下圖: 在這裡插入圖片描述

如上圖所示,stride屬性表示點陣圖資料矩陣的行寬

以byte為單位。出於效率考慮,矩陣的行寬並非剛好是每行畫素數的整數倍,系統往往會將其封裝成4的整數倍。舉例來說,對於一幅24位深17畫素寬的影象,其stride屬性為52;每行的資料量為173=51,系統將其自動封裝一個位元組,所以它的stride為52byte(或134byte)。對於一幅17畫素寬的4位索引圖,其stride為12,其中9byte(準確地說是8.5個byte)用來記錄資料資訊,每行再自動新增3(3.5)個byte保證其為4的整數倍。 具體資料的分佈因其pixel format而異。24位深的影象每隔3個byte包含一組RGB資訊;32位深的影象每隔4個byte包含一組RGBA資訊。那些每個位元組包含多個畫素的pixel format,比如4位索引影象或1位索引影象,必須經過仔細處理,從而保證同一位元組中的相鄰byte不會混淆。 指標的準確定位 32位RGB:假設X、Y為點陣圖中畫素的座標,則其在記憶體中的地址為scan0+Ystride+X
4。此時指標指向藍色,其後分別是綠色、紅色,alpha分量。 24位RGB:scan0+Ystride+X3。此時指標指向藍色,其後分別是綠色和紅色。 8位索引:scan0+Ystride+X。當前指標指向影象的調色盤。 4位索引:scan0+Ystride+(X/2)。當前指標所指的位元組包括兩個畫素,通過高位和低位索引16色調色盤,其中高位表示左邊的畫素,低位表示右邊的畫素。 1位索引:scan0+Y*stride+X/8。當前指標所指的位元組中的每一位都表示一個畫素的索引顏色,調色盤為兩色,最左邊的畫素為8,最右邊的畫素為0。 畫素間使用迭代器 下面這個範例將一幅32位深的影象中所有畫素的藍色分量設為最大(255): BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat); int PixelSize=4;

  for(int y=0; y<bmd.Height; y++)
  {
    byte* row=(byte *)bmd.Scan0+(y*bmd.Stride);
    for(int x=0; x<bmd.Width; x++)
    {
      row[x*PixelSize]=255;
    }
  }

  處理4位索引圖,高低位應分開處理,程式碼如下:
  int offset = (y * bmd.Stride) + (x >> 1);
  byte currentByte = ((byte *)bmd.Scan0)[offset];
  if((x&1) == 1)
  {
    currentByte &= 0xF0;
    currentByte |= (byte)(colorIndex & 0x0F);
  }
  else
  {
    currentByte &= 0x0F;
    currentByte |= (byte)(colorIndex << 4);
  }
  ((byte *)bmd.Scan0)[offset]=currentByte;

  處理1位索引的程式碼:
  byte* p=(byte*)bmd.Scan0.ToPointer();
  int index=y*bmd.Stride+(x>>3);
  byte mask=(byte)(0x80>>(x&0x7));
  if(pixel)
    p[index]|=mask;
  else
    p[index]&=(byte)(mask^0xff);

  最後在進行完所有處理後馬不要忘記使用Unlockbits命令解鎖。