2009年3月5日 星期四

我對 Scanline

一點點看法 看過就好

首先要說明的是,在講義 1.2.2 儲存影像陣列裡的
第一部分程式碼 (page.14)

 TCanvas *csBuf=new TCanvas;
 HDC hDC=GetDC(Panel->Handle);
 csBuf->Handle=hDC;

 Graphics::TBitmap*Source=new Graphics::TBitmap();

 Source->Width=Panel->Width;

 Source->Height=Panel->Height;

 Source->Canvas->CopyRect(Rect(0, 0, Source->Width, Source->Height),

              csBuf,
              Rect(0, 0, Panel->Width, Panel->Height));
 Image->Picture->Bitmap->Assign(Source);


 for(i=0;i<BitmapInfoHead.biHeight;i++)
 {
  ptr=(Byte *) Image->Picture->Bitmap->ScanLine[i];
  for(k=0,j=0;j<BitmapInfoHead.biWidth;j++)
  {
   background[i][j][0] =ptr[k+2];
   background[i][j][1]= ptr[k+1];
   background[i][j][2] =ptr[k];
   k+=3;
  }
 }
只要key in藍色部分,就會顯示出來一張圖了!!
顯示圖片靠 Image->Picture->Bitmap->Assign(Source);裡的Assign( )函數
(我覺得這範例寫得不好,傳回給source會影響截圖計算,
可能會給初學者錯誤的觀點,所以我改成background

而且範例需要修改正確 ... 講義上都是不完整的code)

至於使用Scanline的部分正如敘述所說,是將像素值輸入到陣列中
是給後續使用的,跟顯示影像無關
如果你只是想顯示一張擷取的影像,請不要理後續Scanline的部分

這樣瞭解了吧?
anyway, 下面接著講 Scanline


=========================================

 ptr=(Byte*) Image->Picture->Bitmap->ScanLine[i];


就是這條混蛋指令讓人死一堆腦細胞
ptr是什麼? 為什麼可以這樣做?? Scanline的功能???

首先,對於這條指令,還必須得宣告 Byte *ptr;才能使用
也就是如下:
 Byte *ptr;
 ptr=(Byte *) Image->Picture->Bitmap->ScanLine[i];

ptr是個Byte型態的變數,加個星號就是指標嘛
所以 *ptr儲存的是Byte型態的記憶體位址

(Byte *)Image->Picture->Bitmap->ScanLine[i];
是該Image,第 i行(height)的起始位置


以此3x3的圖為例,
 (Byte*)Image->Picture->Bitmap->ScanLine[0]; 回傳的就是0xA0
 (Byte*)Image->Picture->Bitmap->ScanLine[1]; 回傳的就是0xB0
 (Byte*)Image->Picture->Bitmap->ScanLine[2]; 回傳的就是0xC0

用很白話的講,Scanline就是掃瞄一條線,

他指向某條水平線第一個點的位子

如果你要問我為什麼要加
(Byte*) ?
別傻了,單純用Image->Picture->Bitmap->ScanLine[i];
跑出來的應該是RGB的值吧
我猜的

想多認識(Byte*)可以參考此網誌
http://www.wretch.cc/blog/hhmmjj/9594208




這樣應該懂Scanline是做什麼的了吧?
所以,
 for(i=0;i<BitmapInfoHead.biHeight;i++)
 {
  ptr=(Byte *) Image->Picture->Bitmap->ScanLine[i];
  for(k=0,j=0;j<BitmapInfoHead.biWidth;j++)
  {
   background[i][j][0] =ptr[k+2];
   background[i][j][1]= ptr[k+1];
   background[i][j][2] =ptr[k];
   k+=3;
  }
 }
這段,其實就是將Image裡每個pixel的RGB值傳給background
讓你可以將background的值累加起來後取平均,
然後把background顯示出來,就可以得到視訊的背景圖了



再囉嗦一點

上一篇文章作者提到因不懂Scanline
所以改用GetRValue()、GetGValue()、GetBValue()
去擷取Image的RGB值

這兩者有什麼差異?
最大的差異就是在執行速度吧

上禮拜在作業系統的課上老師有提到,
以前ANSI C用來寫OS的武器之一就是指標 => *

用指標直接存取記憶體的速度本來就較快,
更何況用了三個函數才能去得RGB值

假設
ptr[k]    取出R值的速度是0.01秒
GetRValue() 取出R值的速度是0.1秒
相差10倍

每一個pixel有RGB三個值,等於需要花 0.03 : 0.3
一張800x600的圖片有480000個pixel
這樣用函數取RGB所花的時間會比用指標多花上10x480000倍 ...
應該是這樣算吧,我只是舉例有錯不能怪我



還有一張圖畫了沒用到,不管,我還是要放上來



p.s.
在blogspot可以利用修改css,讓文章內可以有個物件包住程式碼
  看起來也比較美觀 ...
  可以參考這篇文章修改:【Blog】文章中引用程式碼的作法

=================================================

附錄: i++ v.s. ++i

老師說這個不會也不影響寫程式是沒錯
但是多瞭解一些還是可以幫助釐清觀念
尤其是上arping的課如果有要寫程式時,沒分清楚可能就哉了
雖然我也沒很懂,簡易的描述使用上的差異應該還是ok的

把寫好的程式進行編譯時,
編譯器也是從上到下,由左至右讀取文字判斷要編譯成什麼樣的低階語言
所以
 i++; 編譯器先讀到 i 再讀到++,所以把 i 加1
 ++i; 編譯器先讀到++,再讀到 i ,接著把 i 加1
看起來意思都相同,但這是因為這兩句語法都是獨立使用

如果程式碼長這樣呢?
 int a[3]={0,0,0};
 i=0;
 a[i++]=1;
 i=0;
 a[++i]=1;
看起來似乎都是把a[1]的值變成1,輸出就會是 {0,1,0} 對吧?
其實執行結果陣列a的內容會是這樣 => {1,1,0}

 i=0;
 a[i++]=1;
編譯器會先把 i 提出來,並把 i 的值,0給a[]使用
然後才做 i++
所以這裡其實是 a[0]=1; i 的值為1

 i=0;
 a[++i]=1;
編譯器會先執行 ++ 的動作,也就是把 i 加1之後,
才把 i 的值,1給a[]使用
也就是這裡其實是 a[1]=1; i 的值為1

所以真正的執行結果陣列a的內容會是 {1,1,0}

就我的印象,arping好像還講過 i++ 和 ++i 在執行上還有別的差異
那時他沒多講我也沒多問就 .... 現在就不會了


 i=0;
 a[(i++)]=2;
你覺得陣列a之中哪個位置會變成2?

 i=1;
 c = (i++)+(++i)+(i++)+(++i)+i+(i++)+(++i);
最後c的值會是多少?

我用手算是29,用Dev-C++執行結果卻是19
怎麼會這樣 ... ??? (使用不同的IDE可能會有不同的執行結果)

試試看吧 很好玩的 :D

1 則留言:

YKLee 提到...

將變數 ptr 宣告為 (BYTE *) 的用意很簡單, * 用來告訴電腦 ptr 是一個指標的資料型態, BYTE 用來告訴電腦, 這個指標所指向的位址, 其資料是一個 BYTE, 一個BYTE 為單位來看待的, 因此, ptr+1 就是指向下一個 BYTE, 在實際的電腦記憶體中, +1 就是差一個 BYTE。

可以做一個實驗, 如果今天將變數 ptr 宣告為一個 (INT *), 將 ptr+1 時, 其實際的位址差了多少?