Visual C++程序开发范例宝典(软件工程师典藏版)
上QQ阅读APP看书,第一时间看更新

3.2 图像预览

在应用程序中经常会用到图像预览。下面的几个实例主要实现了图片的自动预览、批量浏览、浏览大幅BMP图片以及控制图片大小等功能。

实例094 图片自动预览程序

本实例是一个提高效率的程序

实例位置:光盘\mingrisoft\03\094

实例说明

可以通过自动预览的功能来浏览多幅图片,这样就不用手动去选择要浏览的图片了。运行程序,单击“文件”/“打开”菜单项,选择一幅BMP图片,程序将自动预览和这幅图片相同文件夹下的其他图片,效果如图3.7所示。

技术要点

图3.7 图片自动预览程序

本实例使用定时器设置在一定时间后自动显示下一幅图片,在MFC中可以使用SetTimer函数来定义和开始一个定时器,通过处理消息WM_TIMER来达到定时控制的功能,最后使用KillTimer函数关闭定时器。

SetTimer函数原型如下:

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );

参数说明:

● nIDEvent:定时器的标识ID。

● nElapse:延迟时间(多长时间重复一次),单位是毫秒。

● 第3个参数:重复调用的函数的地址指针,为NULL时将发送WM_TIMER消息。

KillTimer函数原型如下:

BOOL KillTimer( int nIDEvent );

参数说明:

● nIDEvent:定时器的标识ID。

实现过程

(1)新建一个基于单文档的应用程序。

(2)主要程序代码如下:

        void CBmpView::OnTimer(UINT nIDEvent)
        {
            CString str=Search(GetDocument()->GetPathName());       //获得文件路径名
            if(str=="")
              return;
            AfxGetApp()->OpenDocumentFile(str);                    //打开文件
            CView::OnTimer(nIDEvent);
        }
        //自定义函数,查找并打开图片
        CString CBmpView::Search(CString curstr)
        {
            long handle;
            if(curstr.IsEmpty())
              return "";
            if(_getcwd( buffer, 1000)==NULL)
            {
              AfxMessageBox("没有当前路径,请打开一个图像文件!");
              return "";
            }
            CString m_sPartname;
            int len = curstr.GetLength();
            int i;
            for(i = len-1;curstr[i] != '\\';i--)
              m_sPartname.Insert(0,curstr[i]);                     //获得文件名
            i++;
            while(i--<0)
              buffer[i]=curstr[i];
              if (_chdir(buffer) != 0)
              return "";
              bool b_notfinde=false;
              struct _finddata_t filestruct;
              // 开始查找工作, 找到当前目录下的第一个实体(文件或子目录),
              // “*”表示查找任何的文件或子目录,filestruct为查找结果
              handle = _findfirst("*", &filestruct);
              do{
              if((handle==-1))               // 当handle为-1时, 表示当前目录为空,结束查找并返回
                      break;
              // 检查找到的第一个实体是否是一个目录
              if( ::GetFileAttributes(filestruct.name) & FILE_ATTRIBUTE_DIRECTORY )
              {
                      continue ;
              }
              CString Filename=filestruct.name;
              {
                      CString tailstr;
                      //获取文件扩展名
                      tailstr = Filename.Mid(Filename.GetLength()-3);
                      tailstr.MakeUpper();
                      Filename.MakeUpper();
                      m_sPartname.MakeUpper();
                      if(tailstr=="BMP")
                      {
                          if(b_notfinde==false)
                          {
                            if(m_sPartname==Filename)
                              b_notfinde=true;
                          }
                          else
                          {
                            _findclose(handle);
                            return Filename;
                          }
                      }
              }
              } while(_findnext(handle, &filestruct)==0);
              _findclose(handle);
              this->KillTimer(1);            //关闭定时器
              AfxMessageBox("已经到达最后一个图像文件!");
              return "";
            }
            char szFilter[]="BMP Files(*.BMP)|*.BMP";

举一反三

根据本实例,读者可以:

开发电子相册程序;

制作屏保程序。

实例095 图片批量浏览

本实例是一个提高效率的程序

实例位置:光盘\mingrisoft\03\095

实例说明

很多时候,需要通过“缩略图”来浏览某一个文件夹中的图片,本实例即实现了与缩略图相同的功能。运行程序,单击“打开”按钮,选择一幅BMP图片,程序将自动打开和这幅图片相同文件夹下的其他3幅图片,单击“上一条”、“下一条”、“上一组”和“下一组”按钮可以浏览该文件夹下的其他图片,效果如图3.8所示。

技术要点

本实例使用LoadImage函数装载位图文件,该函数用于装载图标、光标或位图。函数原型如下:

HANDLE LoadImage(NINSTANCE hinst,LPCTSTR lpszName,UINT uType,int cxDesired,int CyDesired,UINT fuLoad);

图3.8 图片批量浏览

参数说明:

● hinst:处理包含被装载图像模块的特例。

● lpszName:处理图像装载。如果参数hinst为非空,而且参数fuLoad不包括LR_LOADFROMFILE的值时,那么参数lpszName是一个指向保留在hinst模块中装载的图像资源名称,并以NULL为结束符的字符串。

● uType:指定被装载图像类型。此参数值如表3.1所示。

表3.1 uType参数可选值表

