(转)OpenGL中位图的操作(glReadPixels,glDrawPixels和glCopyPixels应用举例)
(一)BMP文件格式簡單介紹
BMP文件是一種像素文件,它保存了一幅圖象中所有的像素。這種文件格式可以保存單色位圖、16色或256色索引模式像素圖、24位真彩色圖象,每種模式種單一像素的大小分別為1/8字節(jié),1/2字節(jié),1字節(jié)和3字節(jié)。目前最常見的是256色BMP和24位色BMP。這種文件格式還定義了像素保存的幾種方法,包括不壓縮、RLE壓縮等。常見的BMP文件大多是不壓縮的。
這里為了簡單起見,我們僅討論24位色、不使用壓縮的BMP。(如果你使用Windows自帶的畫圖程序,很容易繪制出一個(gè)符合以上要求的BMP)
Windows所使用的BMP文件,在開始處有一個(gè)文件頭,大小為54字節(jié)。保存了包括文件格式標(biāo)識(shí)、顏色數(shù)、圖象大小、壓縮方式等信息,因?yàn)槲覀儍H討論24位色不壓縮的BMP,所以文件頭中的信息基本不需要注意,只有“大小”這一項(xiàng)對(duì)我們比較有用。圖象的寬度和高度都是一個(gè)32位整數(shù),在文件中的地址分別為0x0012和0x0016,于是我們可以使用以下代碼來讀取圖象的大小信息:
GLint?width,?height;?//?使用OpenGL的GLint類型,它是32位的。
?????????????????????????????????????//?而C語言本身的int則不一定是32位的。
FILE*?pFile;
//?在這里進(jìn)行“打開文件”的操作
fseek(pFile,?0x0012,?SEEK_SET);?????????//?移動(dòng)到0x0012位置
fread(&width,?sizeof(width),?1,?pFile);?//?讀取寬度
fseek(pFile,?0x0016,?SEEK_SET);?????????//?移動(dòng)到0x0016位置
????????????????????????????????????????//?由于上一句執(zhí)行后本就應(yīng)該在0x0016位置
????????????????????????????????????????//?所以這一句可省略
fread(&height,?sizeof(height),?1,?pFile);?//?讀取高度
54個(gè)字節(jié)以后,如果是16色或256色BMP,則還有一個(gè)顏色表,但24位色BMP沒有這個(gè),我們這里不考慮。接下來就是實(shí)際的像素?cái)?shù)據(jù)了。24位色的BMP文件中,每三個(gè)字節(jié)表示一個(gè)像素的顏色。
注意,OpenGL通常使用RGB來表示顏色,但BMP文件則采用BGR,就是說,順序被反過來了。
另外需要注意的地方是:像素的數(shù)據(jù)量并不一定完全等于圖象的高度乘以寬度乘以每一像素的字節(jié)數(shù),而是可能略大于這個(gè)值。原因是BMP文件采用了一種“對(duì)齊”的機(jī)制,每一行像素?cái)?shù)據(jù)的長度若不是4的倍數(shù),則填充一些數(shù)據(jù)使它是4的倍數(shù)。這樣一來,一個(gè)17*15的24位BMP大小就應(yīng)該是834字節(jié)(每行17個(gè)像素,有51字節(jié),補(bǔ)充為52字節(jié),乘以15得到像素?cái)?shù)據(jù)總長度780,再加上文件開始的54字節(jié),得到834字節(jié))。分配內(nèi)存時(shí),一定要小心,不能直接使用“圖象的高度乘以寬度乘以每一像素的字節(jié)數(shù)”來計(jì)算分配空間的長度,否則有可能導(dǎo)致分配的內(nèi)存空間長度不足,造成越界訪問,帶來各種嚴(yán)重后果。
一個(gè)很簡單的計(jì)算數(shù)據(jù)長度的方法如下:
int?LineLength,?TotalLength;
LineLength?=?ImageWidth?*?BytesPerPixel;?//?每行數(shù)據(jù)長度大致為圖象寬度乘以
?????????????????????????????????????????//?每像素的字節(jié)數(shù)
while(?LineLength?%?4?!=?0?)?????????????//?修正LineLength使其為4的倍數(shù)
????++LineLenth;
TotalLength?=?LineLength?*?ImageHeight;??//?數(shù)據(jù)總長?=?每行長度?*?圖象高度
這并不是效率最高的方法,但由于這個(gè)修正本身運(yùn)算量并不大,使用頻率也不高,我們就不需要再考慮更快的方法了。
(二)簡單的OpenGL像素操作
OpenGL提供了簡潔的函數(shù)來操作像素:
glReadPixels:讀取一些像素。當(dāng)前可以簡單理解為“把已經(jīng)繪制好的像素(它可能已經(jīng)被保存到顯卡的顯存中)讀取到內(nèi)存”。
glDrawPixels:繪制一些像素。當(dāng)前可以簡單理解為“把內(nèi)存中一些數(shù)據(jù)作為像素?cái)?shù)據(jù),進(jìn)行繪制”。
glCopyPixels:復(fù)制一些像素。當(dāng)前可以簡單理解為“把已經(jīng)繪制好的像素從一個(gè)位置復(fù)制到另一個(gè)位置”。雖然從功能上看,好象等價(jià)于先讀取像素再繪制像素,但實(shí)際上它不需要把已經(jīng)繪制的像素(它可能已經(jīng)被保存到顯卡的顯存中)轉(zhuǎn)換為內(nèi)存數(shù)據(jù),然后再由內(nèi)存數(shù)據(jù)進(jìn)行重新的繪制,所以要比先讀取后繪制快很多。
這三個(gè)函數(shù)可以完成簡單的像素讀取、繪制和復(fù)制任務(wù),但實(shí)際上也可以完成更復(fù)雜的任務(wù)。當(dāng)前,我們僅討論一些簡單的應(yīng)用。由于這幾個(gè)函數(shù)的參數(shù)數(shù)目比較多,下面我們分別介紹。
(三)glReadPixels的用法和舉例
3.1?函數(shù)的參數(shù)說明
該函數(shù)總共有七個(gè)參數(shù)。前四個(gè)參數(shù)可以得到一個(gè)矩形,該矩形所包括的像素都會(huì)被讀取出來。(第一、二個(gè)參數(shù)表示了矩形的左下角橫、縱坐標(biāo),坐標(biāo)以窗口最左下角為零,最右上角為最大值;第三、四個(gè)參數(shù)表示了矩形的寬度和高度)
第五個(gè)參數(shù)表示讀取的內(nèi)容,例如:GL_RGB就會(huì)依次讀取像素的紅、綠、藍(lán)三種數(shù)據(jù),GL_RGBA則會(huì)依次讀取像素的紅、綠、藍(lán)、alpha四種數(shù)據(jù),GL_RED則只讀取像素的紅色數(shù)據(jù)(類似的還有GL_GREEN,GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA顏色模式,而是采用顏色索引模式,則也可以使用GL_COLOR_INDEX來讀取像素的顏色索引。目前僅需要知道這些,但實(shí)際上還可以讀取其它內(nèi)容,例如深度緩沖區(qū)的深度數(shù)據(jù)等。
第六個(gè)參數(shù)表示讀取的內(nèi)容保存到內(nèi)存時(shí)所使用的格式,例如:GL_UNSIGNED_BYTE會(huì)把各種數(shù)據(jù)保存為GLubyte,GL_FLOAT會(huì)把各種數(shù)據(jù)保存為GLfloat等。
第七個(gè)參數(shù)表示一個(gè)指針,像素?cái)?shù)據(jù)被讀取后,將被保存到這個(gè)指針?biāo)硎镜牡刂?。注?#xff0c;需要保證該地址有足夠的可以使用的空間,以容納讀取的像素?cái)?shù)據(jù)。例如一幅大小為256*256的圖象,如果讀取其RGB數(shù)據(jù),且每一數(shù)據(jù)被保存為GLubyte,總大小就是:256*256*3?=?196608字節(jié),即192千字節(jié)。如果是讀取RGBA數(shù)據(jù),則總大小就是256*256*4?=?262144字節(jié),即256千字節(jié)。
注意:glReadPixels實(shí)際上是從緩沖區(qū)中讀取數(shù)據(jù),如果使用了雙緩沖區(qū),則默認(rèn)是從正在顯示的緩沖(即前緩沖)中讀取,而繪制工作是默認(rèn)繪制到后緩沖區(qū)的。因此,如果需要讀取已經(jīng)繪制好的像素,往往需要先交換前后緩沖。
再看前面提到的BMP文件中兩個(gè)需要注意的地方:
3.2?解決OpenGL常用的RGB像素?cái)?shù)據(jù)與BMP文件的BGR像素?cái)?shù)據(jù)順序不一致問題
可以使用一些代碼交換每個(gè)像素的第一字節(jié)和第三字節(jié),使得RGB的數(shù)據(jù)變成BGR的數(shù)據(jù)。當(dāng)然也可以使用另外的方式解決問題:新版本的OpenGL除了可以使用GL_RGB讀取像素的紅、綠、藍(lán)數(shù)據(jù)外,也可以使用GL_BGR按照相反的順序依次讀取像素的藍(lán)、綠、紅數(shù)據(jù),這樣就與BMP文件格式相吻合了。即使你的gl/gl.h頭文件中沒有定義這個(gè)GL_BGR,也沒有關(guān)系,可以嘗試使用GL_BGR_EXT。雖然有的OpenGL實(shí)現(xiàn)(尤其是舊版本的實(shí)現(xiàn))并不能使用GL_BGR_EXT,但我所知道的Windows環(huán)境下各種OpenGL實(shí)現(xiàn)都對(duì)GL_BGR提供了支持,畢竟Windows中各種表示顏色的數(shù)據(jù)幾乎都是使用BGR的順序,而非RGB的順序。這可能與IBM-PC的硬件設(shè)計(jì)有關(guān)。
3.3?消除BMP文件中“對(duì)齊”帶來的影響
實(shí)際上OpenGL也支持使用了這種“對(duì)齊”方式的像素?cái)?shù)據(jù)。只要通過glPixelStore修改“像素保存時(shí)對(duì)齊的方式”就可以了。像這樣:
int?alignment?=?4;
glPixelStorei(GL_UNPACK_ALIGNMENT,?alignment);
第一個(gè)參數(shù)表示“設(shè)置像素的對(duì)齊值”,第二個(gè)參數(shù)表示實(shí)際設(shè)置為多少。這里像素可以單字節(jié)對(duì)齊(實(shí)際上就是不使用對(duì)齊)、雙字節(jié)對(duì)齊(如果長度為奇數(shù),則再補(bǔ)一個(gè)字節(jié))、四字節(jié)對(duì)齊(如果長度不是四的倍數(shù),則補(bǔ)為四的倍數(shù))、八字節(jié)對(duì)齊。分別對(duì)應(yīng)alignment的值為1,?2,?4,?8。實(shí)際上,默認(rèn)的值是4,正好與BMP文件的對(duì)齊方式相吻合。
glPixelStorei也可以用于設(shè)置其它各種參數(shù)。但我們這里并不需要深入討論了。
現(xiàn)在,我們已經(jīng)可以把屏幕上的像素讀取到內(nèi)存了,如果需要的話,我們還可以將內(nèi)存中的數(shù)據(jù)保存到文件。正確的對(duì)照BMP文件格式,我們的程序就可以把屏幕中的圖象保存為BMP文件,達(dá)到屏幕截圖的效果。
我們并沒有詳細(xì)介紹BMP文件開頭的54個(gè)字節(jié)的所有內(nèi)容,不過這無傷大雅。從一個(gè)正確的BMP文件中讀取前54個(gè)字節(jié),修改其中的寬度和高度信息,就可以得到新的文件頭了。假設(shè)我們先建立一個(gè)1*1大小的24位色BMP,文件名為dummy.bmp,又假設(shè)新的BMP文件名稱為grab.bmp。則可以編寫如下代碼:
FILE*?pOriginFile?=?fopen("dummy.bmp",?"rb);
FILE*?pGrabFile?=?fopen("grab.bmp",?"wb");
char??BMP_Header[54];
GLint?width,?height;
/*?先在這里設(shè)置好圖象的寬度和高度,即width和height的值,并計(jì)算像素的總長度?*/
//?讀取dummy.bmp中的頭54個(gè)字節(jié)到數(shù)組
fread(BMP_Header,?sizeof(BMP_Header),?1,?pOriginFile);
//?把數(shù)組內(nèi)容寫入到新的BMP文件
fwrite(BMP_Header,?sizeof(BMP_Header),?1,?pGrabFile);
//?修改其中的大小信息
fseek(pGrabFile,?0x0012,?SEEK_SET);
fwrite(&width,?sizeof(width),?1,?pGrabFile);
fwrite(&height,?sizeof(height),?1,?pGrabFile);
//?移動(dòng)到文件末尾,開始寫入像素?cái)?shù)據(jù)
fseek(pGrabFile,?0,?SEEK_END);
/*?在這里寫入像素?cái)?shù)據(jù)到文件?*/
fclose(pOriginFile);
fclose(pGrabFile);
我們給出完整的代碼,演示如何把整個(gè)窗口的圖象抓取出來并保存為BMP文件。
#define?WindowWidth??400
#define?WindowHeight?400
#include?<stdio.h>
#include?<stdlib.h>
/*?函數(shù)grab
?*?抓取窗口中的像素
?*?假設(shè)窗口寬度為WindowWidth,高度為WindowHeight
?*/
#define?BMP_Header_Length?54
void?grab(void)
{
????FILE*????pDummyFile;
????FILE*????pWritingFile;
????GLubyte*?pPixelData;
????GLubyte??BMP_Header[BMP_Header_Length];
????GLint????i,?j;
????GLint????PixelDataLength;
????//?計(jì)算像素?cái)?shù)據(jù)的實(shí)際長度
????i?=?WindowWidth?*?3;???//?得到每一行的像素?cái)?shù)據(jù)長度
????while(?i%4?!=?0?)??????//?補(bǔ)充數(shù)據(jù),直到i是的倍數(shù)
????????++i;???????????????//?本來還有更快的算法,
???????????????????????????//?但這里僅追求直觀,對(duì)速度沒有太高要求
????PixelDataLength?=?i?*?WindowHeight;
????//?分配內(nèi)存和打開文件
????pPixelData?=?(GLubyte*)malloc(PixelDataLength);
????if(?pPixelData?==?0?)
????????exit(0);
????pDummyFile?=?fopen("dummy.bmp",?"rb");
????if(?pDummyFile?==?0?)
????????exit(0);
????pWritingFile?=?fopen("grab.bmp",?"wb");
????if(?pWritingFile?==?0?)
????????exit(0);
????//?讀取像素
????glPixelStorei(GL_UNPACK_ALIGNMENT,?4);
????glReadPixels(0,?0,?WindowWidth,?WindowHeight,
????????GL_BGR_EXT,?GL_UNSIGNED_BYTE,?pPixelData);
????//?把dummy.bmp的文件頭復(fù)制為新文件的文件頭
????fread(BMP_Header,?sizeof(BMP_Header),?1,?pDummyFile);
????fwrite(BMP_Header,?sizeof(BMP_Header),?1,?pWritingFile);
????fseek(pWritingFile,?0x0012,?SEEK_SET);
????i?=?WindowWidth;
????j?=?WindowHeight;
????fwrite(&i,?sizeof(i),?1,?pWritingFile);
????fwrite(&j,?sizeof(j),?1,?pWritingFile);
????//?寫入像素?cái)?shù)據(jù)
????fseek(pWritingFile,?0,?SEEK_END);
????fwrite(pPixelData,?PixelDataLength,?1,?pWritingFile);
????//?釋放內(nèi)存和關(guān)閉文件
????fclose(pDummyFile);
????fclose(pWritingFile);
????free(pPixelData);
}
把這段代碼復(fù)制到以前任何課程的樣例程序中,在繪制函數(shù)的最后調(diào)用grab函數(shù),即可把圖象內(nèi)容保存為BMP文件了。(在我寫這個(gè)教程的時(shí)候,不少地方都用這樣的代碼進(jìn)行截圖工作,這段代碼一旦寫好,運(yùn)行起來是很方便的。)
(四)glDrawPixels的用法和舉例
glDrawPixels函數(shù)與glReadPixels函數(shù)相比,參數(shù)內(nèi)容大致相同。它的第一、二、三、四個(gè)參數(shù)分別對(duì)應(yīng)于glReadPixels函數(shù)的第三、四、五、六個(gè)參數(shù),依次表示圖象寬度、圖象高度、像素?cái)?shù)據(jù)內(nèi)容、像素?cái)?shù)據(jù)在內(nèi)存中的格式。兩個(gè)函數(shù)的最后一個(gè)參數(shù)也是對(duì)應(yīng)的,glReadPixels中表示像素讀取后存放在內(nèi)存中的位置,glDrawPixels則表示用于繪制的像素?cái)?shù)據(jù)在內(nèi)存中的位置。
注意到glDrawPixels函數(shù)比glReadPixels函數(shù)少了兩個(gè)參數(shù),這兩個(gè)參數(shù)在glReadPixels中分別是表示圖象的起始位置。在glDrawPixels中,不必顯式的指定繪制的位置,這是因?yàn)槔L制的位置是由另一個(gè)函數(shù)glRasterPos*來指定的。glRasterPos*函數(shù)的參數(shù)與glVertex*類似,通過指定一個(gè)二維/三維/四維坐標(biāo),OpenGL將自動(dòng)計(jì)算出該坐標(biāo)對(duì)應(yīng)的屏幕位置,并把該位置作為繪制像素的起始位置。
很自然的,我們可以從BMP文件中讀取像素?cái)?shù)據(jù),并使用glDrawPixels繪制到屏幕上。我們選擇Windows?XP默認(rèn)的桌面背景Bliss.bmp作為繪制的內(nèi)容(如果你使用的是Windows?XP系統(tǒng),很可能可以在硬盤中搜索到這個(gè)文件。當(dāng)然你也可以使用其它BMP文件來代替,只要它是24位的BMP文件。注意需要修改代碼開始部分的FileName的定義),先把該文件復(fù)制一份放到正確的位置,我們?cè)诔绦蜷_始時(shí),就讀取該文件,從而獲得圖象的大小后,根據(jù)該大小來創(chuàng)建合適的OpenGL窗口,并繪制像素。
繪制像素本來是很簡單的過程,但是這個(gè)程序在骨架上與前面的各種示例程序稍有不同,所以我還是打算給出一份完整的代碼。
#include?<gl/glut.h>
#define?FileName?"Bliss.bmp"
static?GLint????ImageWidth;
static?GLint????ImageHeight;
static?GLint????PixelLength;
static?GLubyte*?PixelData;
#include?<stdio.h>
#include?<stdlib.h>
void?display(void)
{
????//?清除屏幕并不必要
????//?每次繪制時(shí),畫面都覆蓋整個(gè)屏幕
????//?因此無論是否清除屏幕,結(jié)果都一樣
????//?glClear(GL_COLOR_BUFFER_BIT);
????//?繪制像素
????glDrawPixels(ImageWidth,?ImageHeight,
????????GL_BGR_EXT,?GL_UNSIGNED_BYTE,?PixelData);
????//?完成繪制
????glutSwapBuffers();
}
int?main(int?argc,?char*?argv[])
{
????//?打開文件
????FILE*?pFile?=?fopen("Bliss.bmp",?"rb");
????if(?pFile?==?0?)
????????exit(0);
????//?讀取圖象的大小信息
????fseek(pFile,?0x0012,?SEEK_SET);
????fread(&ImageWidth,?sizeof(ImageWidth),?1,?pFile);
????fread(&ImageHeight,?sizeof(ImageHeight),?1,?pFile);
????//?計(jì)算像素?cái)?shù)據(jù)長度
????PixelLength?=?ImageWidth?*?3;
????while(?PixelLength?%?4?!=?0?)
????????++PixelLength;
????PixelLength?*=?ImageHeight;
????//?讀取像素?cái)?shù)據(jù)
????PixelData?=?(GLubyte*)malloc(PixelLength);
????if(?PixelData?==?0?)
????????exit(0);
????fseek(pFile,?54,?SEEK_SET);
????fread(PixelData,?PixelLength,?1,?pFile);
????//?關(guān)閉文件
????fclose(pFile);
????//?初始化GLUT并運(yùn)行
????glutInit(&argc,?argv);
????glutInitDisplayMode(GLUT_DOUBLE?|?GLUT_RGBA);
????glutInitWindowPosition(100,?100);
????glutInitWindowSize(ImageWidth,?ImageHeight);
????glutCreateWindow(FileName);
????glutDisplayFunc(&display);
????glutMainLoop();
????//?釋放內(nèi)存
????//?實(shí)際上,glutMainLoop函數(shù)永遠(yuǎn)不會(huì)返回,這里也永遠(yuǎn)不會(huì)到達(dá)
????//?這里寫釋放內(nèi)存只是出于一種個(gè)人習(xí)慣
????//?不用擔(dān)心內(nèi)存無法釋放。在程序結(jié)束時(shí)操作系統(tǒng)會(huì)自動(dòng)回收所有內(nèi)存
????free(PixelData);
????return?0;
}
這里僅僅是一個(gè)簡單的顯示24位BMP圖象的程序,如果讀者對(duì)BMP文件格式比較熟悉,也可以寫出適用于各種BMP圖象的顯示程序,在像素處理時(shí),它們所使用的方法是類似的。
OpenGL在繪制像素之前,可以對(duì)像素進(jìn)行若干處理。最常用的可能就是對(duì)整個(gè)像素圖象進(jìn)行放大/縮小。使用glPixelZoom來設(shè)置放大/縮小的系數(shù),該函數(shù)有兩個(gè)參數(shù),分別是水平方向系數(shù)和垂直方向系數(shù)。例如設(shè)置glPixelZoom(0.5f,?0.8f);則表示水平方向變?yōu)樵瓉淼?0%大小,而垂直方向變?yōu)樵瓉淼?0%大小。我們甚至可以使用負(fù)的系數(shù),使得整個(gè)圖象進(jìn)行水平方向或垂直方向的翻轉(zhuǎn)(默認(rèn)像素從左繪制到右,但翻轉(zhuǎn)后將從右繪制到左。默認(rèn)像素從下繪制到上,但翻轉(zhuǎn)后將從上繪制到下。因此,glRasterPos*函數(shù)設(shè)置的“開始位置”不一定就是矩形的左下角)。
(五)glCopyPixels的用法和舉例
從效果上看,glCopyPixels進(jìn)行像素復(fù)制的操作,等價(jià)于把像素讀取到內(nèi)存,再從內(nèi)存繪制到另一個(gè)區(qū)域,因此可以通過glReadPixels和glDrawPixels組合來實(shí)現(xiàn)復(fù)制像素的功能。然而我們知道,像素?cái)?shù)據(jù)通常數(shù)據(jù)量很大,例如一幅1024*768的圖象,如果使用24位BGR方式表示,則需要至少1024*768*3字節(jié),即2.25兆字節(jié)。這么多的數(shù)據(jù)要進(jìn)行一次讀操作和一次寫操作,并且因?yàn)樵趃lReadPixels和glDrawPixels中設(shè)置的數(shù)據(jù)格式不同,很可能涉及到數(shù)據(jù)格式的轉(zhuǎn)換。這對(duì)CPU無疑是一個(gè)不小的負(fù)擔(dān)。使用glCopyPixels直接從像素?cái)?shù)據(jù)復(fù)制出新的像素?cái)?shù)據(jù),避免了多余的數(shù)據(jù)的格式轉(zhuǎn)換,并且也可能減少一些數(shù)據(jù)復(fù)制操作(因?yàn)閿?shù)據(jù)可能直接由顯卡負(fù)責(zé)復(fù)制,不需要經(jīng)過主內(nèi)存),因此效率比較高。
glCopyPixels函數(shù)也通過glRasterPos*系列函數(shù)來設(shè)置繪制的位置,因?yàn)椴恍枰婕暗街鲀?nèi)存,所以不需要指定數(shù)據(jù)在內(nèi)存中的格式,也不需要使用任何指針。
glCopyPixels函數(shù)有五個(gè)參數(shù),第一、二個(gè)參數(shù)表示復(fù)制像素來源的矩形的左下角坐標(biāo),第三、四個(gè)參數(shù)表示復(fù)制像素來源的舉行的寬度和高度,第五個(gè)參數(shù)通常使用GL_COLOR,表示復(fù)制像素的顏色,但也可以是GL_DEPTH或GL_STENCIL,分別表示復(fù)制深度緩沖數(shù)據(jù)或模板緩沖數(shù)據(jù)。
值得一提的是,glDrawPixels和glReadPixels中設(shè)置的各種操作,例如glPixelZoom等,在glCopyPixels函數(shù)中同樣有效。
下面看一個(gè)簡單的例子,繪制一個(gè)三角形后,復(fù)制像素,并同時(shí)進(jìn)行水平和垂直方向的翻轉(zhuǎn),然后縮小為原來的一半,并繪制。繪制完畢后,調(diào)用前面的grab函數(shù),將屏幕中所有內(nèi)容保存為grab.bmp。其中WindowWidth和WindowHeight是表示窗口寬度和高度的常量。
void?display(void)
{
????//?清除屏幕
????glClear(GL_COLOR_BUFFER_BIT);
????//?繪制
????glBegin(GL_TRIANGLES);
????????glColor3f(1.0f,?0.0f,?0.0f);????glVertex2f(0.0f,?0.0f);
????????glColor3f(0.0f,?1.0f,?0.0f);????glVertex2f(1.0f,?0.0f);
????????glColor3f(0.0f,?0.0f,?1.0f);????glVertex2f(0.5f,?1.0f);
????glEnd();
????glPixelZoom(-0.5f,?-0.5f);
????glRasterPos2i(1,?1);
????glCopyPixels(WindowWidth/2,?WindowHeight/2,
????????WindowWidth/2,?WindowHeight/2,?GL_COLOR);
????//?完成繪制,并抓取圖象保存為BMP文件
????glutSwapBuffers();
????grab();
}
小結(jié):
本課結(jié)合Windows系統(tǒng)常見的BMP圖象格式,簡單介紹了OpenGL的像素處理功能。包括使用glReadPixels讀取像素、glDrawPixels繪制像素、glCopyPixels復(fù)制像素。
本課僅介紹了像素處理的一些簡單應(yīng)用,但相信大家已經(jīng)可以體會(huì)到,圍繞這三個(gè)像素處理函數(shù),還存在一些“外圍”函數(shù),比如glPixelStore*,glRasterPos*,以及glPixelZoom等。我們僅使用了這些函數(shù)的一少部分功能。
本課內(nèi)容并不多,例子足夠豐富,三個(gè)像素處理函數(shù)都有例子,大家可以結(jié)合例子來體會(huì)。
?
附錄(其它位色的BMP文件簡介):
BMP文件組成?
BMP文件由文件頭、位圖信息頭、顏色信息和圖形數(shù)據(jù)四部分組成。?
?BMP文件頭?
BMP文件頭數(shù)據(jù)結(jié)構(gòu)含有BMP文件的類型、文件大小和位圖起始位置等信息。?
其結(jié)構(gòu)定義如下:
typedef?struct?tagBITMAPFILEHEADER
{
WORDbfType;?//?位圖文件的類型,必須為BM
DWORD?bfSize;?//?位圖文件的大小,以字節(jié)為單位
WORDbfReserved1;?//?位圖文件保留字,必須為0
WORDbfReserved2;?//?位圖文件保留字,必須為0
DWORD?bfOffBits;?//?位圖數(shù)據(jù)的起始位置,以相對(duì)于位圖
//?文件頭的偏移量表示,以字節(jié)為單位
}?BITMAPFILEHEADER;
位圖信息頭?
BMP位圖信息頭數(shù)據(jù)用于說明位圖的尺寸等信息。
typedef?struct?tagBITMAPINFOHEADER{
DWORD?biSize;?//?本結(jié)構(gòu)所占用字節(jié)數(shù)
LONGbiWidth;?//?位圖的寬度,以像素為單位
LONGbiHeight;?//?位圖的高度,以像素為單位
WORD?biPlanes;?//?目標(biāo)設(shè)備的級(jí)別,必須為1
WORD?biBitCount//?每個(gè)像素所需的位數(shù),必須是1(雙色),
//?4(16色),8(256色)或24(真彩色)之一
DWORD?biCompression;?//?位圖壓縮類型,必須是?0(不壓縮),
//?1(BI_RLE8壓縮類型)或2(BI_RLE4壓縮類型)之一
DWORD?biSizeImage;?//?位圖的大小,以字節(jié)為單位
LONGbiXPelsPerMeter;?//?位圖水平分辨率,每米像素?cái)?shù)
LONGbiYPelsPerMeter;?//?位圖垂直分辨率,每米像素?cái)?shù)
DWORD?biClrUsed;//?位圖實(shí)際使用的顏色表中的顏色數(shù)
DWORD?biClrImportant;//?位圖顯示過程中重要的顏色數(shù)
}?BITMAPINFOHEADER;
顏色表?
顏色表用于說明位圖中的顏色,它有若干個(gè)表項(xiàng),每一個(gè)表項(xiàng)是一個(gè)RGBQUAD類型的結(jié)構(gòu),定義一種顏色。RGBQUAD結(jié)構(gòu)的定義如下:?
typedef?struct?tagRGBQUAD?{
BYTErgbBlue;//?藍(lán)色的亮度(值范圍為0-255)
BYTErgbGreen;?//?綠色的亮度(值范圍為0-255)
BYTErgbRed;?//?紅色的亮度(值范圍為0-255)
BYTErgbReserved;//?保留,必須為0
}?RGBQUAD;
顏色表中RGBQUAD結(jié)構(gòu)數(shù)據(jù)的個(gè)數(shù)有biBitCount來確定:
當(dāng)biBitCount=1,4,8時(shí),分別有2,16,256個(gè)表項(xiàng);
當(dāng)biBitCount=24時(shí),沒有顏色表項(xiàng)。
位圖信息頭和顏色表組成位圖信息,BITMAPINFO結(jié)構(gòu)定義如下:
typedef?struct?tagBITMAPINFO?{
BITMAPINFOHEADER?bmiHeader;?//?位圖信息頭
RGBQUAD?bmiColors[1];?//?顏色表
}?BITMAPINFO;
位圖數(shù)據(jù)?
位圖數(shù)據(jù)記錄了位圖的每一個(gè)像素值,記錄順序是在掃描行內(nèi)是從左到右,掃描行之間是從下到上。位圖的一個(gè)像素值所占的字節(jié)數(shù):?
當(dāng)biBitCount=1時(shí),8個(gè)像素占1個(gè)字節(jié);
當(dāng)biBitCount=4時(shí),2個(gè)像素占1個(gè)字節(jié);
當(dāng)biBitCount=8時(shí),1個(gè)像素占1個(gè)字節(jié);
當(dāng)biBitCount=24時(shí),1個(gè)像素占3個(gè)字節(jié);
Windows規(guī)定一個(gè)掃描行所占的字節(jié)數(shù)必須是
4的倍數(shù)(即以long為單位),不足的以0填充,
一個(gè)掃描行所占的字節(jié)數(shù)計(jì)算方法:
DataSizePerLine=?(biWidth*?biBitCount+31)/8;?
//?一個(gè)掃描行所占的字節(jié)數(shù)
DataSizePerLine=?DataSizePerLine/4*4;?//?字節(jié)數(shù)必須是4的倍數(shù)
位圖數(shù)據(jù)的大小(不壓縮情況下):
DataSize=?DataSizePerLine*?biHeight;
轉(zhuǎn)載于:https://www.cnblogs.com/dongerlei/p/5614185.html
總結(jié)
以上是生活随笔為你收集整理的(转)OpenGL中位图的操作(glReadPixels,glDrawPixels和glCopyPixels应用举例)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces Round #35
- 下一篇: HTML 常用标签演示