之前我做过一个电子相框的项目,涉及到的重难点主要为:在LCD上放大、缩小、移动图片。
首先我们得明白的一点是:无论是放大或缩小,实际上都是对原图进行等比例的缩小,然后在LCD上面显示,只不过缩小的程度不同罢了(关于如何取出原图的数据,然后缩小为我们需要的大小,可以看我另外一篇博客:https://blog.csdn.net/qq_37659294/article/details/104382032)。
经过上一篇博客介绍的缩放算法之后,我们已经得到一张缩小后、和原图等比例的图片并存放在buffer中,但这只是得到一张图片,还没有显示到LCD上,那么我们如何将这张图片在LCD上动态地放大、缩小、移动呢?
首先,我们来看一下一个非常关键的函数,ShowZoomedPictureInLayout函数,这个函数是在LCD上显示一张经过缩放、移动的图片,因为图片的大小可能比显示区域要大,也有可能小,也有可能被移动到相框的边缘,只能显示图片的一部分,所以我们必须确认要从图片的哪里(iStartXofNewPic,iStartYofNewPic)开始取数据,在显示区域的哪里(iStartXofOldPic,iStartYofOldPic)开始显示,显示多大(iWidthPictureInPlay,iHeightPictureInPlay)的区域。
ShowZoomedPictureInLayout函数的实现思路为:
①计算出iStartXofNewPic,iStartYofNewPic,判断要从图片的哪个位置开始取数据
②利用 g_iXofZoomedPicShowInCenter - iStartXofNewPic = iDeltaX = 显示区中心点X坐标(g_tManualPictureLayout.iTopLeftX + iPictureLayoutWidth / 2) - iStartXofOldPic(LCD上显示图片的起始坐标)等式算出iStartXofOldPic,iStartYofOldPic,判断要从显示区域的哪个位置开始显示图片
③计算出实际显示的宽度iWidthPictureInPlay和高度iHeightPictureInPlay
④根据上面计算好的参数,把图片数据刷到LCD的framebuffer中
/**********************************************************************
* 函数名称: ShowZoomedPictureInLayout
* 功能描述: 在"manual页面"中显示经过缩放的图片
* 输入参数: ptZoomedPicPixelDatas - 内含已经缩放的图片的象素数据
* ptVideoMem - 在这个VideoMem中显示
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
static void ShowZoomedPictureInLayout(PT_PixelDatas ptZoomedPicPixelDatas, PT_VideoMem ptVideoMem)
{
/* iStartXofNewPic和iStartYofNewPic这两个变量意思不是新图片在显示区域(或者整个屏幕区域)的xy坐标
* 而是从缩放后的图片坐标(iStartXofNewPic,iStartYofNewPic)开始,显示这个图片
* 坐标(x,y)之前的一块区域,即横坐标小于x,纵坐标小于y的所有地方,都不显示
* 因为可能缩放后的图片,不能完全显示到屏幕上,只能显示一部分
* 算出从图片什么地方开始显示,并且加上一个长和宽,那么在屏上显示的部分就可以描绘出来了
*/
int iStartXofNewPic, iStartYofNewPic;
/* iStartXofOldPic和iStartYofOldPic这两个变量并说不是上一张图片的显示位置,而是新图片在屏幕显示的起始坐标
* 这六个变量连起来想就是:
* 从zoom后的图片的(iStartXofNewPic,iStartYofNewPic)这个地方开始
* 取长宽为iWidthPictureInPlay, iHeightPictureInPlay这么大的一块区域
* 显示到显存的(iStartXofOldPic,iStartYofOldPic)这个地方去
*/
int iStartXofOldPic, iStartYofOldPic;
int iWidthPictureInPlay, iHeightPictureInPlay;
int iPictureLayoutWidth, iPictureLayoutHeight;
int iDeltaX, iDeltaY;//计算过程的中间变量
/* iPictureLayoutWidth 显示区域的宽
* iPictureLayoutHeight 显示区域的高
*/
iPictureLayoutWidth = g_tManualPictureLayout.iBotRightX - g_tManualPictureLayout.iTopLeftX + 1;
iPictureLayoutHeight = g_tManualPictureLayout.iBotRightY - g_tManualPictureLayout.iTopLeftY + 1;
/* g_iXofZoomedPicShowInCenter 是 图片最左边 到 显示区中心 的距离,g_iXofZoomedPicShowInCenter = 缩放后宽的一半 + 移动的偏移量 (左移时为正,右移时为负(移动太大有可能变成负数的))
* 这个变量第一次赋值是在ShowPictureInManualPage(第一次显示图片(居中显示),这时候图片的大小比LCD的图片显示区域小)这个函数里,其值就是显示图片的宽的一半,也代表着最左边到中心的距离
* 在不移动时,g_iXofZoomedPicShowInCenter值跟着放大和缩小相同的倍数
* 当移动的时候,在ManualPageRun函数里"移动图片"的处理逻辑中,左移就增加,右移就减小,
* 同理 g_iYofZoomedPicShowInCenter 是图片显示位置最上面到显示区域中心的距离
*/
iStartXofNewPic = g_iXofZoomedPicShowInCenter - iPictureLayoutWidth/2;//利用g_iXofZoomedPicShowInCenter算出iStartXofNewPic,便知道了要从图片的哪个位置取出数据来显示了,对应上面思路的第①步
if (iStartXofNewPic < 0)//即g_iXofZoomedPicShowInCenter < iPictureLayoutWidth/2(显示区宽的一半),说明图片的最左边在显示区域内
{
iStartXofNewPic = 0;//如果图片的最左边在显示区域内,那么就从图片的最左边开始取数据
}
/* 这中间还有一种情况,就是
* 当 (iStartXofNewPic > 0) && (iStartXofNewPic < ptZoomedPicPixelDatas->iWidth) 时
* 表示 图片最左边 已经移出了显示区域(最右边还在显示区域内),只能显示一部分
* 此时 iStartXofNewPic = g_iXofZoomedPicShowInCenter - iPictureLayoutWidth/2;
* 表示就从图片的这个地方开始显示
*/
if (iStartXofNewPic > ptZoomedPicPixelDatas->iWidth)//即g_iXofZoomedPicShowInCenter > ptZoomedPicPixelDatas->iWidth(图宽) + iPictureLayoutWidth/2(显示区宽的一半),说明图片已经完全左移出去了
{
iStartXofNewPic = ptZoomedPicPixelDatas->iWidth;//整张图片都已经被左移出显示区域了,不用显示了
}
/* iDeltaX 是 实际所显示图片的起始位置 到 显示区域中心点 的距离,由g_iXofZoomedPicShowInCenter减去iStartXofNewPic得到,只是一个用来计算iStartXofOldPic的中间变量
* 因为可能会移动到或者放大到左边不能从头开始,就是 0<iStartXofNewPic<ptZoomedPicPixelDatas->iWidth的情况了
* 这个式子算出的是,实际图片开始显示的最左边 到 显示区域中心点的距离
* iDeltaX有可能是负数(当图片移动到中心线右边时,那么iDeltaX就是个负数了),但因为我们是利用数学等式来计算iStartXofOldPic,最后负负会得正
*/
iDeltaX = g_iXofZoomedPicShowInCenter - iStartXofNewPic;
/* g_iXofZoomedPicShowInCenter - iStartXofNewPic
* = iDeltaX
* = 显示区中心点X坐标(g_tManualPictureLayout.iTopLeftX + iPictureLayoutWidth / 2) - iStartXofOldPic(LCD上显示图片的起始坐标)
* 根据上面的等式我们可算出 iStartXofOldPic
*/
iStartXofOldPic = (g_tManualPictureLayout.iTopLeftX + iPictureLayoutWidth / 2) - iDeltaX;
/* 当 iStartXofNewPic = ptZoomedPicPixelDatas->iWidth 时,说明整张图片已经从左边划出去了
* iDeltaX = g_iXofZoomedPicShowInCenter - ptZoomedPicPixelDatas->iWidth >= iPictureLayoutWidth / 2
* 所以iStartXofOldPic在这种情况下是可能小于g_tManualPictureLayout.iTopLeftX,所以需要判断
*/
if (iStartXofOldPic < g_tManualPictureLayout.iTopLeftX)
{
iStartXofOldPic = g_tManualPictureLayout.iTopLeftX;
}
/* 向右边划出去了 */
if (iStartXofOldPic > g_tManualPictureLayout.iBotRightX)
{
iStartXofOldPic = g_tManualPictureLayout.iBotRightX + 1;
}
/* ptZoomedPicPixelDatas->iWidth - iStartXofNewPic 是图片除去了左边可能不显示的地方,剩下要显示的部分
* g_tManualPictureLayout.iBotRightX - iStartXofOldPic + 1 是从显示位置开始,到可以显示图片区域的结束,一共多宽
* 这两个选一个小的,作为实际显示的宽度
*/
if ((ptZoomedPicPixelDatas->iWidth - iStartXofNewPic) > (g_tManualPictureLayout.iBotRightX - iStartXofOldPic + 1))
iWidthPictureInPlay = (g_tManualPictureLayout.iBotRightX - iStartXofOldPic + 1);
else
iWidthPictureInPlay = (ptZoomedPicPixelDatas->iWidth - iStartXofNewPic);
/* 上面是关于X坐标的计算,下面开始Y方向的计算,原理一样 */
iStartYofNewPic = g_iYofZoomedPicShowInCenter - iPictureLayoutHeight/2;
if (iStartYofNewPic < 0)
{
iStartYofNewPic = 0;
}
if (iStartYofNewPic > ptZoomedPicPixelDatas->iHeight)
{
iStartYofNewPic = ptZoomedPicPixelDatas->iHeight;
}
iDeltaY = g_iYofZoomedPicShowInCenter - iStartYofNewPic;
iStartYofOldPic = (g_tManualPictureLayout.iTopLeftY + iPictureLayoutHeight / 2) - iDeltaY;
if (iStartYofOldPic < g_tManualPictureLayout.iTopLeftY)
{
iStartYofOldPic = g_tManualPictureLayout.iTopLeftY;
}
if (iStartYofOldPic > g_tManualPictureLayout.iBotRightY)
{
iStartYofOldPic = g_tManualPictureLayout.iBotRightY + 1;
}
if ((ptZoomedPicPixelDatas->iHeight - iStartYofNewPic) > (g_tManualPictureLayout.iBotRightY - iStartYofOldPic + 1))
{
iHeightPictureInPlay = (g_tManualPictureLayout.iBotRightY - iStartYofOldPic + 1);
}
else
{
iHeightPictureInPlay = (ptZoomedPicPixelDatas->iHeight - iStartYofNewPic);
}
/* 把整个图片显示区填充为背景色 */
ClearVideoMemRegion(ptVideoMem, &g_tManualPictureLayout, COLOR_BACKGROUND);
/* 传入前面计算好的参数,显示缩放、移动后的图片
* iStartXofNewPic, iStartYofNewPic :从图片的哪个位置开始取数据
* iStartXofOldPic, iStartYofOldPic :在LCD的哪个位置开始显示图片
* iWidthPictureInPlay, iHeightPictureInPlay :正真显示图片的宽和高
* ptZoomedPicPixelDatas, &ptVideoMem->tPixelDatas:源和目的
*/
PicMergeRegion(iStartXofNewPic, iStartYofNewPic, iStartXofOldPic, iStartYofOldPic, iWidthPictureInPlay, iHeightPictureInPlay, ptZoomedPicPixelDatas, &ptVideoMem->tPixelDatas);
}
PicMergeRegion函数定义如下:
/*********************************************************************** 函数名称: PicMergeRegion* 功能描述: 把新图片的某部分, 合并入老图片的指定区域* 输入参数: iStartXofNewPic, iStartYofNewPic : 从新图片的(iStartXofNewPic, iStartYofNewPic)座标处开始读出数据用于合并* iStartXofOldPic, iStartYofOldPic : 合并到老图片的(iStartXofOldPic, iStartYofOldPic)座标去* iWidth, iHeight : 合并区域的大小* ptNewPic : 新图片* ptOldPic : 老图片* 输出参数: 无* 返 回 值: 0 - 成功, 其他值 - 失败***********************************************************************/
int PicMergeRegion(int iStartXofNewPic, int iStartYofNewPic, int iStartXofOldPic, int iStartYofOldPic, int iWidth, int iHeight, PT_PixelDatas ptNewPic, PT_PixelDatas ptOldPic)
{int i;unsigned char *pucSrc;unsigned char *pucDst;int iLineBytesCpy = iWidth * ptNewPic->iBpp / 8;if ((iStartXofNewPic < 0 || iStartXofNewPic >= ptNewPic->iWidth) || \(iStartYofNewPic < 0 || iStartYofNewPic >= ptNewPic->iHeight) || \(iStartXofOldPic < 0 || iStartXofOldPic >= ptOldPic->iWidth) || \(iStartYofOldPic < 0 || iStartYofOldPic >= ptOldPic->iHeight)){return -1;}pucSrc = ptNewPic->aucPixelDatas + iStartYofNewPic * ptNewPic->iLineBytes + iStartXofNewPic * ptNewPic->iBpp / 8;pucDst = ptOldPic->aucPixelDatas + iStartYofOldPic * ptOldPic->iLineBytes + iStartXofOldPic * ptOldPic->iBpp / 8;for (i = 0; i < iHeight; i++){memcpy(pucDst, pucSrc, iLineBytesCpy);pucSrc += ptNewPic->iLineBytes;pucDst += ptOldPic->iLineBytes;}return 0;
}
下面是放大的完整处理过程:
static int g_iXofZoomedPicShowInCenter;
static int g_iYofZoomedPicShowInCenter;
/* 放大/缩小系数 */
#define ZOOM_RATIO (0.9)
case 2: /* 放大按钮 */
{/* 获得放大后的数据 */iZoomedWidth = (float)g_tZoomedPicPixelDatas.iWidth / ZOOM_RATIO;iZoomedHeight = (float)g_tZoomedPicPixelDatas.iHeight / ZOOM_RATIO;ptZoomedPicPixelDatas = GetZoomedPicPixelDatas(&g_tOriginPicPixelDatas, iZoomedWidth, iZoomedHeight);/* 重新计算中心点 */g_iXofZoomedPicShowInCenter = (float)g_iXofZoomedPicShowInCenter / ZOOM_RATIO;g_iYofZoomedPicShowInCenter = (float)g_iYofZoomedPicShowInCenter / ZOOM_RATIO;/* 显示新数据 */ShowZoomedPictureInLayout(ptZoomedPicPixelDatas, ptDevVideoMem);break;
}
缩小:
/* 放大/缩小系数 */
#define ZOOM_RATIO (0.9)
case 1: /* 缩小按钮 */
{/* 获得缩小后的数据 */iZoomedWidth = (float)g_tZoomedPicPixelDatas.iWidth * ZOOM_RATIO;iZoomedHeight = (float)g_tZoomedPicPixelDatas.iHeight * ZOOM_RATIO;ptZoomedPicPixelDatas = GetZoomedPicPixelDatas(&g_tOriginPicPixelDatas, iZoomedWidth, iZoomedHeight);/* 重新计算中心点 */g_iXofZoomedPicShowInCenter = (float)g_iXofZoomedPicShowInCenter * ZOOM_RATIO;g_iYofZoomedPicShowInCenter = (float)g_iYofZoomedPicShowInCenter * ZOOM_RATIO;/* 显示新数据 */ShowZoomedPictureInLayout(ptZoomedPicPixelDatas, ptDevVideoMem);break;
}
拖拽:
/* 如果触点滑动距离大于规定值, 则挪动图片 */
if (DistanceBetweenTwoPoint(&tInputEvent, &tPreInputEvent) > SLIP_MIN_DISTANCE)
{ /* 重新计算中心点 */g_iXofZoomedPicShowInCenter -= (tInputEvent.iX - tPreInputEvent.iX);g_iYofZoomedPicShowInCenter -= (tInputEvent.iY - tPreInputEvent.iY);/* 显示新数据 */ShowZoomedPictureInLayout(ptZoomedPicPixelDatas, ptDevVideoMem);/* 记录上次滑动点 */tPreInputEvent = tInputEvent;
}
实验效果:
一开始显示是居中显示
拖拽
放大
参考文章:https://blog.csdn.net/qq_22655017/article/details/97627382