● cxDesired:指定图标或光标的宽度,以像素为单位。如果此参数为零并且参数fuLoad值为LR_DEFAULTSIZE,那么函数使用SM_CXICON或SM_CXCURSOR系统公制值设定宽度;如果此参数为零并且值LR_DEFAULTSIZE没有被使用,那么函数使用目前的资源宽度。

● cyDesired:指定图标或光标的高度,以像素为单位。如果此参数为零并且参数fuLoad值为LR_DEFAULTSIZE,那么函数使用SM_CXICON或SM_CXCURSOR系统公制值设定高度;如果此参数为零并且值LR_DEFAULTSIZE没有被使用,那么函数使用目前的资源高度。

● fuLoad:根据表3.2所示的复合值指定函数值。

表3.2 fuLoad参数可选值表

实现过程

(1)新建一个基于对话框的应用程序。

(2)创建一个图形控件类CPicture,其基类为CStatic。

(3)主要程序代码如下:

        CString CBrowsebmpsDlg::Search(CString curstr,bool judge)
        {
            long handle;
            if(curstr.IsEmpty())                //判断路径是否为空
              return "";
              if(_getcwd( buffer, 1000)==NULL)
              {
                    AfxMessageBox("没有当前路径,请打开一个图像文件!");
                    return"";
              }
              CString m_sbefore="";
              CString m_sPartname;
              int len = curstr.GetLength();
              int i;
              for(i = len-1;curstr[i] != '\\';i--)
                    m_sPartname.Insert(0,curstr[i]);    //获得文件名
              i++;
              while(i--<0)
                    buffer[i]=curstr[i];
              if (_chdir(buffer) != 0)
                    return"";
              bool b_notfinde=false;
              struct _finddata_t filestruct;
              // 开始查找工作,找到当前目录下的第一个实体(文件或子目录)
              // “*”表示查找任何的文件或子目录,filestruct为查找结果
              handle = _findfirst("*", &filestruct);
              do{
                    if((handle==-1))// 当handle为-1时,表示当前目录为空,结束查找并返回
                      break;
                    // 检查找到的第一个实体是否是一个目录
                    if(::GetFileAttributes(filestruct.name)&FILE_ATTRIBUTE_DIRECTORY)
                    {
                      continue ;
                    }
                    CString Filename=filestruct.name;
                    {
                      CString tailstr;
                      tailstr = Filename.Mid(Filename.GetLength()-3);
                      tailstr.MakeUpper();
                      Filename.MakeUpper();
                      m_sPartname.MakeUpper();
                      if(tailstr=="BMP")
                      {
                          if(judge)
                          {
                            if(b_notfinde==false)
                            {
                              if(m_sPartname==Filename)
                                      b_notfinde=true;
                            }
                            else
                            {
                              _findclose(handle);
                              return Filename;
                            }
                          }
                          else
                          {
                            if(m_sPartname==Filename)
                            {
                              _findclose(handle);
                              if(m_sbefore=="")
                              {
                                      AfxMessageBox("已经到达第一个图像文件!");
                              }
                              return m_sbefore;
                            }
                            b_notfinde=true;
                            m_sbefore = Filename;
                          }
                      }
                    }
              } while(_findnext(handle, &filestruct)==0);
              _findclose(handle);
              if(judge)
              {
                    AfxMessageBox("已经到达最后一个图像文件!");
              }
              else
              {
                    AfxMessageBox("已经到达第一个图像文件!");
            }
            return "";
        }
        char szFilter[] = "BMP Files(*.BMP)|*.BMP";
        void CBrowsebmpsDlg::Loadpicture(CString str)
        {
              for(int i=0;i<4;i++)
              {
                    if(i != 0)
                    {
                      str = strText+Search(str,true);
                    }
                    strFile[i]=str;
                    HBITMAP m_hBitmap;
                    m_hBitmap=(HBITMAP)::LoadImage(AfxGetInstanceHandle(),str,IMAGE_BITMAP,0,0,
                    LR_LOADFROMFILE|LR_DEFAULTCOLOR|LR_DEFAULTSIZE); //加载图片资源
                    if (m_hBitmap != NULL)
                    {
                      picture[i].SetImage(m_hBitmap);                          //显示图片
                      picture[i].ShowWindow(SW_SHOW);                          //显示控件
                    }
              }
        }

举一反三

根据本实例,读者可以:

开发图片浏览器程序;

浏览不同格式的图片。

实例096 浏览大幅BMP图片

本实例是一个提高效率的程序

实例位置:光盘\mingrisoft\03\096

实例说明

在开发地理定位信息系统时,程序中需要处理大幅的图片,但是程序窗口通常不能够显示整张图片,因此在浏览图片时,需要使用滚动条控件。本实例实现了利用滚动条浏览大幅图片的功能,效果如图3.9所示。

图3.9 浏览大幅BMP图片

技术要点

在使用滚动条控件CScollBar时,需要处理以下几种情况:

(1)用户单击滚动条的左右或上下按钮时,设置滚动块的位置,并滚动窗口。

(2)用户拖动滚动块时,设置滚动块的位置,并滚动窗口。

(3)用户单击滚动块的左右或上下空白滚动区域时,设置滚动块的位置,并滚动窗口。

图3.10描述了这3种情况。

当用户触发了滚动条的消息时,拥有滚动条的对话框会收到WM_HSCROLL(水平滚动条)或WM_VSCROLL(垂直滚动条)消息。以水平滚动条为例,对话框将调用OnHScroll消息处理函数。下面分析OnHScroll消息处理函数:

