Loading

数码相框-测试&项目效果&部分代码讲解

测试

在makefile加上这句话意味着把警告当成错误处理:

image

这里看视频跟着来就好了。

input_manager.c详解


#include <config.h>
#include <input_manager.h>
#include <string.h>

static PT_InputOpr g_ptInputOprHead;
static T_InputEvent g_tInputEvent;

static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;

/**
 * 注册一个输入操作
 * @param ptInputOpr 指向输入操作结构体的指针
 * @return 总是返回0
 */

int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
    PT_InputOpr ptTmp;

    // 如果链表为空,将传入的操作作为头结点
    if (!g_ptInputOprHead)
    {
        g_ptInputOprHead   = ptInputOpr;
        ptInputOpr->ptNext = NULL;
    }
    else
    {
        // 遍历链表,找到最后一个节点
        ptTmp = g_ptInputOprHead;
        while (ptTmp->ptNext)
        {
            ptTmp = ptTmp->ptNext;
        }
        // 将新操作添加到链表末尾
        ptTmp->ptNext	  = ptInputOpr;
        ptInputOpr->ptNext = NULL;
    }

    return 0;
}

/**
 * 函数名称:ShowInputOpr
 * 功能描述:遍历并显示输入操作的列表。
 * 参数列表:无
 * 返回值:无
 */

void ShowInputOpr(void)
{
    int i = 0; // 用于编号的计数器
    PT_InputOpr ptTmp = g_ptInputOprHead; // 指向输入操作链表头部的指针

    while (ptTmp) // 遍历链表
    {
        printf("%02d %s\n", i++, ptTmp->name); // 打印操作的编号和名称
        ptTmp = ptTmp->ptNext; // 指向下一个输入操作
    }
}
/**
 * @brief 输入事件线程函数
 * 
 * 该函数为一个持续运行的线程,其主要功能是从外部获取输入事件,然后将该事件通过条件变量通知给主线程。
 * 使用互斥锁来保护对全局输入事件变量的访问,以确保线程安全。
 * 
 * @param pVoid 指向函数的指针,该函数用于获取输入事件。
 * @return void* 总是返回NULL,因为线程设计为长期运行,不会主动退出。
 */
static void *InputEventTreadFunction(void *pVoid)
{
    T_InputEvent tInputEvent;
  
    /* 将传入的void指针转换为获取输入事件的函数指针 */
    int (*GetInputEvent)(PT_InputEvent ptInputEvent);
    GetInputEvent = (int (*)(PT_InputEvent))pVoid;

    while (1)
    {
        /* 调用函数获取输入事件,如果获取成功,则通知主线程 */
        if(0 == GetInputEvent(&tInputEvent))
        {
            /* 在访问共享资源之前锁定互斥量,以确保线程安全 */
            pthread_mutex_lock(&g_tMutex);
            g_tInputEvent = tInputEvent; // 更新全局输入事件变量

            /* 发送信号以唤醒等待该事件的主线程 */
            pthread_cond_signal(&g_tConVar);

            /* 获取事件后释放互斥量 */
            pthread_mutex_unlock(&g_tMutex);
        }
    }

    return NULL;
}

/**
 * 初始化所有输入设备
 * 
 * 此函数遍历输入设备链表,对每个设备调用其初始化函数。如果设备初始化成功,
 * 则为该设备创建一个事件处理线程。
 * 
 * @return 成功初始化所有设备返回0,否则返回-1。
 */
int AllInputDevicesInit(void)
{
    PT_InputOpr ptTmp = g_ptInputOprHead; // 指向输入操作头部的指针
    int iError = -1; // 初始化错误码

    while (ptTmp)
    {
        // 尝试初始化设备
        if (0 == ptTmp->DeviceInit())
        {
            /* 创建设备的事件处理线程 */
			/* 创建子线程 */
            pthread_create(&ptTmp->tTreadID, NULL, InputEventTreadFunction, ptTmp->GetInputEvent);          
            iError = 0; // 设备初始化成功,错误码设为0
        }
        ptTmp = ptTmp->ptNext; // 指向下一个输入设备
    }
    return iError; // 返回错误码
}

/**
 * 获取输入事件
 * 
 * 本函数用于等待并获取输入事件。当有输入事件发生时,其他线程会唤醒等待中的函数实例。
 * 
 * @param ptInputEvent 指向输入事件结构体的指针,函数将在这里返回接收到的输入事件。
 * @return 总是返回0,表示操作成功。
 */
int GetInputEvent(PT_InputEvent ptInputEvent)
{
    /* 锁定互斥体以保护共享资源 */
	/* 休眠 */
    pthread_mutex_lock(&g_tMutex);
    /* 在条件变量上等待,直到被其他线程唤醒 */
    pthread_cond_wait(&g_tConVar, &g_tMutex);

	/* 被唤醒后,返回数据 */
    /* 唤醒后,将接收到的输入事件赋值给传入的指针 */
    *ptInputEvent = g_tInputEvent;
    /* 解锁互斥体 */
    pthread_mutex_unlock(&g_tMutex);
    return 0;
}
/**
 * 初始化输入设备
 * 本函数用于初始化标准输入设备(如键盘)和触摸屏等输入设备。
 * 
 * @param 无
 * @return 返回初始化过程中的错误码。如果初始化过程中有任何一步失败,都会返回相应的错误码。
 */
