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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384497.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP协议-如何保证传输可靠性

TCP协议传输的特点主要就是面向字节流、传输可靠、面向连接。这篇博客&#xff0c;我们就重点讨论一下TCP协议如何确保传输的可靠性的。 确保传输可靠性的方式 TCP协议保证数据传输可靠性的方式主要有&#xff1a; 校验和序列号确认应答超时重传连接管理流量控制拥塞控制 校…

TCP协议-握手与挥手

认识TCP协议 TCP全称为“传输控制协议”&#xff0c;这是传输层的一个协议&#xff0c;对数据的传输进行一个详细的控制。 特点&#xff1a; 面向字节流安全可靠面向连接 TCP协议段格式 源端口号与目的端口号&#xff1a;这里与UDP的一样&#xff0c;每个数据都要知道从哪个…

ASOC注册过程

一、什么是ASOC 在嵌入式系统里面的声卡驱动为ASOC&#xff08;ALSA System on Chip&#xff09; &#xff0c;它是在ALSA 驱动程序上封装的一层&#xff0c;分为3大部分&#xff0c;Machine&#xff0c;Platform和Codec ,三部分的关系如下图所示&#xff1a;其中Machine是指我…

ASOC调用过程

上一篇文章我们将了嵌入式系统注册声卡的过程&#xff1a;https://blog.csdn.net/qq_37659294/article/details/104748747 这篇文章我们以打开一个声卡的播放节点为例&#xff0c;讲解一下在APP调用open时&#xff0c;最终会如何调用到硬件相关的函数。 在上一篇文章最后我们说…

编写声卡驱动(框架)

在前面两篇文章中&#xff0c;我们分别讲了嵌入式Linux系统声卡注册的过程和调用的过程&#xff1a; https://blog.csdn.net/qq_37659294/article/details/104748747 https://blog.csdn.net/qq_37659294/article/details/104802868 讲了那么多&#xff0c;我们最终的目的无非…

声卡学习笔记

分享几篇关于韦东山声卡驱动的学习笔记&#xff0c;作者写得非常详细。 ALSA驱动框架&#xff1a;https://blog.csdn.net/qingkongyeyue/article/details/52328991 ASoC驱动框架&#xff1a;https://blog.csdn.net/qingkongyeyue/article/details/52349120 ASoC驱动重要结构…

路由器、交换机、集线器的区别

https://blog.csdn.net/weibo1230123/article/details/82779040

$PATH环境变量的作用

echo $PATH 显示当前PATH环境变量&#xff0c;该变量的值由一系列以冒号分隔的目录名组成&#xff0c;如&#xff1a;/usr/local/bin:/bin:/usr/bin。(冒号:是路径分隔符) 在执行一个程序的时候如果没有PATH的话&#xff0c;就需要写出路径名&#xff08;绝对或者相对&#xf…

dmesg

https://blog.csdn.net/zm_21/article/details/31760569

进程上下文与中断上下文的理解

一.什么是内核态和用户态 内核态&#xff1a;在内核空间执行&#xff0c;通常是驱动程序&#xff0c;中断相关程序&#xff0c;内核调度程序&#xff0c;内存管理及其操作程序。 用户态&#xff1a;用户程序运行空间。 二.什么是进程上下文与中断上下文 1.进程上下文&#xf…

GDB调试教程:1小时玩转Linux gdb命令

原文链接&#xff1a;http://c.biancheng.net/gdb/ GDB 入门教程 本教程以下面的代码为例&#xff0c;在 Linux 系统下来讲解 GBD 的调试流程&#xff1a; int main (void) {unsigned long long int n, sum;n 1;sum 0;while (n < 100){sum sum n;n n 1;}return 0; …

shell将命令执行的结果赋值给 变量

https://blog.csdn.net/lemontree1945/article/details/79126819/

Linux下shell脚本指定程序运行时长

https://www.cnblogs.com/yychuyu/p/12626798.html

vim编辑器如何删除一行或者多行内容

http://blog.itpub.net/69955379/viewspace-2681334/

C++经典问题:如果对象A中有对象成员B,对象B没有默认构造函数,那么对象A必须在初始化列表中初始化对象B?

对象成员特点总结&#xff1a; &#xff08;1&#xff09;实例化对象A时&#xff0c;如果对象A有对象成员B,那么先执行对象B的构造函数&#xff0c;再执行A的构造函数。 &#xff08;2&#xff09;如果对象A中有对象成员B,那么销毁对象A时&#xff0c;先执行对象A的析构函数&…

JZ2440用U-Boot给Nand-Flash烧写程序时报错:NAND write: incorrect device type in bootloader ‘bootloader‘ is not

JZ2440开发板使用问题&#xff0c;U-Boot烧写程序到Nand Flash时报错&#xff1a;NAND write: incorrect device type in bootloader bootloader is not a number 这是因为分区名中u-boot&#xff0c;不是bootloader&#xff0c;而cmd_menu.c里用的是bootloader 可以执行&#…

韦东山衔接班——4.4_构建根文件系统之构建根文件系统

文章地址&#xff1a; https://blog.csdn.net/gongweidi/article/details/100086289?biz_id102&utm_term%E9%9F%A6%E4%B8%9C%E5%B1%B1%E8%A1%94%E6%8E%A5%E7%8F%AD&utm_mediumdistribute.pc_search_result.none-task-blog-2~blog~sobaiduweb~default-5-100086289&…

C++中const char *p和char const *p

const char *p;他的意思是p指向的目标空间的内容不可变化 例如定义char cA; p&c;则c的内容不可以变化.如cB;等一些企图改变变量c的值的做法都不行. 然而p仍然是动态的,就是它还可以指向别的空间,被赋予新的地址值,只是被他指向的目标空间的内容不可变化,如上面的c值始终为A…

qt 分割字符串的两种方法

https://blog.csdn.net/a724699769/article/details/62216435

【YOLO系列】YOLOv3代码详解(五):utils.py脚本

前言 以下内容仅为个人在学习人工智能中所记录的笔记&#xff0c;先将目标识别算法yolo系列的整理出来分享给大家&#xff0c;供大家学习参考。 本文仅对YOLOV3代码中关键部分进行了注释&#xff0c;未掌握基础代码的铁汁可以自己百度一下。 若文中内容有误&#xff0c;希望大家…