void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );

图3.10 滚动条描述

参数说明:

● nSBCode:标识用户触发滚动条的消息代码。可选值如下。

■ SB_LEFT:表示滚动到左边缘。

■ SB_ENDSCROLL:表示滚动结束。

■ SB_LINELEFT:表示单击滚动条的左方按钮。

■ SB_LINERIGHT:表示单击滚动条的右方按钮。

■ SB_PAGELEFT :表示在滚动块的左方滚动区域按下鼠标按钮。

■ SB_PAGERIGHT:表示滚动到右边缘。

■ SB_THUMBPOSITION:表示结束拖动滚动块。

■ SB_THUMBTRACK:表示正在拖动滚动块。

● nPos: 标识滚动块的位置,只有在nSBCode为SB_THUMBTRACK或SB_THUMBPOSITION时才可用。

● pScrollBar:标识滚动条控件指针。

默认情况下,用户在触发滚动条消息时,CScrollBar控件是不会自动调整滚动块的位置的,用户需要在滚动条的消息处理函数中根据nSBCode的情况设置滚动块的位置,并执行额外的动作,例如本实例需要根据滚动块的位置适当地滚动窗口,代码如下:

          void CBmpDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
          {
              int pos,min,max,thumbwidth;
              SCROLLINFO vinfo;
              GetScrollInfo(SB_HORZ,&vinfo);
              pos = vinfo.nPos;
              min = vinfo.nMin;
              max = vinfo.nMax;
              thumbwidth = vinfo.nPage;
              switch (nSBCode)
              {
              break;
              case SB_THUMBTRACK:               //拖动滚动块
              ScrollWindow(-(nPos-pos),0);     //滚动窗口
              SetScrollPos(SB_HORZ,nPos);      //设置滚动块的位置
              break;
              case SB_LINELEFT:                 //单击左箭头
              SetScrollPos(SB_HORZ,pos-1);
              if (pos !=0)
                      ScrollWindow(1,0);
              break;
              //代码省略
              }
              CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
          }

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框中添加图片控件、按钮控件、编辑框控件。

(3)新建一个对话框类CBmpDlg,设置对话框的风格,如图3.11所示。

(4)处理CBmpDlg对话框的WM_HSCROLL和WM_VSCROLL消息,设置滚动条的位置,并适当滚动窗口,代码如下:

        //WM_HSCROLL消息处理函数
        void CBmpDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
        {
            int pos,min,max,thumbwidth;
            SCROLLINFO vinfo;
            GetScrollInfo(SB_HORZ,&vinfo);
            pos = vinfo.nPos;
            min = vinfo.nMin;
            max = vinfo.nMax;
            thumbwidth = vinfo.nPage;
            switch (nSBCode)
            {
            break;
            case SB_THUMBTRACK:             //拖动滚动块
              ScrollWindow(-(nPos-pos),0);
              SetScrollPos(SB_HORZ,nPos);
            break;
            case SB_LINELEFT:               //单击左箭头
              SetScrollPos(SB_HORZ,pos-1);
              if (pos !=0)
                    ScrollWindow(1,0);
            break;
            case SB_LINERIGHT:              //单击右箭头
              SetScrollPos(SB_HORZ,pos+1);
              if (pos+thumbwidth <max)
                    ScrollWindow(-1,0);
            break;
            case SB_PAGELEFT:               //在滚动块的左方空白滚动区域单击,增量为6
              SetScrollPos(SB_HORZ,pos-6);
              if (pos+thumbwidth >0)
                    ScrollWindow(6,0);
            break;
            case SB_PAGERIGHT:              //在滚动块的右方空白滚动区域单击,增量为6
              SetScrollPos(SB_HORZ,pos+6);
              if (pos+thumbwidth <max)
                    ScrollWindow(-6,0);
            break;
            }
            CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
        }
        //WM_VSCROLL消息处理函数
        void CBmpDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
        {
            int pos,min,max,thumbwidth;
            SCROLLINFO vinfo;
            GetScrollInfo(SB_VERT,&vinfo);
            pos = vinfo.nPos;
            min = vinfo.nMin;
            max = vinfo.nMax;
            thumbwidth = vinfo.nPage;
            switch (nSBCode)
            {
            case SB_THUMBTRACK:             //拖动滚动块
              pos = GetScrollPos(SB_VERT);
              ScrollWindow(0,-(nPos-pos));
              SetScrollPos(SB_VERT,nPos);
            break;
            case SB_LINELEFT:               //单击左箭头
              pos = GetScrollPos(SB_VERT);
              SetScrollPos(SB_VERT,pos-1);
              if (pos !=0)
                    ScrollWindow(0,1);
            break;
            case SB_LINERIGHT:              //单击右箭头
              pos = GetScrollPos(SB_VERT);
              SetScrollPos(SB_VERT,pos+1);
              if (pos+thumbwidth <max)
                    ScrollWindow(0,-1);
            break;
              case SB_PAGELEFT:               //在滚动块的上方空白滚动区域单击,增量为6
                    SetScrollPos(SB_VERT,pos-6);
                    if(pos+thumbwidth>0)
                      ScrollWindow(0,6);
              break;
              case SB_PAGERIGHT:              //在滚动块的下方空白滚动区域单击,增量为6
                    SetScrollPos(SB_VERT,pos+6);
                    if(pos+thumbwidth<max)
                      ScrollWindow(0,-6);
              break;
              }
              CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
          }

