数码相框项目之显示一张可放大、缩小、拖拽的图片

之前我做过一个电子相框的项目,涉及到的重难点主要为:在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

发布了42 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_37659294/article/details/104518692
今日推荐