数码相框--电子书 之 完全分析

怎么显示一个电子书? 获得电子书的编码格式,利用库解析该编码,转换为可显示的位图,将数据描画出来。

模块化思想:编码格式模块,库模块,显示模块,描画,输入模块。根据传入参数

面向对象思想:每个模块创建一个结构机构体链表,如编码模块,有一个编码链表,各编码格式设置为一个结构体,注册入链表

以后我们操作,只需要操作到链表,而不需要到操作到底层各编码格式的具体函数。

操作过程:根据传入参数,找到支持该编码格式的结构体,从该结构体中找到支持该编码格式转化为位图的库,调用该库转化为位图,描画到显示模块输出。以下是具体一步步的从源码分析:

一.解析传入参数

从main函数开始 ,一开始会根据传进来的参数进行解析,

./show_file [-s Size] [-f freetype_font_file] [-h HZK] <text_file> //输入信息

 dwFontSize = strtoul(optarg, NULL, 0);  //size存放的位置,optagr表示参数存在optagr

 strncpy(acFreetypeFile, optarg, 128);  //freetype(字体库)存放的位置
 acFreetypeFile[127] = '\0';
 
strncpy(acHzkFile, optarg, 128);        //汉子库存放位置(基本没用到)
acHzkFile[127] = '\0';
	
strncpy(acDisplay, optarg, 128);        //显示设备名字存放的
acDisplay[127] = '\0';

二.解析完之后,iError = OpenTextFile(acTextFile); 进入OpenTextFile ,看做了什么

  1.主要是获得文件首地址:g_pucTextFileMem,怎么计算?open文件后得到句柄,fstat获得文件属性,mmap地址空间到内存

  2.计算得到文件结束地址:g_pucTextFileMemEnd,如何计算?首地址+fstat获得的size

  3.获得支持文件编码的结构体g_ptEncodingOprForFile,如何获得?   SelectEncodingOprForFile(g_pucTextFileMem),该函数会调用g_ptEncodingOprHead 即 编码链表 取出每个 isSupport 函数 判断哪一个编码支持,若支持则返回该编码的结构体。这里面isSupport是根据传入的参数,即文件首地址 g_pucTextFileMem  然后strncmp 得到的,因为这几个编码格式的头部不一样。

  4.如果获得了编码结构体 ,则计算得到数据真正开始的地方:g_pucLcdFirstPosAtFile =首地址+结构体的头部 //即头部之后的才是数据

三 SetTextDetail(acHzkFile, acFreetypeFile, dwFontSize)

       该函数里,根据传入的库文件,字体文件,字体大小,找到支持的字体库,并对该库初始化。怎么找的呢?进入函数发现,根据“二”中得到的支持编码结构体g_ptEncodingOprForFile 里的成员 ptFontOprSupportedHead 的name 找到该库,并调用其初始化函数。举个例子例如,在g_ptEncodingOprForFile 如果是 g_tUtf8EncodingOpr,即utf-8编码格式的文件,就会找到支持utf-8 的库,utf-8里有freetype库和ascii库,我们在为utf-8添加支持库时,把freetype放在 ptFontOprSupportedHead了头部,所以拿ptFontOprSupportedHead链表里的第一个成员去匹配时,会先找到freetype库,然后调用其初始化函数初始化这个库。

四 选择显示设备 以及 初始化所有输入设备

1.选择显示设备很简单,SelectAndInitDisplay(acDisplay);acDisplay里存放着我们传入的设备名(在“一”中以说明),然后根据设备名从显示设备链表中找到匹配的成员,取出该结构体并调用其初始化函数初始化设备。