图3.11 对话框属性设置

(5)处理对话框CBmpDlg的WM_MOUSEWHEEL消息,在用户滚动鼠标滚轮时,移动滚动条,代码如下:

          BOOL CBmpDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
          {
              SCROLLINFO vinfo;
              GetScrollInfo(SB_VERT,&vinfo);      //获取滚动条信息
              int min,max,thumbwidth;
              min = vinfo.nMin;
              max = vinfo.nMax;
              thumbwidth = vinfo.nPage;
              int pos=GetScrollPos(SB_VERT);       //获得滚动块位置
              if (zDelta>0)
              {
              if (pos==0)
                      return TRUE;
              SetScrollPos(SB_VERT,pos-6);        //设置滚动块位置
              ScrollWindow(0,6);
              }
              else
              {
              if ((pos+thumbwidth>=max))
                      return TRUE;
              SetScrollPos(SB_VERT,pos+6);        //设置滚动块位置
              ScrollWindow(0,-6);
              }
              return TRUE;
          }

(6)在主对话框中定义一个CBmpDlg对象m_BrownDlg,并在对话框初始化时(OnInitDialog方法中)创建子对话框,代码如下:

          //创建子对话框
          m_BrownDlg.Create(IDD_BMPDLG_DIALOG,this);
          CRect rect,framerect;
          m_BrownDlg.GetClientRect(rect);
          m_Frame.GetClientRect(framerect);        //获得图片控件的客户区域
          m_Frame.MapWindowPoints(this,framerect);
          m_BrownDlg.MoveWindow(framerect);        //设置对话框在图片控件位置
          m_BrownDlg.ShowWindow(SW_SHOW);          //显示对话框

(7)处理主对话框的WM_MOUSEWHEEL消息,调用子窗口CBmpDlg的WM_MOUSEWHEEL消息处理函数移动滚动条,代码如下:

          BOOL CBrownBMPDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
          {
              m_BrownDlg.OnMouseWheel(nFlags,zDelta,pt);
              return CDialog::OnMouseWheel(nFlags, zDelta, pt);
          }

举一反三

根据本实例,读者可以:

设计画图程序。

实例097 放大和缩小图片

本实例是一个提高基础技能的程序

实例位置:光盘\mingrisoft\03\097

实例说明

在浏览图片的时候,可以通过将图片放大或缩小来方便用户浏览。本实例实现的是在窗体上通过按钮来控制图片的大小。运行程序,单击窗体左侧的按钮可以根据比例来显示图片,效果如图3.12所示。

图3.12 放大和缩小图片

技术要点

本实例使用StretchBlt函数根据设置的比例大小重画图片。

StretchBlt函数:绘制位图文件。函数原型如下:

BOOL StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop );

参数说明:

● x:左上角横坐标。

● y:左上角纵坐标。

● nWidth:显示宽度。

● nHeight:显示高度。

● pSrcDC:设备环境指针。

● xSrc:复制位图左上角横坐标。

● ySrc:复制位图左上角纵坐标。

● nSrcWidth:源位图宽度。

● nSrcHeight:源位图高度。

● dwRop:光栅操作类型。

实现过程

(1)新建一个基于对话框的应用程序。

(2)向窗口中添加1个图片控件和5个按钮控件。

(3)向程序中导入一幅位图。

(4)主要程序代码如下:

        void CPictureDlg::DrawPicture(int num)
        {
            //获得窗口大小
            CRect r;
            m_picture.GetClientRect(&r);
            CDC*pDC=m_picture.GetDC();       //获得设备上下文
            //填充背景
            pDC->FillRect(&r,NULL);
            //将位图选进设备场景中
            CBitmap cbmp;
            cbmp.LoadBitmap(IDB_BITMAP1);    //加载图片资源
            CDC memdc;
            memdc.CreateCompatibleDC(pDC);
            memdc.SelectObject(&cbmp);
            //获得位图参数
            long width,height;
            cbmp.GetBitmap(&bmp);
            width = bmp.bmWidth;
            height = bmp.bmHeight;
            //开始缩放
            switch(num)
            {
            case 0://50%显示
              pDC->StretchBlt(r.left,r.top,(int)(width*0.5),(int)(height*0.5),&memdc,0,0,
                    bmp.bmWidth,bmp.bmHeight,SRCCOPY);
              break;
            case 1://75%显示
              pDC->StretchBlt(r.left,r.top,(int)(width*0.75),(int)(height*0.75),&memdc,0,0,
                    bmp.bmWidth,bmp.bmHeight,SRCCOPY);
              break;
            case 2://100%显示
              pDC->BitBlt(r.left,r.top,r.Width(),r.Height(),&memdc,0,0,SRCCOPY);
              break;
              case 3://150%显示
              pDC->StretchBlt(r.left,r.top,(int)(width*1.5),(int)(height*1.5),&memdc,0,0,
                      bmp.bmWidth,bmp.bmHeight,SRCCOPY);
              break;
              case 4://充满窗口
              pDC->StretchBlt(r.left,r.top,r.Width(),r.Height(),&memdc,0,0,
                      bmp.bmWidth,bmp.bmHeight,SRCCOPY);
              break;
              }
          }

举一反三