int InputInit(void)
{
	int iError; // 用于存储初始化过程中的错误码

	// 初始化标准输入设备
	iError = StdinInit();

	// 初始化触摸屏设备,并将之前的错误码与触摸屏初始化的错误码进行或运算,合并错误信息
	iError |= TouchScreenInit();

	return iError; // 返回错误码
}

这段C代码实现了一个输入事件管理系统,主要包括以下功能:

  1. 输入操作注册与显示

    • RegisterInputOpr()​ 函数用于注册一个新的输入操作到全局链表 g_ptInputOprHead​ 中。
    • ShowInputOpr()​ 函数遍历并打印已注册的所有输入操作的编号和名称。
  2. 输入事件线程与同步机制

    • 定义了全局变量 g_tInputEvent​ 存储当前输入事件,以及互斥锁 g_tMutex​ 和条件变量 g_tConVar​ 用于线程间同步。
    • InputEventTreadFunction()​ 是一个输入事件处理线程,它负责从外部获取输入事件,并通过条件变量通知主线程。互斥锁用于保护对全局输入事件变量的访问。
    • GetInputEvent()​ 函数等待并获取输入事件。当有事件发生时,它会被输入事件线程唤醒,并从全局变量中获取输入事件。
  3. 输入设备初始化与事件处理

    • AllInputDevicesInit()​ 函数遍历输入操作链表,对每个设备调用其 DeviceInit()​ 函数进行初始化。若初始化成功,为该设备创建一个输入事件处理线程(调用 InputEventTreadFunction()​)。
    • InputInit()​ 函数负责初始化标准输入设备(如键盘)和触摸屏等输入设备,并返回初始化过程中的错误码。

整体来看,这段代码构建了一个支持多输入设备、多输入操作的输入事件管理系统。通过注册输入操作,可以将不同类型的输入事件关联到相应的处理逻辑。系统使用线程和同步机制确保输入事件的高效、安全地传递与处理。最后,通过初始化函数设置好各个输入设备,使得整个系统能够正常运行。

选择并初始化默认设备

/**
 * 选择并初始化默认显示设备
 * @param name 指向要选择的显示设备名称的字符指针。
 * 注意:该函数不返回任何值。
 */
void SelectAndInitDefaultDispDev(char *name)
{
    // 根据提供的设备名称获取显示操作句柄
    g_ptDefaultDispOpr = GetDispOpr(name);
    if (g_ptDefaultDispOpr)
    {
        // 如果找到设备,则初始化设备
        g_ptDefaultDispOpr->DeviceInit();
        // 清除屏幕,准备显示
        g_ptDefaultDispOpr->CleanScreen(0);
    }
}

该函数用于选择并初始化默认的显示设备。函数的参数是指向要选择的显示设备名称的字符指针。函数内部根据提供的设备名称获取显示操作句柄,如果找到该设备,则对其进行初始化,并清除屏幕准备显示。

在开发板上测试

安装触摸屏驱动,设置环境变量:

image

新建图标目录,放入图标:

image

image

运行程序:

image

修改按钮按下的取反函数

像素跨越多个字节的情况

通常情况下,一个像素的颜色由若干个字节(通常为1至4个字节,取决于色彩深度)来表示。这里的 iBpp​ 表示每像素位数(bits per pixel),即每个像素所占的位数。例如:

  • 对于单色或黑白图像,iBpp​ 可能是 1,每个像素由 1 个位表示。
  • 对于常见的 24 位真彩色图像,iBpp​ 为 24,每个像素由 3 个字节(红、绿、蓝分量各占 8 位)表示。
  • 对于 32 位真彩色带 Alpha 通道的图像,iBpp​ 为 32,每个像素由 4 个字节表示(红、绿、蓝分量各占 8 位,Alpha 通道占 8 位)。

由于没有正确处理像素与字节之间的关系(例如,当 iBpp​ 不等于 8 时,一个像素可能跨越多个字节),直接以像素为单位更新索引可能会导致访问内存时跨过像素边界,从而影响颜色取反的效果,所以需要修改按钮按下的取反函数:


/*
 * 功能:翻转指定按钮区域的颜色
 * 参数:
 *      ptLayout - 指向按钮布局信息的指针
 * 返回值:无
 */

static void InvertButton(PT_Layout ptLayout)
{
    int iY;                      
    int i;                      
    int iButtonWidthBytes;      
    unsigned char *pucVideoMem;   
    PT_DispOpr ptDispOpr = GetDefaultDispDev(); /* 获取默认显示设备的操作接口 */

    pucVideoMem = ptDispOpr->pucDispMem;    
    pucVideoMem += ptLayout->iTopLeftY * ptDispOpr->iLineWidth + ptLayout->iTopLeftX * ptDispOpr->iBpp / 8; /* 计算按钮在Framebuffer中的起始地址 */
    iButtonWidthBytes = (ptLayout->iBotRightX - ptLayout->iTopLeftX) * ptDispOpr->iBpp / 8; /* 计算按钮宽度需要的字节 */

    /* 遍历按钮的每个像素行 */
    for (iY = ptLayout->iTopLeftY; iY <= ptLayout->iBotRightY; iY++)
    {
        /* 遍历当前行的每个像素 */
        for (i = 0; i < iButtonWidthBytes; i++)
        {
            pucVideoMem[i] = ~pucVideoMem[i];  /* 对当前像素的颜色取反 */
        }
        pucVideoMem += ptDispOpr->iLineWidth; /* 移动到下一行的起始位置 */
    }
}

项目效果演示

效果演示

效果演示:

image

动画

屏幕校准:

image

编译使用最终版的数码相框​

​​image

