標籤:
[2014年寫一個UI庫時寫的幾個文章,發布出來]
幾年前的一個嵌入式的UI開發,使自己有機會接觸到了UI的一些底層知識,雖然之前也開發過很多Windows下的資訊應用系統,也做很多的介面開發,但一直卻對UI的一些運作卻不瞭解。
BOSS決定使用UCGUI做為UI的基本庫來開發UI介面的一些應用。用UCGUI的庫來做開發,它已經有很完善的基本構件,像表單的管理,基本的控制項,圖片,文文書處理等,不過聽說UCGUI的授權費用也是不菲的,但我認為它確實是一個物有所值的東西。
在使用中也還是遇到過一些問題,但這些基本都不會有什麼大的影響,主要的是有原始碼,有一些小的BUG,也可以自己處理,滿足不了的控制項可以自己開發,也可以很方便的在他的控制項基礎上做一些擴充。
使用它避免不了要去讀瞭解它的一些底層代碼,這樣自己有機會對UI的動作有了一些基本的瞭解。最近也做些UI的事,想起UCGUI的代碼架構還是值得學習的,所以自己從UI的基本原理上再次學習了一次,自己也花時間動手做了一些基本的代碼實現,很多基本原理都是從UCGUI上學習來的,再把這些基本的東西記下來,方便後來的學習者。
如何自己動手寫一個UI?在我自己沒接觸底層UI前,一直沒想過,也覺得它是一件比較複雜和難的事情。它有多難?現在我覺得如果會一門程式設計語言就可以寫一個UI出來,這樣的基礎就夠了,我想可以試一試!
從畫一個點開始
“像素”這個詞我們一定已經很熟悉了,現在買手機時大家很觀注的一個參數就是網路攝影機的成像像素是多少,因為映像的品質很大成度就是它決定的,一個像素代表一個點,同樣在顯示器上也是一樣,我們說的解析度就是長寬多少個像素,在一個螢幕上顯示有文字,有表單,有圖片及各種形狀等等!看起來這是一個挺複雜的東西,但它卻是由一個一個的點構成的,一個點代表一個像素,而這個點不同的像素值就代表不同的顏色,一個像素可以用8位,16位,32位來表示,位元越多,表示它能表示的顏色就越多顯示的色彩也就越豐富了。
一個螢幕像是這樣的:
我們以左上方為原點,做一個座標系,分別有X,Y方向。像我現在的電腦的解析度是1440x900,這樣 X 最大取值是1440,Y最大取值是900。
如果我們要在一個螢幕上畫一個點,給定一個點如:(500,500),只要在座標系裡找到相應的位置就可以寫一個像素值進去,螢幕就會顯示出一個顏色點。
顯示器有一個顯存,要在螢幕上畫出點,也就需要在相應的顯存位置寫入顏色值,顯存又會映射到記憶體的一塊連續的地區,這樣我們只要把值寫入記憶體的地區,系統又會作I/O讀寫把更新顯存的值,所心我們只要關心寫的記憶體的地區就好了。
操作顯存
在Linux裡有一個叫framebuffer的概念,叫“幀緩衝”,其實就是對顯存當前值的緩衝,Linux系統的顯卡驅動都有實現,我們對framebuffer的讀寫就是對顯存的讀寫。
在Linux裡/dev目錄裡,應都有一個類似fbx(x表示一個數字)裝置檔案,開啟它,再用mmap函數把framebuffer記憶體映射進我們的進程裡就可認方便的對顯示器操作了,這樣我們可以畫點,畫線顯示在顯示器上面。
如下面的代碼操作,開啟fb0,並讀取設定相應的參數:
static struct fb_var_screeninfo stVarInfo;
static struct fb_fix_screeninfo stfixInfo;
static unsigned char *pFrameBuffer = NULL;
char *file_name = "/dev/fb0";
int fbDev, s32Ret;
fbDev = open(file_name,O_RDWR,0);
if(fbDev < 0)
{
printf("open framebuff failed!\n");
return;
}
stVarInfo.bits_per_pixel = 16;
stVarInfo.activate = FB_ACTIVATE_NOW;
stVarInfo.xres = stVarInfo.xres_virtual = 1440;
stVarInfo.yres = stVarInfo.yres_virtual = 900;
s32Ret = ioctl(fbDev, FBIOPUT_VSCREENINFO, &stVarInfo);
if(s32Ret < 0)
{
printf("PUT_VSCREENINFO failed!%x\n",s32Ret);
return ;
}
if (ioctl(fbDev, FBIOGET_FSCREENINFO, &stfixInfo) < 0)
{
printf("Get fix screen info failed!\n");
return ;
}
pFrameBuffer = mmap(HI_NULL, stfixInfo.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fbDev, 0);
memset(pFrameBuffer,0x00,stfixInfo.smem_len);
在上面的代碼中開啟一個framebuffer,設定讀取顯示器的資訊其中兩個比較重要的結構:fb_var_screeninfo和fb_fix_screeninfo ,具體的可以搜尋瞭解一下。
在mmap時,stfixInfo.smem_len就是顯存在記憶體地區的大小, memset的操作效果就是我們把顯示器設成全黑色了,因為我們寫入的每一個點的像素值都是0x0。
在一個指定的點畫點
顯存的地址空間是一個線性一維地址空間,我們的螢幕像上面的座標系,是一個二維的了,給定一個點(x,y),這樣我們就需要把它轉換成相應的記憶體所在的地址。當知道一個螢幕的解析度了,如1440 * 900,現在通過FBIOGET_FSCREENINFO知道了一些基本的資訊,通過mmap知道了首地址。一個像素可以用8位,或16位,或32位,或更多的位表示,如我的板上的系統是16位,我用的LINUX系統是用32位的。以我板上的為例,用16位來表示一個像素點,也就是2個位元組表示一個像素點。
那麼螢幕上的點是這樣確定的,想像你拿支筆從第一行畫點,但把第一行畫滿後,再從第二行開始,持續的把整個屏畫滿,在屏上畫點也是這樣,假如是16位2個位元組表示一個像素,解析度是1400*900,所以給定的點(x,y)的地址為:
x * 2 + y * (1440 * 2),
如果用上一步代碼裡得到的資訊來表示就是:
(x + stVarInfo.xoffset) * (stVarInfo.bits_per_pixel >> 3) + (y + stVarInfo.yoffset) * stfixInfo.line_length;
Xoffset,yoffset表示是否相對原點的位移,bit_per_pixel表示一個像素用多少位表示,line_length表示一行佔用多少位元組。
畫一個點(x,y)的地址確定了,這樣寫入一個值,相應的位置顯示相應的顏色。那麼對應的畫點函數如下:
void UI_SetPointPixel(int x, int y, int pixelValue)
{
int location = 0;
location = (x + stVarInfo.xoffset) * (stVarInfo.bits_per_pixel >> 3) + (y + stVarInfo.yoffset) * stfixInfo.line_length;
*(short *)(pFrameBuffer + location) = (short)pixelValue;
}
應為是16位的,所以注意上面的轉換 (short *).
擷取一個點的顏色
同樣有時操作需要得到一個點的顏色,根據上面畫點的函數,可以如下寫出擷取點的函數:
unsigned int fb_GetPointPixel(int x, int y)
{
int location = 0;
location = (x + stVarInfo.xoffset) * (stVarInfo.bits_per_pixel >> 3) + (y + stVarInfo.yoffset) * stfixInfo.line_length;
int PixelIndex = *(short*)(pShowScreen + location);
return PixelIndex;
}
基本的UI底層操作
我們工作用的操作UI上,有表單,圖片,視頻等等,很多東西,看起來很複雜,但這樣的複雜北後,卻是基本的像素點組成的,但再複雜的實現,都是在上面的畫點函數,為基礎的,為了方便我們開法,於是擴充了上面的畫點函數,有畫線段,矩形,圓等等,為了方便後面畫更複雜的,我們實現畫線和填充矩形函數:
void fb_DrawHLine(int x0, int y0, int x1)
{
U16 PixelColor = UI_GetDrawPixeColor();
for(; x0 <= x1; x0++)
{
UI_SetPointPixel(x0, y0, PixelColor);
}
}
void fb_DrawVLine(int x0, int y0, int y1)
{
U16 PixelColor = UI_GetDrawPixeColor();
for(; y0 <= y1; y0++)
{
UI_SetPointPixel(x0, y0, PixelColor);
}
}
void fb_DrawFillRect(int x0, int y0, int x1, int y1)
{
for(; y0<=y1; y0++)
{
UI_DrawHLine(x0, y0, x1);
}
}
分別實現畫水平,垂直線,畫填充的矩形,上面的 UI_GetDrawPixeColor 函數得到當前畫點線的填充顏色值。
這樣我們就有了基本的操作UI的工具了。我們現在用上面的函數就可以畫點,線,矩形,還是很簡單的。
2014-11-15
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
自已實現一個UI庫