根据本实例,读者可以:

在窗体中动态控制控件的大小。

实例098 图像任意角度旋转

这是一个可以提高分析能力的实例

实例位置:光盘\mingrisoft\03\098

实例说明

图像旋转是图像的几何变换,是在不改变图像内容的前提下实现对像素进行空间几何变换的一种处理方式。在GDI+中Bitmap提供了图像旋转的功能,但是它只能对固定的一些角度进行旋转,例如90°、180°、270°等。在本节实例中,利用数学公式实现对图像的任意角度旋转,效果如图3.13所示。

技术要点

图像旋转核心内容就是旋转公式。有了旋转公式,将位图数据带入,就可以实现图像旋转了。下面笔者来介绍图像旋转公式的推导过程。图3.14描述了平面内一坐标点顺时针旋转θ角的情况。

图3.13 图像任意角度旋转

图3.14 坐标点旋转平面图

图3.14中旋转角度为θ角。根据三角函数有:

X0 = Rcosα

Y0 = Rsinα

旋转后

X = Rcos(α-θ)

Y = Rsin(α-θ)

根据正弦加法定理和余弦加法定理可知:

cos(α-θ) = cosαcosθ + sinαsinθ

sin(α-θ) = sinαcosθ - cosαsinθ

X = Rcosαcosθ + Rsinαsinθ

= X0cosθ + Y0sinθ

Y = Rsinαcosθ- Rcosαsinθ

= Y0cosθ- X0sinθ

有了上面的公式,还需要进行逆运算。因为,在进行位图旋转时,在确定旋转后的位图大小之后,可以确定旋转后的位图矩形区域,需要访问该区域的每一个坐标,以获得该坐标对应于源位图的坐标,最后获得该坐标对应于源位图坐标点处的像素数据。因此需要进行逆运算。

因为

X = X0cosθ + Y0sinθ

因为

Y = Y0cosθ - X0sinθ

则有

将其展开有

Xcosθ - X0cos2θ = Ysinθ + X0sin2θ

导出

X0cosθ- Y0sinθ = X0cos2θ + X0sin2θ

= X0(cos2θ+sin2θ)

= X0

X0 = Xcosθ- Ysinθ

按照上面的方法,可以导出

Y0 = Xsinθ + Ycosθ

这样对应的逆运算矩阵为

有了转换公式,我们的图像转换任务并没有完成,还需要进行坐标转换。以一幅图像为例,在计算机中的原点为图像的左上角,如图3.15所示。

而在数学中,坐标原点为图像的中心点,如图3.16所示。

图3.15 计算机中表示的图像原点坐标

图3.16 数学坐标系中的图像原点

假设位图的高度为H,宽度为W,则图像坐标(X0, Y0)与数学坐标(X,Y)的关系是

X = X0 -0.5W

Y = -Y0 + 0.5H

对应的矩阵表示为

逆运算为

X0 = X+ 0.5W

Y0 = -Y + 0.5H

逆运算矩阵为

在经过数学公式旋转后,还需要数学坐标转换为新位图的图像坐标。转换的公式为

X = X0 + 0.5W’

Y = -Y0 + 0.5H’

其中,W’和H’表示旋转后位图的宽度和高度。

矩阵表示为

逆运算为

X0 = X-0.5W’

Y0 = -Y + 0.5H’

逆运算矩阵为

有了上述的旋转公式,就可以进行图像旋转了。

实现过程

(1)新建一个基于对话框的工程。

(2)向对话框中添加按钮、编辑框、单选按钮、滑块、图片控件。