2.初始化所有输入设备也简单:AllInputDevicesInit();该函数会去输入设备链表对每一个成员都初始化,并且创建子线程,等待数据传入后唤醒。一开始是如何休眠的?在初始化设备时(调用每个成员的初始化函数),我们open的方式为阻塞方式。iRet = ts_read(g_tTSDev, &tSamp, 1); /* 如果无数据则休眠 *,有数据传入,就会调用 InputEventTreadFunction 去唤醒主线程。

五 真正的显示函数ShowNextPage() 里的 ShowOnePage() 分析这两个函数即可知道如何显示

ShowNextPage(),该函数里会调用ShowOnePage()去显示一页,之后就会根据输入事件去判断显示哪一页了

 1.分析ShowNextPage() 主要做了哪些事情?

1.1得到该页首地址(数据真正开始的地方),存放在 pucTextFileMemCurPos

//第一次先执行else,因为 g_ptCurPage一开始等于NULL
if (g_ptCurPage)
{
	//以后再调用ShowNextPage 会得到下一页的地址
	pucTextFileMemCurPos = g_ptCurPage->pucLcdNextPageFirstPosAtFile;
}
else
{
	//根据OpenTextFile得到文件数据开始的地方 g_pucLcdFirstPosAtFile,在这里就可以使用了
	pucTextFileMemCurPos = g_pucLcdFirstPosAtFile;
}

1.2.根据当前位置开始显示一页内容,显示完成返回 0 ;

iError = ShowOnePage(pucTextFileMemCurPos);        

1.3.初始化下一页,申请空间,设置下一页地址,并且记录当前显示的页,将其放入Page链表。具体看代码

if (iError == 0)
	{
		//如果还有下一页,指定下一页地址,第一次没有下一页,不执行该if语句
		if (g_ptCurPage && g_ptCurPage->ptNextPage)
		{
			g_ptCurPage = g_ptCurPage->ptNextPage;
			return 0;
		}

		//为下一页申请内存,同时做一些初始化,ptPage是个双向链表成员
		ptPage = malloc(sizeof(T_PageDesc));
		if (ptPage)
		{
			//记录下该页位置,以及记录下一页位置,g_pucLcdNextPosAtFile怎么来的呢?在 
              showOnePage中得到的

			ptPage->pucLcdFirstPosAtFile         = pucTextFileMemCurPos;
			ptPage->pucLcdNextPageFirstPosAtFile = g_pucLcdNextPosAtFile;
			ptPage->ptPrePage                    = NULL;
			ptPage->ptNextPage                   = NULL;
			g_ptCurPage = ptPage;
			DBG_PRINTF("%s %d, pos = 0x%x\n", __FUNCTION__, __LINE__, (unsigned int)ptPage->pucLcdFirstPosAtFile);

			//将ptPage添加入g_ptPages链表中,RecordPage 中会对双链表操作,指定next和pre
			RecordPage(ptPage);
			return 0;
		}
    }

2.分析主要的显示函数ShowOnePage

2.1 得到了该编码用多少字节表示这个“字” ,得到dwCode 编码的内容,这就是GetCodeFrmBuf 里的功能(如何得到这些,需要相关编码知识)

iLen = g_ptEncodingOprForFile->GetCodeFrmBuf(pucBufStart, g_pucTextFileMemEnd, &dwCode)

 pucBufStart = pucTextFileMemCurPos,pucTextFileMemCurPos 数据真正开始的地方,在 (五.1.1)中注明来源

 g_pucTextFileMemEnd 即文本结束的地址,在(二.2)中得到

2.2 得到下一“字”(这里的字,表示要显示的一个字,如一个汉字“中”或)开始的地址 pucBufStart += iLen ,获得支持的库的结构体

ptFontOpr = g_ptEncodingOprForFile->ptFontOprSupportedHead;,准备开始转换位图

2.3 转换位图 得到位图存于tFontBitMap,并且重新计算显示位置,显示位图 ShowOneFont(&tFontBitMap),计算下一“字”的原点坐标以及数据开始的地方,break跳出又从(五.2.1)开始执行。具体代码注释如下:

while (ptFontOpr)
{
    DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    //根据编码开始转换位图,转换成功返回0 ,且位图存在 tFontBitMap
	iError = ptFontOpr->GetFontBitmap(dwCode, &tFontBitMap);
	DBG_PRINTF("%s %s %d, ptFontOpr->name = %s, %d\n", __FILE__, __FUNCTION__, __LINE__,         
                   ptFontOpr->name, iError);

	if (0 == iError)
	{
		DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

		//重新计算显示位置
		if (RelocateFontPos(&tFontBitMap))
		{
			/* 剩下的LCD空间不能满足显示这个字符 */
			return 0;
		}
		DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

		if (bHasNotClrSceen)
		{
			/* 首先清屏 */
			g_ptDispOpr->CleanScreen(COLOR_BACKGROUND);
				bHasNotClrSceen = 0;
		}
		DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

		/* 显示一个字符 */
		if (ShowOneFont(&tFontBitMap))
		{
			return -1;
		}

		//获得下一“字”的原点坐标以及数据开始的地方
		tFontBitMap.iCurOriginX = tFontBitMap.iNextOriginX;
		tFontBitMap.iCurOriginY = tFontBitMap.iNextOriginY;
		g_pucLcdNextPosAtFile = pucBufStart;

		/* 跳出while循环,继续取出下一个编码来显示 */
		break;
	 }
	ptFontOpr = ptFontOpr->ptNext;
}	

