1. 程式人生 > >使用GDI+點陣圖資料掃描線處理影象的小技巧

使用GDI+點陣圖資料掃描線處理影象的小技巧

   在GDI+影象處理中,我們經常利用BitmapData結構對影象資料掃描線進行操作,在我的大部分BOLG文章中,都使用了這個方法。GDI+點陣圖通過其LockBits方法和UnlockBits方法,分別用來鎖定(獲取)和解鎖(釋放)BitmapData資料,我們一般都在這2個方法之間操作影象資料掃描線,如:

  1. Bitmap *bmp = new Bitmap(L"d://001-1.jpg");  
  2. BitmapData data;  
  3. Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());  
  4.         // 鎖定為32位畫素格式
  5. bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, &data);  
  6. // 這裡對影象資料掃描線操作,由於bmp已經鎖定,很多方法不能呼叫
  7. bmp->UnlockBits(&data); // 解鎖
  8. Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);  
  9. g->DrawImage(bmp, 0, 0);  
  10. delete g;  
  11. delete bmp;  

   由於在LockBits方法和UnlockBits方法之間,點陣圖物件是鎖定的,很多方法無法呼叫,有時也感到有些不方便,甚至繁瑣。比如對影象資料掃描線進行多次處理,在處理過程中想分步驟顯示或者儲存時,就不得不反覆呼叫這2個方法;還有就是點陣圖格式低於24位格式的影象無法鎖定為24位或32位資料進行操作(我們大多利用24位或者32位畫素掃描線進行影象處理)等等。

  可以使用一些小技巧來規避因點陣圖物件鎖定而帶來的不方便,也可對低於24位格式的影象進行24位或32點陣圖像資料掃描線操作。請看下面的例子:

  1. Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);  
  2. Bitmap *bmp = new Bitmap(L"d://001-1.jpg");  
  3. BitmapData data;  
  4. Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());  
  5. // 預先設定掃描線長度和影象資料
  6. data.Stride = r.Width * 4;  
  7. data.Scan0 = (void*)newchar[r.Height * data.Stride];  
  8. // 建立一個32位畫素格式的自定義資料點陣圖物件
  9. Bitmap *bmp2 = new Bitmap(r.Width, r.Height, data.Stride,  
  10.     PixelFormat32bppARGB, (BYTE*)data.Scan0);  
  11. // 使用ImageLockModeRead標記使bmp影象資料拷貝到data.Scan0。
  12. // 使用ImageLockModeUserInputBuf標記鎖定影象bmp,使bmp和bmp2共享影象資料。
  13. // 使用ImageLockModeWrite標記使bmp和bmp2同步,即影象資料處理同時
  14. // 作用於bmp和bmp2,去掉ImageLockModeWrite後,bmp影象不改變,
  15. // 低於24位格式的影象必須去掉ImageLockModeWrite
  16. bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite | ImageLockModeUserInputBuf,  
  17.     PixelFormat32bppARGB, &data);  
  18. // 這裡可分步操作影象掃描線,同時也可顯示bmp2,或者呼叫bmp2的任何方法
  19. ImageGray(&data); // 影象灰度化,具體過程略
  20. g->DrawImage(bmp2, 0, 0); // 畫影象(或者其它方法呼叫)
  21. ImageTwoValues(&dada, 127); // 影象二值化,具體過程略
  22. g->DrawImage(bmp2, 200, 0); // 畫影象(或者其它方法呼叫)
  23. bmp->UnlockBits(&data);  
  24. delete g;  
  25. delete bmp;  
  26. delete bmp2;  
  27. delete[] data.Scan0; // 必須釋放

    上面例子程式碼中作了較詳細的說明,就不再解釋。

    上面的例子為了解釋點陣圖物件共享和資料處理同步,程式碼顯得有些凌亂,其實只要記住一點:例子中,自定義點陣圖物件bmp2通過bmp->LockBits方法取得資料後,如果無特殊需要,bmp就可解鎖甚至delete,這時也不再需要對bmp2鎖定,就可通過對data的處理,達到對bmp2包含的影象資料進行改變的目的。

    將上面程式碼重新規劃一下,使之清晰一些:

  1. BOOL GetBitmapData(WCHAR *fileName, PixelFormat pixelFormat, BitmapData *data)  
  2. {  
  3.     Bitmap *bmp = new Bitmap(fileName);  
  4.     if (bmp->GetLastStatus() != Ok)  
  5.         return FALSE;  
  6.     Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());  
  7.     UINT pixelSize = GetPixelFormatSize(pixelFormat);  
  8.     data->Stride = ((pixelSize * r.Width + 31) & 0xffffffe0) >> 3;  
  9.     data->Scan0 = (void*)newchar[r.Height * data->Stride];  
  10.     bmp->LockBits(&r, ImageLockModeRead | ImageLockModeUserInputBuf,  
  11.         pixelFormat, data);  
  12.     bmp->UnlockBits(data);  
  13.     delete bmp;  
  14.     return TRUE;  
  15. }  
  16. void __fastcall TForm2::Button1Click(TObject *Sender)  
  17. {  
  18.     // 取得影象的24位畫素格式資料
  19.     BitmapData data;  
  20.     if (!GetBitmapData(L"d://001-1.jpg", PixelFormat24bppRGB, &data))  
  21.         return;  
  22.     // 建立一個24位畫素格式的自定義資料點陣圖物件
  23.     Bitmap *bmp = new Bitmap(data.Width, data.Height, data.Stride,  
  24.         data.PixelFormat, (BYTE*)data.Scan0);  
  25.     Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);  
  26.     // 這裡可操作影象資料掃描線,不必再鎖定bmp,同時也可顯示bmp,或者呼叫bmp的任何方法
  27.     ImageGray(&data);           // 影象灰度化,具體過程略
  28.     g->DrawImage(bmp, 0, 0);     // 畫影象(或者其它方法呼叫)
  29.     ImageTwoValues(&dada, 127); // 影象二值化,具體過程略
  30.     g->DrawImage(bmp, 200, 0);   // 畫影象(或者其它方法呼叫)
  31.     delete g;  
  32.     delete bmp;  
  33.     delete[] data.Scan0; // 必須釋放
  34. }  

    通過上面程式碼,就可以看出呼叫GetBitmapData後,所有的影象資料資訊就已經包含在BitmapData結構中了,所以我們可以對這個資料結構進行任何的操作,而不再依賴任何GDI+物件,由此避免了本文前面所說的不方便。之所以又建立一個自定義資料點陣圖物件,只是要藉助它進行影象顯示、儲存等操作而已。

    利用類似於前面的例子程式碼還可以進行拼圖操作:

  1. Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);  
  2. Bitmap *bmp = new Bitmap(L"d://001-1.jpg");  
  3. BitmapData data;  
  4. Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());  
  5. // 預先設定掃描線長度和影象資料,掃描線為2個影象的寬度
  6. data.Stride = r.Width * 2 * 4;  
  7. data.Scan0 = (void*)newchar[r.Height * data.Stride];  
  8. // 拷貝影象到左邊,注意這裡只是讀取資料,所以ImageLockModeWrite沒使用
  9. bmp->LockBits(&r, ImageLockModeRead | ImageLockModeUserInputBuf,  
  10.     PixelFormat32bppARGB, &data);  
  11. bmp->UnlockBits(&data);  
  12. // 拼合影象到右邊,為簡化,使用同一張圖。
  13. // 如果影象大小不同,只要r的尺寸一致(不能大於原圖)
  14. (char*)data.Scan0 += (r.Width * 4); // 掃描線地址向右移動
  15. bmp->LockBits(&r, ImageLockModeRead | ImageLockModeUserInputBuf,  
  16.     PixelFormat32bppARGB, &data);  
  17. bmp->UnlockBits(&data);  
  18. (char*)data.Scan0 -= (r.Width * 4); // 還原掃描線地址
  19.         data.Width = r.Width * 2;  // 重新設定拼圖寬度
  20.       // 拼合圖資料資訊已經包含在data中了,可以繼續對data進行任何的操作
  21. // 建立一個自定義資料點陣圖物件
  22. Bitmap *bmp2 = new Bitmap(data.Width, data.Height, data.Stride,  
  23.     data.PixelFormat, (BYTE*)data.Scan0);  
  24. // 顯示拼合後的影象。
  25. g->DrawImage(bmp2, 0, 0);  
  26. delete g;  
  27. delete bmp;  
  28. delete bmp2;  
  29. delete[] data.Scan0; // 必須釋放