(3)处理“…”按钮的单击事件,首先加载位图图像,读取位图信息,然后再将位图数据转换为4个字节表示的位图数据,便于对图像进行处理,最后根据位图的大小设置窗口的滚动范围。代码如下:

        void CRotateImage::OnBtLoad()
        {
            CFileDialog flDlg(TRUE,"","",OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
              "位图文件|*.bmp||",this);                        //定义文件打开窗口
            if (flDlg.DoModal()==IDOK)
            {
              CString csFileName=flDlg.GetPathName();           //获取选择的图像名称
              m_SrcFile = flDlg.GetPathName();
              m_BmpName.SetWindowText(csFileName);
              if(m_hBmp!=NULL)                                 //判断之前是否已加载了图像
              {
                    DeleteObject(m_hBmp);                      //删除之前加载的图像
                    m_hBmp = NULL;
              }
              m_hBmp = (HBITMAP)LoadImage(NULL,csFileName,IMAGE_BITMAP,0,0,
                    LR_LOADFROMFILE);                          //加载图像信息
              if(m_hBmp)                                       //图像是否加载成功
              {
                    m_Image.SetBitmap(m_hBmp);                 //显示图像
                    m_bLoaded=TRUE;                            //设置加载标记为已加载
              }
              CFile file;                                       //定义文件对象
              file.Open(csFileName,CFile::modeRead);           //开发文件
              //读取位图文件头
              file.Read(&m_bmFileHeader,sizeof(BITMAPFILEHEADER));
              //读取位图信息头
              file.Read(&m_bmInfoHeader,sizeof(BITMAPINFOHEADER));
              int szPalette = 0;
              if(m_bmInfoHeader.biBitCount!=24)                //判断是否为真彩色位图
              {
                    file.Close();
                    MessageBox("请选择真彩色位图!","提示");
                    return;
              }
              int nBmpData=m_bmInfoHeader.biSizeImage;          //获取位图数据的大小
              if(m_pBmpData!=NULL)                             //判断是否已有位图数据
              {
                    delete[]m_pBmpData;                        //删除位图数据
                    m_pBmpData = NULL;
              }
              m_pBmpData=new BYTE[nBmpData];                    //为位图数据分配空间
              file.ReadHuge(m_pBmpData,nBmpData);              //从文件中读取位图数据
              file.Close();                                    //关闭文件
              //将位图数据转换为每个像素4字节形式,计算转换后的图像大小
              int sizeofbuffer = m_bmInfoHeader.biWidth * m_bmInfoHeader.biHeight * 4;
              //计算位图每行填充的字节数
              int externWidth;
              externWidth = m_bmInfoHeader.biWidth * 3;
              if(externWidth % 4 != 0)
                    externWidth = 4- externWidth % 4;
              else
                    externWidth = 0;
              int k = 0;
              //为转换后的位图数据分配空间
              BYTE* m_pImageTempBuffer = new BYTE[sizeofbuffer];
              //按行遍历位图
              for (int n = m_bmInfoHeader.biHeight -1; n >= 0; n--)
              {
                    //按列遍历位图
                    for (UINT m = 0; m < m_bmInfoHeader.biWidth * 3; m += 3)
                    {
                        //将源位图数据读取到目标位图中
                        m_pImageTempBuffer[k] = m_pBmpData[n*(m_bmInfoHeader.biWidth*3+
                            externWidth)+m];
                        m_pImageTempBuffer[k+1] = m_pBmpData[n*(m_bmInfoHeader.biWidth*3+
                            externWidth)+m+1];
                        m_pImageTempBuffer[k+2] = m_pBmpData[n*(m_bmInfoHeader.biWidth*3+
                              externWidth)+m+2];
                            m_pImageTempBuffer[k+3]=255;                 //第4位填充为255
                            k+=4;                                        //填充下一个像素
                        }
                  }
                    delete[]m_pBmpData;                                  //释放位图数据
                    m_pBmpData=new BYTE[sizeofbuffer];                    //重新分配空间
                    memcpy(m_pBmpData,m_pImageTempBuffer,sizeofbuffer);  //复制图像数据
                    delete[]m_pImageTempBuffer;                          //释放临时的图像数据
                    CRect bmpRC,wndRC;
                    m_ImagePanel.GetClientRect(wndRC);                   //获取面板的客户区域
                    m_Image.GetClientRect(bmpRC);                        //获取图像控件的客户区域
                    m_ImagePanel.OnHScroll(SB_LEFT,1,NULL);
                    m_ImagePanel.OnVScroll(SB_LEFT,1,NULL);
                  //设置滚动范围
                    m_ImagePanel.SetScrollRange(SB_VERT,0,bmpRC.Height()-wndRC.Height());
                    m_ImagePanel.SetScrollRange(SB_HORZ,0,bmpRC.Width()-wndRC.Width());
              }
            }

(4)向对话框中添加RotateBmp方法,按指定的角度旋转图像。其中,参数pBmpData表示源位图的实际数据,pTmpData表示缩放后的位图数据,nWidth,nHeight表示源位图的宽度和高度,nzmWidth、nzmHeight表示缩放后的位图宽度和高度,dXRate、dYRate表示水平和垂直方向的缩放比例。代码如下:

          void CRotateImage::RotateBmp(BYTE *pBmpData, BYTE *&pDesData, int nWidth, int nHeight,
                                      int nDesWidth, int nDesHeight, double dAngle)
          {
              double dSin=sin(dAngle);                           //计算正弦值
              double dCos=cos(dAngle);                           //计算余弦值
              pDesData=new BYTE[nDesWidth*nDesHeight*4];         //为目标位图数据分配内存空间
              memset(pDesData,255,nDesWidth*nDesHeight*4);      //初始化内存空间为0
              //计算与坐标x、y无关的变量
              double dX = -0.5*nDesWidth*dCos -0.5*nDesHeight*dSin + 0.5*nWidth;
              double dY = 0.5*nDesWidth*dSin -0.5*nDesHeight*dCos + 0.5*nHeight;
              BYTE*pSrc=NULL;                                   //定义指向源位图数据的指针
              BYTE*pDes=NULL;                                   //定义指向目的位图数据的指针
              int x = 0;
              int   y=0;
              for(int h=0;h<nDesHeight;h++)                      //按行遍历位图
              {
              for(int w=0;w<nDesWidth;w++)                       //按列遍历位图
              {
                    //加0.5是为了向上取整
                    x=(int)(w*dCos+h*dSin+dX+0.5);              //获取旋转前的横坐标
                    y=(int)(-w*dSin+h*dCos+dY+0.5);             //获取旋转前的纵坐标
                    if(x==nWidth)
                    {
                      x--;
                    }
                    if(y==nHeight)
                    {
                      y--;
                    }
                    pSrc=pBmpData+y*nWidth*4+x*4;               //根据坐标获取源位图指定点的数据
                    pDes=pDesData+h*nDesWidth*4+w*4;            //根据坐标获取目的位图指定点的数据
                    if(x>=0&&x<nWidth&&y>=0&&y<nHeight)
                    {
                      memcpy(pDes,pSrc,4);                      //复制源位图数据到目的位图数据
                    }
              }
              }
          }

