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

之前我做过一个电子相框的项目,涉及到的重难点主要为:在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;最终会如何调用到硬件相关的函数。 在上一篇文章最后我们说…

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

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

内核的Makefile与Kconfig关系解析

在子目录下的Kconfig里添加make menuconfig的选项&#xff08;如图一&#xff09;&#xff0c;并默认设置为y&#xff0c;make menuconfig的菜单里就会有该项并默认为选上状态&#xff0c;make menuconfig配置完之后在.config文件里就有该选项&#xff0c;并等于y&#xff08;如…

Linux信号之signal函数

1. 信号概述 何为信号&#xff1a;信号就是由用户、系统或进程发送给目标进程的信息&#xff0c;以通知目标进程中某个状态的改变或是异常。 信号产生&#xff1a;总体来说&#xff0c;其产生的条件有两种&#xff0c;分别是&#xff1a;硬件和软件原因&#xff0c;又称为&…

Linux中wait()函数及waitpid()函数

编程过程中&#xff0c;有时需要让一个进程等待另一个进程&#xff0c;最常见的是父进程等待自己的子进程&#xff0c;或者父进程回收自己的子进程资源包括僵尸进程。这里简单介绍一下系统调用函数&#xff1a;wait() 函数原型是 #include <sys/types.h> #include <…

学习笔记 --- DM9000网卡原理与基地址设置

前面有文章分析了网卡也是属于类内存总线的设备&#xff0c;类内存总线的设备有地址总线和数据总线&#xff0c;先来看下DM9000的管脚&#xff1a; 从上面可以看出DM9000的地址总线就一根&#xff0c;它不像CS8900那样地址总线和数据总线都齐全。而这里只有一根地址线(CMD)&…

静态VLAN的配置

在一台交换机上连接3台PC机&#xff0c;然后创建两个VLAN&#xff0c;分别为VLAN 10 和VLAN 20&#xff0c;把第一台PC机分配给VLAN 10&#xff0c;把其他两台分配给VLAN 20.然后测试他们的互通情况。 在这里命令我用的都是简化命令&#xff0c;想卡完整版命令&#xff0c;请到…

静态路由原理

1、路由器的工作原理 路由工作简单原理图 1&#xff09;主机1.1要发生数据包给主机4.1.因为IP地址不在同一网段&#xff0c;所以主机会将数据包发送给本网段的网关路由器。 2&#xff09;路由器A 接收到数据包&#xff0c;先查看数据包IP首部中的目标IP地址。再查找自己的路由表…

静态路由和默认路由

一、静态路由的配置 下边实验对该拓扑图进行配置 实验目标&#xff1a;配置静态路由&#xff0c;实现全网互通 1、配置路由器R1 进入接口f0/0&#xff0c;配置IP&#xff0c;并开启。 进入接口f0/1&#xff0c;配置IP&#xff0c;并开启。 设置静态路由。 查看PC1的路由表 2、配…

IP地址192.168.1.1/24中的/24是什么意思

/24是指子网掩码的位数。 子网掩码的位数总共有32个&#xff0c;写的的/24个就是24个1&#xff0c;其它8位都是0。 /24就可以写成子网掩码是&#xff1a;11111111 11111111 11111111 00000000 例如&#xff1a; /25&#xff0c;就代表有25个1&#xff0c;7个0&#xff0c;…

IP地址中A类、B类、C类地址的区别

区别如下&#xff1a; 1、IP地址表示方法不同&#xff1a; 一个A类IP地址是指&#xff0c; 在IP地址的四段号码中&#xff0c;第一段号码为网络号码&#xff0c;剩下的三段号码为本地计算机的号码。如果用二进制表示IP地址的话&#xff0c;A类IP地址就由1字节的网络地址和3字…

原始套接字简介

一 原始套接字概述 原始套接字&#xff0c;指在传输层下面使用的套接字。流式套接字和数据报套接字这两种套接字工作在传输层&#xff0c;主要为应用层的应用程序提供服务&#xff0c;并且在接收和发送时只能操作数据部分&#xff0c;而不能对IP首部或TCP和UDP首部进行操作&am…

TCP socket心跳包示例程序

TCP socket心跳包示例程序_xqhrs232的专栏-CSDN博客_setsockopt 心跳包 原文地址::TCP socket心跳包示例程序_神奕的专栏-CSDN博客_tcp心跳包 相关文章 1、Linux网络编程--服务端判断客户端断开的经验方法 ----Linux网络编程--服务端判断客户端断开的经验方法_志存高远-CSDN博…

sizeof()计算结构体的大小

原文链接&#xff1a;sizeof()计算结构体的大小_海月汐辰-CSDN博客_结构体的sizeof怎么计算 简要说明&#xff1a;结构体成员按照定义时的顺序依次存储在连续的内存空间&#xff0c;但是结构体的大小并不是简单的把所有成员大小相加&#xff0c;而是遵循一定的规则&#xff0c…

select、poll、epoll使用小结

转载&#xff1a;http://blog.csdn.net/kkxgx/article/details/7717125 Linux上可以使用不同的I/O模型&#xff0c;我们可以通过下图了解常用的I/O模型&#xff1a;同步和异步模型&#xff0c;以及阻塞和非阻塞模型&#xff0c;本文主要分析其中的异步阻塞模型。 一、select使用…

Qt报错:undefined reference to xxxxx

报错信息&#xff1a; 首先&#xff0c;要区分与undefined reference to xxxxx和 "xxxx was not declared in this scope"两种报错信息的差别&#xff0c;前者是因为编译器能找到函数的声明&#xff0c;但是找不到函数的定义&#xff0c;从而报错&#xff1b;而后者是…

对象的浅拷贝和深拷贝

对象的浅拷贝和深拷贝简要介绍代码实现简要介绍 浅拷贝&#xff1a;python拷贝一般都是浅拷贝。拷贝时&#xff0c;对象包含的子对象内容不拷贝。因此&#xff0c;源对象和拷贝对象引用同一个对象 深拷贝&#xff1a;使用copy模块的deepcopy函数&#xff0c;递归拷贝对象中包含…