自已实现一个UI库

 [2014年写一个UI库时写的几个文章,发布出来]

几年前的一个嵌入式的UI开发,使自己有机会接触到了UI的一些底层知识,虽然之前也开发过很多Windows下的信息应用系统,也做很多的界面开发,但一直却对UI的一些运作却不了解。

BOSS决定使用UCGUI做为UI的基本库来开发UI界面的一些应用。用UCGUI的库来做开发,它已经有很完善的基本构件,像窗体的管理,基本的控件,图片,文字处理等,不过听说UCGUI的授权费用也是不菲的,但我认为它确实是一个物有所值的东西。

在使用中也还是遇到过一些问题,但这些基本都不会有什么大的影响,主要的是有源代码,有一些小的BUG,也可以自己处理,满足不了的控件可以自己开发,也可以很方便的在他的控件基础上做一些扩展。

使用它避免不了要去读了解它的一些底层代码,这样自己有机会对UI的动作有了一些基本的了解。最近也做些UI的事,想起UCGUI的代码架构还是值得学习的,所以自己从UI的基本原理上再次学习了一次,自己也花时间动手做了一些基本的代码实现,很多基本原理都是从UCGUI上学习来的,再把这些基本的东西记下来,方便后来的学习者。

如何自己动手写一个UI?在我自己没接触底层UI前,一直没想过,也觉得它是一件比较复杂和难的事情。它有多难?现在我觉得如果会一门编程语言就可以写一个UI出来,这样的基础就够了,我想可以试一试!

从画一个点开始

“像素”这个词我们一定已经很熟悉了,现在买手机时大家很观注的一个参数就是摄像头的成像像素是多少,因为图像的质量很大成度就是它决定的,一个像素代表一个点,同样在显示器上也是一样,我们说的分辨率就是长宽多少个像素,在一个屏幕上显示有文字,有窗体,有图片及各种形状等等!看起来这是一个挺复杂的东西,但它却是由一个一个的点构成的,一个点代表一个像素,而这个点不同的像素值就代表不同的颜色,一个像素可以用8位,16位,32位来表示,位数越多,表示它能表示的颜色就越多显示的色彩也就越丰富了。

一个屏幕像是这样的:

wps_clip_image-10802

我们以左上角为原点,做一个坐标系,分别有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

猜你喜欢

转载自blog.csdn.net/jhting/article/details/46738585