第五章 点矩阵档格式
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第五章點矩陣檔格式
我們可以將數位影像視為一個由像素(Pixel)所組成的矩陣,而每一個像素的數值代表一個顏色。
在視窗作業系統中儲存影像的格式有許多種,常用的有點矩陣(Bitmap)、JPEG、GIF或PNG等,不同的格式有不一樣的特性及用途。
在這些格式中Bitmap是一個簡單且基本的格式,且C++Builder也提供了一個元件來方便我們處理此一格式。
因此本章的目的即是簡單的討論Bitmap的格式,及如何使用此格式來完成影像處理。
5.1 Bitmap格式簡介
如圖5.1所示,Bitmap格式可分為三大部分:檔頭(File Header)、調色盤(Palette)及影像資料(Image Data)。
檔頭主要儲存的是影像的特性,如影像的寬高及大小,及使用的顏色數目等。
調色盤則儲存有影像所使用的每一種顏色之內容,而影像資料則含有影像中每一個像素的數值。
File Header
Palette
Image Data
圖5.1
舉例說明,若有一個小圖形其高及寬各為16像素,且每一個像素都可以使用256個顏色,則這些高16、寬16及顏色數目256等資訊需儲存於檔頭中。
這256顏色的內容則是包含於調色盤中,每一個顏色的內容是由紅色、綠色及藍色三原色表達之。
因此我們可以將調色盤想像為一個表格,在此例子中此表格共有256列,每一列代表一個顏色。
每一列共有三欄,分別是紅色、綠色及藍色在此列所代表的顏色中之比重。
最常使用的調色盤之一是灰階調色盤,在此調色盤中顏色都是灰色,但亮度不同。
我們將這些顏色依其亮度由暗到亮在此調色盤中由底部到頂部排列之,如圖5.2所示。
圖5.2
在調色盤之後是影像資料,其包含每一個像素的數值。
在本例子中由於每一像素都可以使用256個顏色,每一像素的數值之範圍應為0至255,且可用8位元二進制碼代表之,而每一數值代表在調色盤中的一個顏色。
若我們使用如圖5.2所示的調色盤,則若像素的數值為0,此像素為黑色;若像素的數值為255,此像素為白色;若像素的數值是128,此像素是灰色。
5.2 Bitmap 元件簡介
本節主要目的是說明在C++ Builder程式設計中如何使用Bitmap元件。
此元件的主要功能是處理格式為Bitmap的影像。
我們無法在元件盤中找到此元件,因為Bitmap元件是依附在Picture元件之下,且Picture元件是依附在Image元件之下。
因此若在視窗程式設計中,自元件盤點選了Image元件,則我們就可以使用Bitmap元件來處理格式為Bitmap的影像。
Bitmap元件能用來讀取或設定格式為Bitmap的影像之檔頭、調色盤及影像資料。
我們將此元件與檔頭、調色盤及影像資料相關的屬性條列如下:
5.2.1 Bitmap元件中與Bitmap格式之檔頭相關的屬性
Height: 此屬性儲存Bitmap影像的高度(單位為像素)
Width: 此屬性儲存影像的寬度(單位為像素)
PixelFormat: 此屬性儲存每一個像素的位元數,常用的PixelFormat 數值有pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit等,其分別代表一個像素有1、4、8、15、16及24個位元。
也就是此影像使用了2、16、256、152、162及242個顏色。
5.2.2 Bitmap元件中與Bitmap格式之調色盤相關的屬性
Palette: 此屬性儲存指向Bitmap格式所使用的調色盤之指標名稱。
5.2.3 Bitmap元件中與Bitmap格式之影像資料相關的屬性
Canvas: 此屬性事實上也是一個元件,我們可以使用這一個元件來設定影像中任一個像素的數值。
ScanLine: 此屬性為一指標,其指向影像資料中每一列的開始位置,
我們可以使用此一屬性快速設定影像中任一像素數值。
scanline的回傳值為(void*)單純一個指標,因為我想要一個byte一個byte讀取
所以強制將回(void*)傳指標轉為(byte*),我們才能用ptr這個指標一個byte
一個byte讀取。
這就為何要在scanline的前面加上(byte*)的原因。
如果我想要4個bytes 4個bytes讀取,可宣告一個長整數指標(long*)接受
scanline所回傳的值。
這樣就能4個byte 4個byte讀取。
5.3 應用Bitmap元件之實例—翻轉及旋轉影像
圖5.3
事實上我們已在第四章中使用到了Bitmap元件,在4.1節中改變顯
示影像大小時,利用Image1->Picture->Bitmap->Width及
Image1->Picture->Bitmap->Height來了解影像原始之寬及高,但到目前為止都未討論過如何改變或設定影像中任一像素之數值。
本節之主要目的,即為說明如何使用Bitmap元件來完成兩個簡單卻重要的功能:翻轉及旋轉,這兩個功能都需要改變影像像素的數值。
如圖5.3所示,在主選單[View]之下產生一個新的次選單[Flip/Rotate]。
我們在此選單之事件處理程式中會實現這兩個功能,以下分別討論此兩種功能的實現方式。
5.3.1 翻轉影像
翻轉影像又分為垂直翻轉及水平翻轉,讓I[x][y]及O[x][y]代表翻轉前及翻轉後的影像在位置[x][y]之像素的數值,其中0≦x≦Width-1,0≦y≦Height-1,且Width及Height分別代表影像之寬及長。
則在垂直翻轉時,I[x][y]及O[x][y]之關係是
O[x][Height-1-y]=I[x][y],
而在水平翻轉時,I[x][y]及O[x][y]之關係是
O[Width-1-x][y]=I[x][y]。
這兩種翻轉的實現方式十分類似,因此在本節只討論執行水平翻轉的方式,以下為實現水平翻轉的完整程式。
Graphics::TBitmap *TheBitmap, *TempBitmap;
int Width, Height;
Byte *ptr1, *ptr2;
TempBitmap= new Graphics::TBitmap();
TheBitmap= Image1->Picture->Bitmap;
TempBitmap->Assign(TheBitmap);
Width= TheBitmap->Width;
Height= TheBitmap->Height;
for (int y=0; y< Height; y++)
for (int x=0; x< Width; x++)
TempBitmap->Canvas->Pixels[x][y] =
TheBitmap->Canvas->Pixels[Width-1-x][y];
TheBitmap->Assign(TempBitmap);
delete TempBitmap;
在上述的程式中,主要是使用Canvas元件來設定像素的數值。
此程式完成水平翻轉的基本做法如下。
我們共使用兩個Bitmap元件,第一個元件內存原始的影像,而第二個元件則將存放水平翻轉的結果。
為了達成此一目標,在程式中宣告兩個指標TheBitmap及TempBitmap,其分別指向原始及水平翻轉後的影像。
宣告這些指向元件Bitmap之指標的方式是:
Graphics::TBitmap *TheBitmap, *TempBitmap;
而下面的敘述句
TheBitmap= Image1->Picture->Bitmap;
則讓指標TheBitmap指向原始的影像。
另外一個指標TempBitmap必須要指向與TheBitmap完全不同的Bitmap元件,以便利儲存翻轉後的結果,因此我們利用敘述句
TempBitmap= new Graphics::TBitmap();
來產生一個空白的Bitmap元件,並讓TempBitmap指向該元件。
由於此元件是在程式執行時產生,在程式執行後不需要此元件時必須將其去除以避免佔據太多的記憶體,去除此元件的方式是:
delete TempBitmap;
在產生空白的Bitmap元件後,需要對此元件作一些設定。
由於此元件是儲存影像翻轉後的結果,因此其寬、長、所使用的顏色數目及調色盤等應與TheBitmap所指向的元件相同。
我們使用敘述句
TempBitmap->Assign(TheBitmap);
來將TheBitmap所指向的元件內容,完全轉移至TempBitmap所指向的元件上。
使用此敘述句可以將TheBitmap所指向的元件內容中,應與TempBitmap所指向的元件相同的部分,一次就轉移至TempBitmap 所指向的元件上。
接下來將TheBitmap所指向的元件之影像資料翻轉,並將翻轉知結果儲存於TempBitmap所指向的元件之影像資中。
以下為完成此動作的敘述句
for (int y=0; y< Height; y++)
for (int x=0; x< Width; x++)
TempBitmap->Canvas->Pixels[x][y] =
TheBitmap->Canvas->Pixels[Width-1-x][y];
在上述的敘述句中,Height及Width分別代表影像之高及寬,且TheBitmap->Canvas->Pixels[x][y]及TempBitmap-> Canvas
->Pixels[x][y]分別代表原始影像及翻轉後的影像於像素位置[x][y]之數值。
最後使用下列敘述句將翻轉後的結果複製回原來的影像,當此敘述句執行完畢後,我們便可觀察到水平翻轉後的影像已顯示在螢幕上。
TheBitmap->Assign(TempBitmap);
在上述的程式中,我們是使用元件Canvas來完成像素數值的設定,此種方式雖然容易,但執行的時間可能比較長,程式執行時間較短的方式是利用ScanLine來設定影像數值,如下面程式所示。
Graphics::TBitmap *TheBitmap, *TempBitmap;
int Width, Height;
Byte *ptr1, *ptr2;
TempBitmap= new Graphics::TBitmap();
TheBitmap= Image1->Picture->Bitmap;
TheBitmap->PixelFormat=pf8bit;
TempBitmap->Assign(TheBitmap);
Width= TheBitmap->Width;
Height= TheBitmap->Height;
for (int y=0; y< Height; y++)
{
ptr1=(Byte*) TempBitmap->ScanLine[y];
ptr2=(Byte*) TheBitmap->ScanLine[y];
for (int x=0; x< Width; x++)
ptr1[x]=ptr2[Width-1-x];
}
TheBitmap->Assign(TempBitmap);
delete TempBitmap;
此程式與前一程式類似,當中最大的不同是此程式使用ScanLine而前一程式則使用Canvas。
使用ScanLine的方式會稍微複雜一些,因為TheBitmap->ScanLine[y]只是指向影像資料第y列的最前面,而無法如TheBitmap->Canvas->Pixels[x][y]一樣可以裝取或設定任意像素的資料。
若要使用ScanLine來讀寫任意位置[x][y]之像素資料,則先宣告指向Byte之指標,如下所示
Byte *ptr1, *ptr2;
接下來將TempBitmap->ScanLine[y]及TheBitmap->ScanLine[y]所指向的位置分別傳給ptr1及ptr2,如下所示
ptr1=(Byte*) TempBitmap->ScanLine[y];
ptr2=(Byte*) TheBitmap->ScanLine[y];
最後,若擬將原始影像中[width-1-x][y]之資料傳至翻轉後的影像之[x][y]位置,則可使用下列的敘述句來完成之
ptr1[x]=ptr2[Width-1-x];
圖5.4顯示水平及垂直翻轉影像後的結果。
(a)
(b)
(c)
圖5.4
5.3.2 旋轉影像
所謂旋轉影像係指讓影像以順時針或逆時針方向作90度的旋轉。
在逆時針旋轉時,I[x][y]及O[x][y]之關係是
O[y][Width-1-x]=I[x][y],
而在順時針旋轉時,I[x][y]及O[x][y]之關係是
O[Height-1-y][x]=I[x][y]。
由於這兩種旋轉的方式類似,本節也只是討論逆時針旋轉的實現方式,以下為完成逆時針旋轉的程式。
Graphics::TBitmap *TheBitmap, *TempBitmap;
int Width, Height;
Byte *ptr1, *ptr2;
TempBitmap= new Graphics::TBitmap();
TheBitmap= Image1->Picture->Bitmap;
TheBitmap->PixelFormat=pf8bit;
Width= TheBitmap->Width;
Height= TheBitmap->Height;
TempBitmap->Assign(TheBitmap);
TempBitmap->Width= Height;
TempBitmap->Height= Width;
for (int y=0; y< Height; y++)
{
ptr2=(Byte*) TheBitmap->ScanLine[y];
for (int x=0; x< Width; x++)
{
ptr1=(Byte*) TempBitmap->ScanLine[Width-1-x];
ptr1[y]=ptr2[x];
}
}
TheBitmap->Assign(TempBitmap);
delete TempBitmap;
在上述程式中我們使用ScanLine來讀取或設定像素資料。
此程式與前一小節最後所討論的程式大致類似,因此不再詳細的說明每一個敘述句的涵義。
然而仍需強調的是在上述的程式中,敘述句
ptr2=(Byte*) TheBitmap->ScanLine[y];
及
ptr1=(Byte*) TempBitmap->ScanLine[Width-1-x];
ptr1[y]=ptr2[x];
之主要目的即為完成O[y][Width-1-x]=I[x][y],因此此程式可以進行逆時針旋轉。
圖5.5顯示逆時針旋轉的結果。
5.3.3 結合翻轉與旋轉於單一事件處理程式中
由於翻轉有兩種,旋轉也有兩種,且每種的實現方式都類似,若每種都寫一個事件處理程式,則顯得累贅及不必要。
因此我們將全部的翻轉及旋轉程式都合併於主選單[View]之下的次選單[Flip/Rotate]的事件處理程式中。
為了讓使用者決定要使用那一種翻轉或旋轉,我們也設計了如圖5.6所示的對話盒,該對話盒上有四個選項鈕分別代表了兩種翻轉及兩種旋轉。
在事件處理程式中則根據被圈選的選項來決定翻轉或旋轉的方式。
以下為此完整的事件處理程式。
void __fastcall TForm1::FlipRotate1Click(TObject *Sender)
{
Graphics::TBitmap *TheBitmap,*TempBitmap;
int Width, Height;
Byte *ptr1,*ptr2;
TempBitmap= new Graphics::TBitmap();
TheBitmap= Image1->Picture->Bitmap;
TheBitmap->PixelFormat=pf8bit;
Width=TheBitmap->Width;
Height=TheBitmap->Height;
TempBitmap->Assign(TheBitmap);
//----------------------------------------
FlipRotateDlg->ShowModal();
//----------------------------------------
if(FlipRotateDlg->ModalResult==mrOk)
{
if(FlipRotateDlg->RadioGroup1->ItemIndex>1)
{
TempBitmap->Width=Height;
TempBitmap->Height=Width;
}
//-----------------------------------------
for (int y=0; y < Height; y++)
{
ptr2=(Byte*) TheBitmap->ScanLine[y];
for (int x=0; x < Width; x++)
{
if(FlipRotateDlg->RadioGroup1->ItemIndex==0)
{
ptr1=(Byte*) TempBitmap->ScanLine[Height-1-y];
ptr1[x]=ptr2[x];
}
else if(FlipRotateDlg->RadioGroup1->ItemIndex==1)
{
ptr1=(Byte*) TempBitmap->ScanLine[y];
ptr1[Width-1-x]=ptr2[x];
}
else if(FlipRotateDlg->RadioGroup1->ItemIndex==2)
{
ptr1=(Byte*) TempBitmap->ScanLine[x];
ptr1[Height-1-y]=ptr2[x];
}
else
{
ptr1=(Byte*) TempBitmap->ScanLine[Width-1-x];
ptr1[y]=ptr2[x];
}
}
}
TheBitmap->Assign(TempBitmap);
delete TempBitmap;
}
}
在此程式中,FlipRotateDlg是提供使用者選擇翻轉或旋轉的對話盒名稱,若FlipRotateDlg->RadioGroup1->ItemIndex為0代表使用者選擇垂直翻轉,為1代表水平翻轉,為2代表順時針旋轉,為3代表逆時針旋轉。
(a)
(b)
(c)
圖5.5
圖5.6。