关于转换位图 怎么转换呢?可以去一个库文件看看便知,如freetype.c 主要是设置 各种参数,Xtop,Ytop,buf地址等,这些值怎么来的呢?是初始化的时候,我们就去计算来的。

2.3.1这里面我们还要关心的两个函数是RelocateFontPos(&tFontBitMap) 重新计算位置 和 ShowOneFont(&tFontBitMap) 显示一个字符,重新计算位置这个函数都比较简单,根据位图信息,如果Xmax>Xres(x方向分辨率)就换行,同理Ymax>Yres换页,满页了,返回,不显示该字了。

2.3.2说一下ShowOneFont 会里面是个双层for循环,即y方向和x方向双层for,显示位图的每一个像素,如果该像素有值就显示,没有值就显示和背景色一样的颜色。显示每一个像素就调用到了 显示设备的 g_ptDispOpr->ShowPixel(x, y, unsigned int dwColor);

问题1.   x,y的参数怎么来的?

答:去看ShowOneFont(&tFontBitMap),根据传入的位图,位图的x,y坐标等信息都存在了里面,我们从位图原点开始画每个像素即可。

问题2. 怎么显示一个像素的呢?

首先获得该像素的显示地址:

//得到该像素的显示地址,g_dwLineWidth * iY表示在哪一行,g_dwPixelWidth * iX 表示x值
pucFB  = g_pucFBMem + g_dwLineWidth * iY + g_dwPixelWidth * iX;

然后根据传入的颜色值dwColor 这个颜色值 转换成  8bpp、16bpp或者32bpp 的 color值,再将其赋值到 显示地址,即可显示出来。

六.根据输入事件判断显示上一页还是下一页

我们在(四.2)时AllInputDevicesInit(),为输入事件创建了子线程函数并且让输入事件 ptTmp->GetInputEvent 休眠了

iRet = ts_read(g_tTSDev, &tSamp, 1); /* 如果无数据则休眠 */,它的唤醒函数 InputEventTreadFunction。有数据传来,处理数据,给

ptInputEvent->iVal赋值,然后返回0,GetInputEvent ==0 就会执行相应的if语句了。

七.总结 

其实分析那么多,最主要的是想把细节说清楚,掌握这些细节的知识,如freetype是怎么获得位图的,才算是真正的懂。如果答不出来,说明只是半桶水而已。

所以,细节又涉及很多知识点,具体知识点不在这里说明,只说方法。我认为重要的细节点是

1.ascii、utf-8、utf-16be、utf-16le编码如何分辨?即如何分辨出文件的编码格式,很简单,根据数据头部信息判断格式

如何解码?主要是掌握utf-8, 因为它不固定,即表示一个字用的字节数不固定

2.各库freetype·,gdk,ascii如何 转换为位图?其中,以freetype最为重要

3.如何在lcd上显示?这个问题相当复杂,详细可以看第五大点,细分为以下几个问题

如何显示在lcd显示一个像素(提示:mmap显存空间,将数据以x,y计算所得位置),如何显示一个位图的数据,如何显示一页数据,怎么换行,怎么换页(提示:每次会计算下一页地址,并且该页会放入双链表中),输入事件怎么休眠的?

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值