image

​​image​​

​​​image​​​

编译移植tslib:

韦东山 数码相框 项目学习(六)tslib的移植_tslib 源码包-CSDN博客

如果内核比较新,需要修改头文件,不然用不了:

image

驱动程序需要看这一节内容:

image

完整代码讲解

修改main_page

image

模块介绍

debug模块:

int DebugInit(void)
{
	int iError;

	iError = StdoutInit();
	iError |= NetPrintInit();
	return iError;
}

通过调用StdoutInit​和NetPrintInit​两个函数来初始化不同的调试输出通道,可以视为对特定功能(调试输出的初始化)进行了封装。这两个函数内部可能各自负责设置和配置其对应的输出机制(标准输出或网络打印),对外提供统一的接口,隐藏了实现细节。

image

display模块:

image

input模块:

image

要使用标准输入,必须要位于要程序位于前台。

font模块:

image

数码相框并没有用到asci i和gbk。

render模块:

image

page模块:

image

file模块:

image

encoding模块:

image

流程介绍

  1. 初始化模块数据:

    	/* 注册调试通道 */
    	DebugInit();
    
    	/* 初始化调试通道 */
    	InitDebugChanel();
    
    	if (argc != 2)
    	{
    		DBG_PRINTF("Usage:\n");
    		DBG_PRINTF("%s <freetype_file>\n", argv[0]);
    		return 0;
    	}
    
    	/* 注册显示设备 */
    	DisplayInit();
    	/* 可能可支持多个显示设备: 选择和初始化指定的显示设备 */
    	SelectAndInitDefaultDispDev("fb");
    
    	/* 
    	 * VideoMem: 为加快显示速度,我们事先在内存中构造好显示的页面的数据,
    	             (这个内存称为VideoMem)
    	 *           显示时再把VideoMem中的数据复制到设备的显存上
    	 * 参数的含义就是分配的多少个VideoMem
    	 * 参数可取为0, 这意味着所有的显示数据都是在显示时再现场生成,然后写入显存
    	 */
    	AllocVideoMem(5);
    
        /* 注册输入设备 */
    	InputInit();
        /* 调用所有输入设备的初始化函数 */
    	AllInputDevicesInit();
    
        /* 注册编码模块 */
        EncodingInit();
    
        /* 注册字库模块 */
    	iError = FontsInit();
    	if (iError)
    	{
    		DBG_PRINTF("FontsInit error!\n");
    	}
    
        /* 设置freetype字库所用的文件和字体尺寸 */
    	iError = SetFontsDetail("freetype", argv[1], 24);
    	if (iError)
    	{
    		DBG_PRINTF("SetFontsDetail error!\n");
    	}
    
        /* 注册图片文件解析模块 */
        PicFmtsInit();
    
        /* 注册页面 */
    	PagesInit();
    
  2. "主页面"的运行函数:

    static void MainPageRun(PT_PageParams ptParentPageParams)
    {
    	int iIndex;
    	T_InputEvent tInputEvent;
    	int bPressed = 0;
    	int iIndexPressed = -1;
        T_PageParams tPageParams;
    
        tPageParams.iPageID = ID("main");
    
    	/* 1. 显示页面 */
    	ShowMainPage(&g_tMainPageLayout);
    
    	/* 2. 创建Prepare线程 */
    
    	/* 3. 调用GetInputEvent获得输入事件,进而处理 */
    	while (1)
    	{
    		iIndex = MainPageGetInputEvent(&g_tMainPageLayout, &tInputEvent);
    		if (tInputEvent.iPressure == 0)
    		{
    			/* 如果是松开 */
    			if (bPressed)
    			{
    				/* 曾经有按钮被按下 */
    				ReleaseButton(&g_atMainPageIconsLayout[iIndexPressed]);
    				bPressed = 0;
    
    				if (iIndexPressed == iIndex) /* 按下和松开都是同一个按钮 */
    				{
    					switch (iIndexPressed)
    					{
    						case 0: /* 浏览按钮 */
    						{
    							Page("browse")->Run(&tPageParams);
    
    							/* 从设置页面返回后显示当首的主页面 */
    							ShowMainPage(&g_tMainPageLayout);
    
    							break;
    						}
    						case 1: /* 连播按钮 */
    						{
    							Page("auto")->Run(&tPageParams);
    
    							/* 从设置页面返回后显示当首的主页面 */
    							ShowMainPage(&g_tMainPageLayout);
    
    							break;
    						}
    						case 2: /* 设置按钮 */
    						{
    							Page("setting")->Run(&tPageParams);
    
    							/* 从设置页面返回后显示当首的主页面 */
    							ShowMainPage(&g_tMainPageLayout);
    
    							break;
    						}
    						default:
    						{
    							break;
    						}
    					}
    				}
    		
    				iIndexPressed = -1;
    			}
    		}
    		else
    		{
    			/* 按下状态 */
    			if (iIndex != -1)
    			{
    				if (!bPressed)
    				{
    					/* 未曾按下按钮 */
    					bPressed = 1;
    					iIndexPressed = iIndex;
    					PressButton(&g_atMainPageIconsLayout[iIndexPressed]);
    				}
    			}
    		}
    	}
    }
    

    该函数是一个“主页面”的运行函数,主要实现了以下功能:

    1. 在页面上显示菜单图标。
    2. 根据用户输入事件进行相应处理,包括按下和松开按钮等。
    3. 根据用户选择,调用相应的功能页面并执行相应的操作,如浏览、连播和设置等。
    4. 在从设置页面返回后,显示当前的主页面。

    该函数通过循环获取输入事件,并根据事件类型进行相应的处理。在按下按钮时,记录按下按钮的索引,并在松开按钮时,根据按下和松开的按钮是否相同来执行相应的操作。如果相同,则根据按钮索引调用相应的功能页面,并在返回后显示主页面。

  3. 显示页面:

     /* 运行主页面 */
    Page("main")->Run(NULL);
    

    显示主页面:

    /* 1. 显示页面 */
    ShowMainPage(&g_tMainPageLayout);
    
    static void ShowMainPage(PT_PageLayout ptPageLayout)
    {
    	PT_VideoMem ptVideoMem;
    	int iError;
    
    	PT_Layout atLayout = ptPageLayout->atLayout;
    
    	/* 1. 获得显存 */
    	ptVideoMem = GetVideoMem(ID("main"), 1);
    	if (ptVideoMem == NULL)
    	{
    		DBG_PRINTF("can't get video mem for main page!\n");
    		return;
    	}
    
    	/* 2. 描画数据 */
    
    	/* 如果还没有计算过各图标的坐标 */
    	if (atLayout[0].iTopLeftX == 0)
    	{
    		CalcMainPageLayout(ptPageLayout);
    	}
    
    	iError = GeneratePage(ptPageLayout, ptVideoMem);
    
    	/* 3. 刷到设备上去 */
    	FlushVideoMemToDev(ptVideoMem);
    
    	/* 4. 解放显存 */
    	PutVideoMem(ptVideoMem);
    }
    
    

    该函数的功能是显示一个名为"主页面"的界面。函数通过传入的ptPageLayout​​​​​参数获取页面布局信息,其中包括多个图标的文件名和显示区域。函数主要执行以下操作:

    1. 调用GetVideoMem​​​​​函数获取显示主页面所需的显存。
    2. 若获取显存失败,则打印错误信息并返回。
    3. 调用CalcMainPageLayout​​​​​函数计算各图标的坐标(如果之前未计算过)。
    4. 调用GeneratePage​​​​​函数根据页面布局信息和显存生成页面。
    5. 调用FlushVideoMemToDev​​​函数将生成的页面刷到设备上去。
    6. 调用PutVideoMem​​​​函数释放显存。
  4. 生成页面函数:

    int GeneratePage(PT_PageLayout ptPageLayout, PT_VideoMem ptVideoMem)
    {
    	T_PixelDatas tOriginIconPixelDatas;
    	T_PixelDatas tIconPixelDatas;
    	int iError;
    	PT_Layout atLayout = ptPageLayout->atLayout;
    
    	/* 描画数据: VideoMem中的页面数据未生成的情况下才执行下面操作 */
    	if (ptVideoMem->ePicState != PS_GENERATED)
    	{
    		ClearVideoMem(ptVideoMem, COLOR_BACKGROUND);
    
    		tIconPixelDatas.iBpp          = ptPageLayout->iBpp;
            /* 分配一个临时的内存, 它用来存放缩放后的图标数据 */
    		tIconPixelDatas.aucPixelDatas = malloc(ptPageLayout->iMaxTotalBytes);
    		if (tIconPixelDatas.aucPixelDatas == NULL)
    		{
    			return -1;
    		}
    
    		while (atLayout->strIconName)
    		{
                /* 取得图标文件的象素数据 */
    			iError = GetPixelDatasForIcon(atLayout->strIconName, &tOriginIconPixelDatas);
    			if (iError)
    			{
    				DBG_PRINTF("GetPixelDatasForIcon %s error!\n", atLayout->strIconName);
                    free(tIconPixelDatas.aucPixelDatas);
    				return -1;
    			}
     			tIconPixelDatas.iHeight = atLayout->iBotRightY - atLayout->iTopLeftY + 1;
    			tIconPixelDatas.iWidth  = atLayout->iBotRightX - atLayout->iTopLeftX+ 1;
    			tIconPixelDatas.iLineBytes  = tIconPixelDatas.iWidth * tIconPixelDatas.iBpp / 8;
    			tIconPixelDatas.iTotalBytes = tIconPixelDatas.iLineBytes * tIconPixelDatas.iHeight;
    
                /* 把原始的图标象素数据缩放为指定大小 */
     			PicZoom(&tOriginIconPixelDatas, &tIconPixelDatas);
    
                /* 把缩放后的图标数据,合并到VideoMem的指定区域 */
     			PicMerge(atLayout->iTopLeftX, atLayout->iTopLeftY, &tIconPixelDatas, &ptVideoMem->tPixelDatas);
    
                /* 释放原始的图标象素数据 */
     			FreePixelDatasForIcon(&tOriginIconPixelDatas);
     			atLayout++;
    		}
    
            /* 数据上面分配的临时内存 */
    		free(tIconPixelDatas.aucPixelDatas);
    		ptVideoMem->ePicState = PS_GENERATED;
    	}
    
    	return 0;
    }
    

    该函数的功能是从图标文件中解析出图像数据并将其放在指定区域,从而生成页面数据。函数接受两个参数,一个是包含多个图标的文件名和显示区域的ptPageLayout​​​​​,另一个是在VideoMem​​​​​里构造页面数据的ptVideoMem​​​​​。函数首先检查VideoMem​​​​​中的页面数据是否已生成,如果未生成则执行以下操作:清除VideoMem​​​​​,分配一个临时内存来存放缩放后的图标数据,循环遍历布局中的每个图标,获取图标文件的像素数据,将原始图标像素数据缩放为指定大小,将缩放后的图标数据合并到VideoMem​​​​​的指定区域,释放原始图标像素数据,最后释放临时内存并将VideoMem​​​​​的页面数据状态设置为已生成。函数返回值为0表示成功,其他值表示失败。

  5. GetPixelDatasForIcon函数:

    int GetPixelDatasForIcon(char *strFileName, PT_PixelDatas ptPixelDatas)
    {
        T_FileMap tFileMap;
        int iError;
        int iXres, iYres, iBpp;
    
        /* 图标存在 /etc/digitpic/icons */
        snprintf(tFileMap.strFileName, 128, "%s/%s", ICON_PATH, strFileName);
        tFileMap.strFileName[127] = '\0';
         // 将文件映射到内存
        iError = MapFile(&tFileMap);
        if (iError)
        {
            DBG_PRINTF("MapFile %s error!\n", strFileName);
            return -1;
        }
     	// 检查文件格式是否支持
        iError = Parser("bmp")->isSupport(&tFileMap);
        if (iError == 0)
        {
            DBG_PRINTF("can't support this file: %s\n", strFileName);
            UnMapFile(&tFileMap);
            return -1;
        }
    	// 获取显示分辨率和位深度
        GetDispResolution(&iXres, &iYres, &iBpp);
        ptPixelDatas->iBpp = iBpp;
    	 // 从文件中提取像素数据
        iError = Parser("bmp")->GetPixelDatas(&tFileMap, ptPixelDatas);
        if (iError)
        {
            DBG_PRINTF("GetPixelDatas for %s error!\n", strFileName);
            UnMapFile(&tFileMap);
            return -1;
        }
    	// 解除文件映射
        UnMapFile(&tFileMap);
        return 0;
    }
    
    

    该函数的功能是读取位于/etc/digitpic/icons目录下的BMP格式图标文件的像素数据,并将这些数据存储在通过malloc分配的空间中,该空间由函数的调用者在不需要时使用FreePixelDatasForIcon函数释放。函数返回0表示成功,返回其他值表示失败。函数内部使用了文件映射和解析器功能来处理文件,并获取显示分辨率和像素数据。

  6. ​​FlushVideoMemToDev​函数:

    void FlushVideoMemToDev(PT_VideoMem ptVideoMem)
    {
        // 如果ptVideoMem没有使用设备的帧缓冲,则调用显示设备的ShowPage函数显示图像
        if (!ptVideoMem->bDevFrameBuffer)
        {
            GetDefaultDispDev()->ShowPage(ptVideoMem);
        }
    }
    

    函数的作用是将存储在视频内存中的图像数据刷到显示设备上,使得用户可以观察到缓冲区中的图像。

  7. DBG_PRINTF函数:

    int DebugPrint(const char *pcFormat, ...)
    {
        char strTmpBuf[1000]; // 用于格式化后临时存储打印信息的缓冲区
        char *pcTmp;         // 指向处理后待打印字符串的指针
        va_list tArg;        // 可变参数列表处理
        int iNum;            // 格式化后字符串长度
        PT_DebugOpr ptTmp = g_ptDebugOprHead; // 指向调试操作链表头部的指针
        int dbglevel = DEFAULT_DBGLEVEL; // 调试信息级别
    
        /* 处理可变参数,格式化字符串到临时缓冲区 */
    	/* 可变参数的处理, 抄自glibc的printf函数 */
        va_start(tArg, pcFormat);
        iNum = vsprintf(strTmpBuf, pcFormat, tArg);
        va_end(tArg);
        strTmpBuf[iNum] = '\0';
    
        pcTmp = strTmpBuf;  
    
        /* 解析调试信息级别,决定是否打印 */
        if ((strTmpBuf[0] == '<') && (strTmpBuf[2] == '>'))
        {
            dbglevel = strTmpBuf[1] - '0';
            if (dbglevel >= 0 && dbglevel <= 9)
            {
                pcTmp = strTmpBuf + 3;
            }
            else
            {
                dbglevel = DEFAULT_DBGLEVEL;
            }
        }
    
        /* 若调试信息级别高于设置的限制,则不打印 */
        if (dbglevel > g_iDbgLevelLimit)
        {
            return -1;
        }
    
        /* 调用链表中所有可用的调试输出函数,输出调试信息 */
        while (ptTmp)
        {
            if (ptTmp->isCanUse)
            {
                ptTmp->DebugPrint(pcTmp);
            }
            ptTmp = ptTmp->ptNext;
        }
    
        return 0;
    
    }
    

    该函数是一个打印信息的总入口函数,用于处理程序中使用DBG_PRINTF​​​​宏来打印的信息。DBG_PRINTF​​​​的使用方法与printf​​​​完全一致。函数首先将格式化后的字符串存储到临时缓冲区,然后解析调试信息级别并决定是否打印。如果调试信息级别高于设置的限制,则不打印。最后,调用链表中所有可用的调试输出函数,输出调试信息。函数返回0表示打印成功,返回-1表示打印失败。

  8. GenericGetInputEvent函数:

    该函数的功能是读取输入数据(触摸屏事件),并判断该数据是否位于任何一个图标上。函数接收两个参数,一个是指向包含多个图标的显示区域的指针(ptPageLayout),另一个是指向包含得到的输入数据的结构体的指针(ptInputEvent)。函数返回-1表示输入数据不位于任何一个图标之上,返回其他值表示输入数据所落在的图标在PageLayout->atLayout数组中的索引。该函数首先调用GetInputEvent函数获取原始的触摸屏数据,然后检查事件类型是否为触摸屏事件。如果是,则将得到的输入数据赋值给ptInputEvent指向的结构体,并遍历所有图标,检查触点是否落在任一图标上。如果触点落在某个图标上,函数返回该图标在数组中的索引;否则,返回-1。

    int GenericGetInputEvent(PT_PageLayout ptPageLayout, PT_InputEvent ptInputEvent)
    {
    	T_InputEvent tInputEvent;
    	int iRet;
    	int i = 0;
    	PT_Layout atLayout = ptPageLayout->atLayout;
    
    	/* 获得原始的触摸屏数据 
    	 * 它是调用input_manager.c的函数,此函数会让当前线否休眠
    	 * 当触摸屏线程获得数据后,会把它唤醒
    	 */
    	iRet = GetInputEvent(&tInputEvent);
    	if (iRet)
    	{
    		return -1;
    	}
    	/* 如果事件类型不是触摸屏事件,则直接返回 */
    	if (tInputEvent.iType != INPUT_TYPE_TOUCHSCREEN)
    	{
    		return -1;
    	}
    
    	*ptInputEvent = tInputEvent;
    
    	/* 遍历所有图标,检查触点是否落在任一图标上 */
    	while (atLayout[i].strIconName)
    	{
    		if ((tInputEvent.iX >= atLayout[i].iTopLeftX) && (tInputEvent.iX <= atLayout[i].iBotRightX) && \
    			 (tInputEvent.iY >= atLayout[i].iTopLeftY) && (tInputEvent.iY <= atLayout[i].iBotRightY))
    		{
    			/* 找到了被点中的按钮 */
    			return i;
    		}
    		else
    		{
    			i++;
    		}
    	}
    
    	/* 触点没有落在按钮上 */
    	return -1;
    }
    
    
  9. browse页面生成目录和文件:

    该函数GenerateBrowsePageDirAndFile​主要用于为“浏览页面”生成“目录或文件”区域中的图标和文字,即显示目录内容。其功能包括:

    1. 根据输入参数,在屏幕上显示指定开始索引和数量的目录和文件内容。
    2. 使用指定的目录和文件内容数组aptDirContents​,在视频内存ptVideoMem​中构造页面。
    3. 清除即将显示目录和文件内容的区域。
    4. 设置字体大小,以适应显示目录或文件名的区域。
    5. 使用双层循环遍历并显示每个目录或文件,其中根据是目录还是文件显示相应的图标,并在相应位置显示目录或文件名。
    6. 返回成功标志0。

    该函数的输入参数包括起始索引iStartIndex​、目录和文件内容数组数量iDirContentsNumber​、目录和文件内容数组aptDirContents​、视频内存ptVideoMem​。

    该函数的输出参数为空。

    该函数的返回值为成功标志0,或其他值表示失败。

    static int GenerateBrowsePageDirAndFile(int iStartIndex, int iDirContentsNumber, PT_DirContent *aptDirContents, PT_VideoMem ptVideoMem)
    {
        int iError;
        int i, j, k = 0;
        int iDirContentIndex = iStartIndex;
        PT_PageLayout ptPageLayout = &g_tBrowsePageDirAndFileLayout;
        PT_Layout atLayout = ptPageLayout->atLayout;
    
        // 清除即将显示目录和文件内容的区域
        ClearRectangleInVideoMem(ptPageLayout->iTopLeftX, ptPageLayout->iTopLeftY, ptPageLayout->iBotRightX, ptPageLayout->iBotRightY, ptVideoMem, COLOR_BACKGROUND);
    
        // 设置字体大小,以适应显示目录或文件名的区域
        SetFontSize(atLayout[1].iBotRightY - atLayout[1].iTopLeftY - 5);
      
        // 双层循环以遍历并显示每个目录或文件
        for (i = 0; i < g_iDirFileNumPerCol; i++)
        {
            for (j = 0; j < g_iDirFileNumPerRow; j++)
            {
                if (iDirContentIndex < iDirContentsNumber)
                {
                    // 根据是目录还是文件,显示相应的图标
                    if (aptDirContents[iDirContentIndex]->eFileType == FILETYPE_DIR)
                    {
                        PicMerge(atLayout[k].iTopLeftX, atLayout[k].iTopLeftY, &g_tDirClosedIconPixelDatas, &ptVideoMem->tPixelDatas);
                    }
                    else
                    {
                        PicMerge(atLayout[k].iTopLeftX, atLayout[k].iTopLeftY, &g_tFileIconPixelDatas, &ptVideoMem->tPixelDatas);
                    }
    
                    k++;  // 为下一个图标或文件名准备位置
                    // 在相应位置显示目录或文件名
                    iError = MergerStringToCenterOfRectangleInVideoMem(atLayout[k].iTopLeftX, atLayout[k].iTopLeftY, atLayout[k].iBotRightX, atLayout[k].iBotRightY, (unsigned char *)aptDirContents[iDirContentIndex]->strName, ptVideoMem);
                    //ClearRectangleInVideoMem(atLayout[k].iTopLeftX, atLayout[k].iTopLeftY, atLayout[k].iBotRightX, atLayout[k].iBotRightY, ptVideoMem, 0xff0000);
                    k++;  // 为下一个文本行准备位置
    
                    iDirContentIndex++;  // 准备显示下一个目录或文件
                }
                else
                {
                    break;  // 如果已经处理完所有目录和文件内容,则跳出循环
                }
            }
            if (iDirContentIndex >= iDirContentsNumber)
            {
                break;  // 如果已经处理完所有目录和文件内容,则跳出循环
            }
        }
        return 0;
    }
    
  10. MergerStringToCenterOfRectangleInVideoMem函数:

    /**********************************************************************
     * 函数名称: MergerStringToCenterOfRectangleInVideoMem
     * 功能描述: 在VideoMem的指定矩形居中显示字符串
     *            参考: 03.freetype\02th_arm\06th_show_lines_center
     * 输入参数: iTopLeftX,iTopLeftY   - 矩形区域的左上角座标
     *            iBotRightX,iBotRightY - 矩形区域的右下角座标
     *            pucTextString         - 要显示的字符串
     *            ptVideoMem            - VideoMem
     * 输出参数: 无
     * 返 回 值: 0 - 成功,  其他值 - 失败
     * 修改日期        版本号     修改人          修改内容
     * -----------------------------------------------
     * 2013/02/08        V1.0     韦东山          创建
     ***********************************************************************/
    int MergerStringToCenterOfRectangleInVideoMem(int iTopLeftX, int iTopLeftY, int iBotRightX, int iBotRightY, unsigned char *pucTextString, PT_VideoMem ptVideoMem)
    {
    	int iLen;
    	int iError;
    	unsigned char *pucBufStart;
    	unsigned char *pucBufEnd;
    	unsigned int dwCode;
    	T_FontBitMap tFontBitMap;
    
    	int bHasGetCode = 0;
    
    	int iMinX = 32000, iMaxX = -1;
    	int iMinY = 32000, iMaxY = -1;
    
    	int iStrTopLeftX , iStrTopLeftY;
    
    	int iWidth, iHeight;
    
    	tFontBitMap.iCurOriginX = 0;
    	tFontBitMap.iCurOriginY = 0;
    	pucBufStart = pucTextString;
    	pucBufEnd   = pucTextString + strlen((char *)pucTextString);   
    
    	/* 0. 清除这个区域 */
    	ClearRectangleInVideoMem(iTopLeftX, iTopLeftY, iBotRightX, iBotRightY, ptVideoMem, COLOR_BACKGROUND);
    
    	/* 1.先计算字符串显示的总体宽度、高度 */
    	while (1)
    	{
    		/* 从字符串中逐个取出字符 */
    		iLen = GetCodeFrmBuf(pucBufStart, pucBufEnd, &dwCode);
    		if (0 == iLen)
    		{
    			/* 字符串结束 */
    			if (!bHasGetCode)
    			{
    				//DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    				return -1;
    			}
    			else
    			{
    				break;
    			}
    		}
    
    		bHasGetCode = 1;
    		pucBufStart += iLen;
    
    		/* 获得字符的位图, 位图信息里含有字符显示时的左上角、右下角坐标 */
    		iError = GetFontBitmap(dwCode, &tFontBitMap);
    		if (0 == iError)
    		{			
    			// 更新最小/最大x、y坐标					
    			if (iMinX > tFontBitMap.iXLeft)
    			{
    				iMinX = tFontBitMap.iXLeft;
    			}
    			if (iMaxX < tFontBitMap.iXMax)
    			{
    				iMaxX = tFontBitMap.iXMax;
    			}
    
    			if (iMinY > tFontBitMap.iYTop)
    			{
    				iMinY = tFontBitMap.iYTop;
    			}
    			if (iMaxY < tFontBitMap.iXMax)
    			{
    				iMaxY = tFontBitMap.iYMax;
    			}
    		
    			tFontBitMap.iCurOriginX = tFontBitMap.iNextOriginX;
    			tFontBitMap.iCurOriginY = tFontBitMap.iNextOriginY;
    		}
    		else
    		{
    			DBG_PRINTF("GetFontBitmap for calc width/height error!\n");
    		}
    	}
    
    	iWidth  = iMaxX - iMinX;
    	iHeight = iMaxY - iMinY;
    
        /* 如果字符串过长,调整宽度为矩形宽度 */
        if (iWidth > iBotRightX - iTopLeftX)
        {
            iWidth = iBotRightX - iTopLeftX;
        }
    
        /* 如果字符串过高,返回错误 */
    	if (iHeight > iBotRightY - iTopLeftY)
    	{
    		DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    		//DBG_PRINTF("iHeight = %d, iBotRightY - iTopLeftX = %d - %d = %d\n", iHeight, iBotRightY, iTopLeftY, iBotRightY - iTopLeftY);
    		return -1;
    	}
    	//DBG_PRINTF("iWidth = %d, iHeight = %d\n", iWidth, iHeight);
    
    	/* 2.确定第1个字符的原点 
    	 * 2.1 先计算左上角坐标
    	 */
    	iStrTopLeftX = iTopLeftX + (iBotRightX - iTopLeftX - iWidth) / 2;
    	iStrTopLeftY = iTopLeftY + (iBotRightY - iTopLeftY - iHeight) / 2;
    	//DBG_PRINTF("iNewFirstFontTopLeftX = %d, iNewFirstFontTopLeftY = %d\n", iNewFirstFontTopLeftX, iNewFirstFontTopLeftY);
    
    	/*	 
    	 * 2.2 再计算第1个字符原点坐标
    	 * iMinX - 原来的iCurOriginX(0) = iStrTopLeftX - 新的iCurOriginX
    	 * iMinY - 原来的iCurOriginY(0) = iStrTopLeftY - 新的iCurOriginY
    	 */
    	tFontBitMap.iCurOriginX = iStrTopLeftX - iMinX;
    	tFontBitMap.iCurOriginY = iStrTopLeftY - iMinY;
    
    	//DBG_PRINTF("iCurOriginX = %d, iCurOriginY = %d\n", tFontBitMap.iCurOriginX, tFontBitMap.iCurOriginY);
    
    	pucBufStart = pucTextString;
    	bHasGetCode = 0;
    	while (1)
    	{
    		/* 从字符串中逐个取出字符 */
    		iLen = GetCodeFrmBuf(pucBufStart, pucBufEnd, &dwCode);
    		if (0 == iLen)
    		{
    			/* 字符串结束 */
    			if (!bHasGetCode)
    			{
    				DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    				return -1;
    			}
    			else
    			{
    				break;
    			}
    		}
    
    		bHasGetCode = 1;
    		pucBufStart += iLen;
    
    		/* 获得字符的位图 */
    		iError = GetFontBitmap(dwCode, &tFontBitMap);
    		if (0 == iError)
    		{
    			//DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    			// 判断字符是否在矩形区域内,若在则合并到VideoMem显示字符,否则返回0
                if (isFontInArea(iTopLeftX, iTopLeftY, iBotRightX, iBotRightY, &tFontBitMap))
                {
        			if (MergeOneFontToVideoMem(&tFontBitMap, ptVideoMem))
        			{
        				DBG_PRINTF("MergeOneFontToVideoMem error for code 0x%x\n", dwCode);
        				return -1;
        			}
                }
                else
                {
                    return 0;
                }
    			//DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    		
    			tFontBitMap.iCurOriginX = tFontBitMap.iNextOriginX;
    			tFontBitMap.iCurOriginY = tFontBitMap.iNextOriginY;
    		}
    		else
    		{
    			DBG_PRINTF("GetFontBitmap for drawing error!\n");
    		}
    	}
    
    	return 0;
    }
    

    函数名为MergerStringToCenterOfRectangleInVideoMem​,其功能是在VideoMem(视频内存)的指定矩形区域中居中显示字符串。以下是该函数的具体实现过程:

    1. 输入参数初始化

      • 接收5个输入参数:

        • iTopLeftX​和iTopLeftY​:矩形区域左上角坐标。
        • iBotRightX​和iBotRightY​:矩形区域右下角坐标。
        • pucTextString​:指向待显示字符串的指针。
        • ptVideoMem​:指向VideoMem结构体的指针。
      • 初始化一些局部变量,如最小/最大坐标值(iMinX​、iMaxX​、iMinY​、iMaxY​),字符串起始与结束指针(pucBufStart​、pucBufEnd​),以及字体位图结构体T_FontBitMap​。

    2. 清除指定矩形区域

      • 使用ClearRectangleInVideoMem​函数,以指定的矩形区域坐标和背景色,清除VideoMem中的对应区域。
    3. 计算字符串总体宽度和高度

      • 使用循环遍历字符串中的每个字符:

        • 通过GetCodeFrmBuf​函数从字符串中逐个获取字符编码。
        • 对于每个字符,调用GetFontBitmap​函数获取其位图信息(包括左上角、右下角坐标等)。
        • 更新最小/最大坐标值,以便计算字符串整体的宽度(iMaxX - iMinX​)和高度(iMaxY - iMinY​)。
    4. 调整字符串尺寸和定位第一个字符原点

      • 根据计算出的字符串宽度和矩形区域宽度,若字符串过宽,则将其宽度调整为矩形宽度。
      • 若字符串高度超过矩形高度,返回错误(-1)。
      • 计算居中显示时,第一个字符的左上角坐标(iStrTopLeftX​和iStrTopLeftY​)。
      • 根据新计算出的左上角坐标和原始位图信息,更新字体位图结构体中的当前原点坐标(tFontBitMap.iCurOriginX​和tFontBitMap.iCurOriginY​)。
    5. 绘制字符串到VideoMem

      • 重新设置字符串起始指针并重置相关标志。

      • 使用与步骤3相同的循环结构,再次遍历字符串中的每个字符:

        • 获取字符编码及位图信息。
        • 判断字符位图是否在矩形区域内,若在则调用MergeOneFontToVideoMem​函数将字符合并到VideoMem中显示;若不在则返回0。
        • 更新字体位图结构体中的当前原点坐标。
    6. 返回结果

      • 若所有字符均成功合并到VideoMem且无其他错误,返回0表示成功;否则返回非零值表示失败。
  11. GetCodeFrmBuf函数:

    /**********************************************************************
     * 函数名称: GetCodeFrmBuf
     * 功能描述: 从字符串中取出UTF8格式的字符串的UNICODE编码值
     * 输入参数: pucBufStart - 字符串起始位置
     *            pucBufEnd   - 字符串结束位置(这个位置的字符不处理)
     * 输出参数: pdwCode     - 所取出的编码值存在这里
     * 返 回 值: 使用了多少个字符才得到这个编码值
     * 修改日期        版本号     修改人	      修改内容
     * -----------------------------------------------
     * 2013/02/08	     V1.0	  韦东山	      创建
     ***********************************************************************/
    int GetCodeFrmBuf(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode)
    {
        return Encode("utf-8")->GetCodeFrmBuf(pucBufStart, pucBufEnd, pdwCode);
    }
    
    

posted @ 2024-04-13 19:25  阿四与你  阅读(6)  评论(0编辑  收藏  举报