(5)向对话框中添加RotationImage方法,获取源图像和目的图像的信息,并调用RotateBmp方法旋转图像数据。其实现代码如下:

            void CRotateImage::RotationImage(BITMAPINFOHEADER*pBmInfo,int nDegree)
            {
              UINT srcWidth=pBmInfo->biWidth;                     //获取源图像宽度
              UINT srcHeight=pBmInfo->biHeight;                   //获取源图像高度
              double pi=3.1415926535;                             //定义π的值
              double dRadian=nDegree*(pi/180.0);                  //将角度转换为弧度
              //计算目标图像宽度和高度
              UINT desWidth = (abs)(srcHeight*sin(dRadian)) + (abs)(srcWidth*cos(dRadian))+1;
              UINT desHeight = (abs)(srcHeight*cos(dRadian)) + (abs)(srcWidth*sin(dRadian))+1;
            UINT desLineBytes=desWidth*pBmInfo->biBitCount/8;      //计算每行字节数
            int mod=desLineBytes % 4;                                //计算每行填充字节数
            if (mod != 0)
            desLineBytes += 4- mod;
            BYTE*psrcData=m_pBmpData;                             //获取源图像数据
            BYTE*psrcPixData  =NULL;
            BYTE* pdesPixData = NULL;
            //为目标图像数据分配空间
            m_hGlobal = GlobalAlloc(GMEM_MOVEABLE,desHeight*desLineBytes);
            m_RotData=(BYTE*)GlobalLock(m_hGlobal);               //锁定堆空间,获取指向堆空间的指针
            memset(m_RotData,0,desHeight*desLineBytes);           //初始化目标图像数据
            desBmInfo.biBitCount=pBmInfo->biBitCount;             //设置目标位图的颜色位数
            desBmInfo.biClrImportant = pBmInfo->biClrImportant;
            desBmInfo.biClrUsed = pBmInfo->biClrUsed;
            desBmInfo.biCompression = pBmInfo->biCompression;
            desBmInfo.biPlanes=pBmInfo->biPlanes;                 //设置调色板数量
            desBmInfo.biSize=sizeof(BITMAPINFOHEADER);            //设置位图信息头结构大小
            desBmInfo.biXPelsPerMeter = pBmInfo->biXPelsPerMeter;
            desBmInfo.biYPelsPerMeter = pBmInfo->biYPelsPerMeter;
            BYTE* pTemp = NULL;
            //调用RotateBmp方法旋转图像
            RotateBmp(psrcData,pTemp,srcWidth,srcHeight,desWidth,desHeight,dRadian);
            desBmInfo.biHeight=desHeight;                         //设置目标图像宽度
            desBmInfo.biWidth=desWidth;                           //设置目标图像高度
            desLineBytes=desWidth*pBmInfo->biBitCount/8;          //计算每行字节数
            mod=desLineBytes % 4;                                   //计算每行数据需要填充几个字节
            if (mod != 0)
              desLineBytes += 4- mod;
            desBmInfo.biSizeImage=desHeight*desLineBytes;         //设置图像数据大小
            m_bmInfoHeader = desBmInfo;
            if(m_pBmpData!=NULL)                                  //判断位图数据是否为空
            {
              delete[]m_pBmpData;                                 //释放位图数据
              m_pBmpData = NULL;
            }
            m_pBmpData=new BYTE[desHeight*desWidth*4];             //重新分配空间
            memset(m_pBmpData,255,desHeight*desWidth*4);          //初始化内存空间
            memcpy(m_pBmpData,pTemp,desHeight*desWidth*4);        //复制位图数据
            delete[]pTemp;                                        //删除临时的位图数据
            pTemp = NULL;
        }

