使用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;每行的數據量爲17*3=51,系統將其自動封裝一個字節,所以它的stride爲52byte(或13*4byte)。對於一幅17像素寬的4位索引圖,其stride爲12,其中9byte(準確地說是8.5個byte)用來記錄數據信息,每行再自動添加3(3.5)個byte保證其爲4的整數倍。
   具體數據的分佈因其pixelformat而異。24位深的圖像每隔3個byte包含一組RGB信息;32位深的圖像每隔4個byte包含一組RGBA信息。那些每個字節包含多個像素的pixelformat,比如4位索引圖像或1位索引圖像,必須經過仔細處理,從而保證同一字節中的相鄰byte不會混淆。
指針的準確定位
  • 32位RGB:假設X、Y爲位圖中像素的座標,則其在內存中的地址爲scan0+Y*stride+X*4。此時指針指向藍色,其後分別是綠色、紅色,alpha分量。
  • 24位RGB:scan0+Y*stride+X*3。此時指針指向藍色,其後分別是綠色和紅色。
  • 8位索引:scan0+Y*stride+X。當前指針指向圖像的調色盤。
  • 4位索引:scan0+Y*stride+(X/2)。當前指針所指的字節包括兩個像素,通過高位和低位索引16色調色盤,其中高位表示左邊的像素,低位表示右邊的像素。
  • 1位索引:scan0+Y*stride+X/8。當前指針所指的字節中的每一位都表示一個像素的索引顏色,調色盤爲兩色,最左邊的像素爲8,最右邊的像素爲0。

像素間使用迭代器

   下面這個範例將一幅32位深的圖像中所有像素的藍色分量設爲最大(255):

     

     

  1. BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10),     
  2.            System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat);  
  3.   
  4.       int PixelSize=4;  
  5.   
  6.    
  7.   
  8.       for(int y=0; y<bmd.Height; y++)  
  9.   
  10.       {  
  11.   
  12.         byte* row=(byte *)bmd.Scan0+(y*bmd.Stride);  
  13.   
  14.         for(int x=0; x<bmd.Width; x++)  
  15.   
  16.         {  
  17.   
  18.           row[x*PixelSize]=255;  
  19.   
  20.         }  
  21.   
  22.       }  



     處理4位索引圖,高低位應分開處理,代碼如下:

   

  1. int offset = (y * bmd.Stride) + (x >> 1);  
  2.   
  3.    bytecurrentByte = ((byte *)bmd.Scan0)[offset];  
  4.   
  5.    if((x&1)== 1)  
  6.   
  7.    {  
  8.   
  9.      currentByte&= 0xF0;  
  10.   
  11.      currentByte|= (byte)(colorIndex & 0x0F);  
  12.   
  13.    }  
  14.   
  15.    else  
  16.   
  17.    {  
  18.   
  19.      currentByte&= 0x0F;  
  20.   
  21.      currentByte|= (byte)(colorIndex << 4);  
  22.   
  23.    }  
  24.   
  25.    ((byte*)bmd.Scan0)[offset]=currentByte;  

 

     處理1位索引的代碼:

     

  1. byte* p=(byte*)bmd.Scan0.ToPointer();  
  2.   
  3.      intindex=y*bmd.Stride+(x>>3);  
  4.   
  5.      bytemask=(byte)(0x80>>(x&0x7));  
  6.   
  7.      if(pixel)  
  8.   
  9.        p[index]|=mask;  
  10.   
  11.      else  
  12.   
  13.        p[index]&=(byte)(mask^0xff);  

 

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章