1. 程式人生 > >自已實現一個UI庫

自已實現一個UI庫

再次 pixel 詳細 顯存 iop 等等 tpi class 使用

[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的工具了。

我們如今用上面的函數就能夠畫點,線。矩形,還是非常easy的。

2014-11-15

自已實現一個UI庫