(6)处理“旋转”按钮的单击事件,旋转已加载的位图。代码首先确定用户设置的旋转方式和旋转角度,然后调用RotationImage方法旋转图像数据,接着将旋转后的图像数据转换为3个字节表示像素的位图数据格式,最后显示旋转后的位图图像。现代码如下:

        void CRotateImage::OnBtRotate()
        {
            if (m_bLoaded)
            {
              //确定旋转方式
              CButton*pButton=  (CButton*)GetDlgItem(IDC_FIXDEGREE);
              int nState = 0;
              int nDegree = 0;
              if (pButton != NULL)
              {
                    nState=pButton->GetCheck();              //判断按钮是否处于选中状态
              }
              if(nState)                                     //预定角度
              {
                    //利用循环遍历单选按钮,确定用户选择的旋转角度
                    for (int nID = IDC_ROTATE45; nID <= IDC_ROTATE270; nID++)
                    {
                        pButton = (CButton*)GetDlgItem(nID);
                        if (pButton != NULL)
                        {
                            nState = pButton->GetCheck();
                            if (nState)
                            {
                                CString csText;
                                pButton->GetWindowText(csText);
                                int nPos = csText.Find("°");
                                nDegree = atoi(csText.Left(nPos));//获取旋转角度
                                break;
                            }
                        }
                    }
              }
                  else                                               //固定角度
                  {
                        UpdateData(FALSE);
                        nDegree=m_nDegree;                           //读取旋转角度
                  }
                    RotationImage(&m_bmInfoHeader,nDegree);          //调用RotationImage方法旋转图像
                    BYTE byByteAlign;                                 //位图行字节对齐
                    UINT outHeight=m_bmInfoHeader.biHeight;           //获取旋转后的位图高度
                    UINT outWidth=m_bmInfoHeader.biWidth;             //获取旋转后的位图宽度
                  //为位图数据分配空间
                    BYTE*pBmpData  =new BYTE[m_bmInfoHeader.biSizeImage];
                    memset(pBmpData,0,m_bmInfoHeader.biSizeImage);   //初始化内存空间
                  //获取旋转后的图像数据(4字节表示),指向最后一行数据
                    BYTE*pListData=m_pBmpData+((DWORD)outHeight-1)*outWidth*4;
                  //计算每行位图数据需要填充的字节数
                  if (outWidth %4 != 0)
                        byByteAlign=4-((outWidth*3L)% 4);
                  else
                        byByteAlign=0;
                    BYTE byZeroData=0;
                    BYTE*pTmpData=pBmpData;
                  for(int y=0;y<outHeight;y++)                        //按行遍历图像
                  {
                      for(int x=0;x<outWidth;x++)                     //按列遍历图像
                      {
                            memcpy(pTmpData,pListData,3);            //赋值图像数据
                            pTmpData+=3;                             //指向下一个像素数据
                            pListData+=4;                            //指向下一个像素数据
                      }
                      for(int i=0;i<byByteAlign;i++)                  //每行填充数据
                      {
                            memcpy(pTmpData,&byZeroData,1);
                            pTmpData=pTmpData+1;
                      }
                        pListData-=2L*outWidth*4;                    //指向上一行数据
                  }
                    CDC*pDC=m_Image.GetDC();
                    BITMAPINFO bInfo;
                    bInfo.bmiHeader=m_bmInfoHeader;
                  //创建并显示位图
                    HBITMAP hBmp=m_Image.SetBitmap(CreateDIBitmap(pDC->m_hDC,&m_bmInfoHeader,
                        CBM_INIT,pBmpData,&bInfo,DIB_RGB_COLORS));
                  if(hBmp!=NULL)                                     //判断之前是否显示图像
                  {
                      ::DeleteObject(hBmp);                          //删除之前显示的图像
                  }
                    delete[]pBmpData;                                //删除位图数据
                    CRect bmpRC,wndRC;
                    m_ImagePanel.GetClientRect(wndRC);               //获取面板客户区域
                    m_Image.GetClientRect(bmpRC);                    //获取图片控件客户区域
                    m_ImagePanel.OnHScroll(SB_LEFT,1,NULL);
                    m_ImagePanel.OnVScroll(SB_LEFT,1,NULL);
                  //设置滚动范围
                    m_ImagePanel.SetScrollRange(SB_VERT,0,bmpRC.Height()-wndRC.Height());
                    m_ImagePanel.SetScrollRange(SB_HORZ,0,bmpRC.Width()-wndRC.Width());
                  }
          }

(7)处理“保存”按钮的单击事件,保存旋转后的图像。方法是将旋转后的图像数据转换为3个字节表示像素的实际位图数据,然后根据位图信息定义位图文件头、位图信息头,有了位图文件头、位图信息头和位图数据,就可以生成位图文件了。代码如下:

            void CRotateImage::OnBtSave()
            {
              if(m_bLoaded)                                            //已经加载图像
              {
              CFileDialog flDlg(FALSE,"bmp","Demo.bmp",OFN_HIDEREADONLY |
                    OFN_OVERWRITEPROMPT,"位图文件|*.bmp||");           //定义文件保存对话框
              if (flDlg.DoModal()==IDOK)
              {
                    try
                    {
                        CString csSaveName=flDlg.GetPathName();         //获取保存的图像名称
                        CFile file;                                     //定义文件对象
                        //创建并打开文件
                        file.Open(csSaveName,CFile::modeCreate|CFile::modeReadWrite);
                        //写入位图文件信息头
                        file.Write(&m_bmFileHeader,sizeof(m_bmFileHeader));
                    //写入位图信息头
                    file.Write(&m_bmInfoHeader,sizeof(m_bmInfoHeader));
                    BYTE byByteAlign;
                    UINT outHeight=m_bmInfoHeader.biHeight;        //获取位图的高度
                    UINT outWidth=m_bmInfoHeader.biWidth;          //获取位图的宽度
                    //指向旋转的图像数据的最后一行
                    BYTE * pListData = m_pBmpData+((DWORD)outHeight-1)*outWidth*4;
                    //计算需要填充的字节数
                    if (outWidth % 4 != 0)
                        byByteAlign = 4-((outWidth * 3L) % 4);
                    else
                        byByteAlign = 0;
                    BYTE byZeroData = 0;
                    for(int y=0;y<outHeight;y++)                //按行遍历图像
                    {
                        for(int x=0;x<outWidth;x++)             //按列遍历图像
                        {
                            file.Write(pListData,3);           //写入位图实际数据
                            pListData+=4;                      //指向下一个像素
                        }
                        for(int i=0;i<byByteAlign;i++)          //位图字节填充
                        {
                            file.Write(&byZeroData,1);         //写入位图实际数据
                        }
                        pListData-=2L*outWidth*4;              //指向下一行数据
                    }
                    file.Close();                              //关闭文件
              }
              catch(...)
              {
                    MessageBox("文件保存失败!","提示");
              }
            }
            }
        }

举一反三

根据本实例,读者可以:

实现图像的定时旋转。