• mfc 灰度图的绘制,数据与绘图分离


    接到一个项目,需要根据udp收到的数据绘制灰度图,数据量比较大,需要实施绘制,一开始没有使用OnPaint函数,在自定义类中调用绘制部分,使用的是双缓存机制,防止闪烁,代码如下:

    template <class T1, class T2>
    void CchinaDiankeSarDisPlayImagDlg::DisplayGrayImag(T1* pgrayData, int srcWidth, int srcHeight, int extractNum, float coefficient, int dstWidth, int dstHeight)
    {
    try
    {
    //ModifyPictureCtrlSize();

    COLORREF *data;
    CBitmap bm;
    CDC *MemDC = new CDC;

    int width = dstWidth, height = dstHeight;

    CDC* pDC = GetDlgItem(IDC_STATIC)->GetDC();// 获取 HDC(设备句柄) 来进行绘图操作
    pDC->SetBkMode(TRANSPARENT); //设置背景颜色为透明
    data = new COLORREF[width * height];//为data数据分配空间,大小为灰度图像的大小

    //为data赋值
    int itmp = 0;
    T2 val;
    for (int i = 0; i < height; i++)
    {
    for (int j = 0; j < width; j++)
    {
    val = pgrayData[(j*extractNum)*srcWidth + (i*extractNum)];
    data[i*width + j] = RGB(val*coefficient, val*coefficient, val*coefficient);
    //data[i*width + j] = RGB(val/256, val/ 256, val / 256);
    }
    }

    //create bitmap
    bm.CreateBitmap(width, height, 1, 32, data);

    //实现双缓冲,避免闪烁
    MemDC->CreateCompatibleDC(pDC);
    MemDC->SelectObject(&bm);

    CRect rect;
    GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);
    ScreenToClient(&rect);
    pDC->SetStretchBltMode(COLORONCOLOR);//COLORONCOLOR
    pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), MemDC, 0, 0, width, height, SRCCOPY);

    ////删除已经分配的内存空间
    ReleaseDC(MemDC);
    delete MemDC;

    bm.DeleteObject();
    delete[] data;

    ReleaseDC(pDC);
    }
    catch(...)
    { }

    }

    后来发现当停止显示后,最后一幅图在界面放大或者缩小或者拉伸界面时会消失,于是,将此部分内容分割为取数据部分,将取到的数据放在bitmap中,绘图部分放在OnPaint函数中,窗口不管如何改变,都会对当前的数据进行重绘

    需要注意的有两点:

    第一,放置好数据后,需要出发picturn控件进行重绘新数据,需要使用一下代码

    CRect rect;
    GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);
    ScreenToClient(&rect); 需要转为客户区

    InvalidateRect(rect, true);  只让picturn部分失效 其余部分不需要重绘,
    UpdateWindow(); 立即执行onpaint响应函数,如果不适用次函数,绘图可能会滞后,因为wm_pain消息可能在消息队列中稍后的位置排列

    第二 OnPaint函数中,需要将绘图部分放在BeginPaint和EndPaint函数中间

    void CchinaDiankeSarDisPlayImagDlg::OnPaint()
    {
    if (IsIconic())
    {
    CPaintDC dc(this); // 用于绘制的设备上下文

    SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

    // 使图标在工作区矩形中居中
    int cxIcon = GetSystemMetrics(SM_CXICON);
    int cyIcon = GetSystemMetrics(SM_CYICON);
    CRect rect;
    GetClientRect(&rect);
    int x = (rect.Width() - cxIcon + 1) / 2;
    int y = (rect.Height() - cyIcon + 1) / 2;

    // 绘制图标
    dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
    CDialogEx::OnPaint();

    CPaintDC dc(this); // 用于绘制的设备上下文
    CDC *MemDC = new CDC;
    CRect rect;
    /*GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);
    ScreenToClient(&rect);*/
    PAINTSTRUCT ps;
    GetDlgItem(IDC_STATIC)->GetClientRect(&rect);
    //CDC* pDC = GetDlgItem(IDC_STATIC)->GetDC();
    CDC* pDC = GetDlgItem(IDC_STATIC)->BeginPaint(&ps);
    MemDC->CreateCompatibleDC(pDC);
    MemDC->SelectObject(&bm);
    pDC->SetBkMode(TRANSPARENT); //设置背景颜色为透明
    pDC->SetStretchBltMode(COLORONCOLOR);//COLORONCOLOR
    pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), MemDC, 0, 0, 1800, 1800, SRCCOPY);//支持图像根据控件大小拉伸功能
    ReleaseDC(MemDC);
    delete MemDC;
    GetDlgItem(IDC_STATIC)->EndPaint(&ps);
    }
    }

    这里需要用BeginPaint() 和 EndPaint()函数获得DC。原因是主窗体在处理WM_PAINT消息的时候也需要绘制发送消息给各个控件有机会绘制自己;如果不用BeginPaint() 和 EndPaint()函数获得控件DC,该控件的消息队列中的WM_PAINT消息就不会被删除,控件就还会调用其自身的OnPaint()函数重绘控件,从而我们绘制的图像会被覆盖。

    参考资料:https://blog.csdn.net/dafenqie/article/details/54448366

    https://blog.csdn.net/acoolgiser/article/details/87992597

    防止网页消失,将参考内容粘贴如下:

    问题描述:如在摄像头显示时,关闭摄像头,此时Picture控件仍然显示最后一帧图像,需要刷新掉,还原Picture控件。或者重复显示两张不同大小的图片时,第二张背景有第一张图片残留。

    解决方法1:(最笨的方法)

     用对话框背景色来填充控件,

    CRect rect;
    GetDlgItem(IDC_ShowImage)->GetClientRect(&rect);
    GetDlgItem(IDC_ShowImage)->GetDC()->FillSolidRect(&rect2, RGB(255, 255, 255));//可能会把边界线也刷掉
    //CRect rect2(rect.left+1 , rect.top+1 , rect.Width()-1 , rect.Height()-1 ); //可以转化一下,但治标不治本

    解决方法2:

    (1)Invalidate(); //使整个窗体无效,全部重绘

    (2)GetDlgItem(IDC_ShowImage)->Invalidate(true);//使控件部分无效,进行重绘,但测试发现好像无效,原因不知,有知道的可以留言告知,谢谢!

    Invalidate()是使整个窗口客户区无效, 窗口的客户区无效意味着需要重绘,会将整个窗口下所有控件都给刷新,一般会造成闪烁。

    Invalidate标记一个需要重绘的无效区域,并不意味着调用该函数后就立刻进行重绘。类似于PostMessage(WM_PAINT),需要处理到WM_PAINT消息时才真正重绘。

    Invalidate里面有个bool型的参数,用来标识重绘的时候是否用背景色填充,默认为true。

    注意:UpdateWindow() 则是要求系统对区域进行立即重绘。向窗体发送WM_PAINT消息,在发送之前判断GetUpdateRect(hWnd,NULL,TRUE)看有无可绘制的客户区域,如果没有,则不发送WM_PAINT。

    设想了一下:直接UpdateWindow()是不执行的,因此,

    InvalidateRect(rect); //声明无效区
    UpdateWindow(); //进行重绘

    确实重绘了,但由于相对坐标系的问题,导致重绘区域不是想要的,方法3会讲解原因以及解决方法。

    UpdateData()这个函数不是刷新界面用的。
    UpdateData()参数为FALSE时,将界面上控件绑定的变量的数据导到控件内,参数为TRUE时,导入方向则相反。

    解决方法3:(比较推荐的)

    InvalidateRect(rect);
    InvalidateRect(rect,true);   //rect如果为NULL,全部的窗口客户区域将被增加到更新区域中。
      rect是你指定要刷新的区域,此区域外的客户区域不被重绘,这样防止客户区域的一个局部的改动,而导致整个客户区域重绘而导致闪烁,如果最后的参数为TRUE,则还向窗体发送WM_ERASEBKGND消息,在客户区域重绘之前先重绘背景。
    如果希望立即刷新无效区域,可以在调用InvalidateRect之后调用UpdateWindow,
     
    补充说明:
    InvalidateRect是将窗口中的一块矩形区域标注为“无效”,系统会不断向窗口发送WM_PAINT消息令其重绘。在响应WM_PAINT消息时,需要调用BeginPaint获取DC来进行重绘。该函数会合并所有“无效”区域,对DC进行裁剪,将整个窗口标注为“有效”,清除WM_PAINT消息。DC经裁剪之后,在进行绘制时,超出DC范围的操作将不被处理,所以即使在响应WM_PAINT消息时绘制的是整个窗口,而实际上绘制的也只是“无效”区域。恰当地使用InvalidateRect进行刷新比刷新整个窗口的效率要高。在WM_PAINT消息时,应尽量根据PAINTSTRUCT结构中rcPaint指定的矩形来处理重绘,减少执行不必要的代码,从而提高效率。调用InvalidateRect后不需要手动发送WM_PAINT消息。
     
    但要特别注意的是,InvalidateRect(rect)  使用的是对话框的坐标系,而不是绘图控件的坐标系。所以这种用法并不能直接得到想要的效果,
    pWnd->GetClientRect(&rect);//得到控件客户区区域

    这是因为该种方法得到的 rect是控件客户区坐标, 而InvalidateRect ,RedrawWindow用的却是对话框客户区坐标,所以在使用InvalidateRect、RedrawWindow 之前 必须将其他坐标系下的坐标进行转换,否则不能按预想的执行。

    复制代码
    CWnd* pWnd = GetDlgItem(IDC_ShowImage);
    pWnd->GetClientRect(&rect);//得到控件客户端区域坐标
    pWnd->ClientToScreen(rect);//将区域坐标由 控件客户区转成对话框区
    //pWnd->GetWindowRect(&rect); //直接得到控件的对话框区坐标
    
    this->ScreenToClient(rect); //将区域坐标由 对话框区转成对话框客户区坐标
    InvalidateRect(rect);
    //RedrawWindow(rect);
    复制代码

    这里面有几个需要注意的点:

    1.可以通过GetWindowRect直接获取控件相对于对话框区坐标。

    2. 注意对话框区和对话框客户区的区别。(参考链接2)

    3.在这里,InvalidateRect(rect);和RedrawWindow(rect)效果一样,这是由于消息队列消息很少,所以执行很快,但需要注意:

    InvalidateRect()不能实现立即更新 ;

    InvalidateRect()与 UpdateWindow()结合后可以实现立即更新;

    两者结合的效果相当于RedrawWindow(rect)。

    -------------------------------------------------------------

    基于CFormView的应用程序:

    拖入了一个长方形的Picture类型控件,控件的ID为IDC_PIC1,type为Frame,(Type还有Rectange、BitMap等类型)

    Sunken、Border为选中状态,

    现在想在该控件中画图,

    实现方法为,在OnDraw函数中加入以下代码:

    CRect rect;

    CWnd *pWin = GetDlgItem(IDC_PIC1);//获取该控件的指针,就可以对该控件直接操作了
    pWin->GetClientRect(rect);//把控件的长宽、坐标等信息保存在rect里

    int width=rect.Width();//可以获取宽和高
    int height=rect.Height();

    CString str;
    str.Format("高度为:%d,宽度为:%d",height,width);

            AfxMessageBox(str,MB_YESNO|MB_ICONSTOP,0);


    CDC *pDc = pWin->GetDC();//获取该控件的画布

            //有了画布,下面可以自由的画图了,想怎么画就怎么话,

    pDc->Rectangle(rect);
    CBrush myBrush;
    myBrush.CreateSolidBrush(RGB(192,250,233));
    pDc->FillRect(rect,&myBrush);

    其实,实现起来很简单,关键语句就是加粗体的语句

    即,要获取某个区域的指针,然后获取画布

    基于对话框的程序:

    实现功能:

    1、在对话框上的一块区域绘制彩色线条。

    2、窗口被覆盖后恢复时,图像自动重绘。

    3、改变按钮颜色。

    操作:

    根据向导建立一个对话框程序。在资源中添加一个静态文本框,其ID为IDC_STATIC。

    在DialogXXXDlg.h 中加入如下内容

          CBrush m_brush;//用于自定义颜色
        CDC m_memDC;//屏幕DC兼容的内存DC
        CBitmap m_Bmp;//位图
        CWnd* m_pDrawWnd;//用于保存静态文本框的对象指针
        //
         // 初始化和双缓冲相关的数据
        void InitialDBB();
        //双缓冲内存上绘图
        void DrawOnMem();
        //在静态区域画图
        void DrawOnStaticArea();
        //更改颜色的消息响应函数
         afx_msg HBRUSH OnCtlColor(CDC *pDC, CWnd *pWnd, UINT nCtlColor);

    在DialogXXXDlg.cpp 中加入如下内容

    1、添加消息映射

    ON_WM_CTLCOLOR()

    2、OnInitDialog()中添加初始化代码

        // TODO: 在此添加额外的初始化代码
        m_brush.CreateSolidBrush(RGB(255,0,0));//初始化画刷
        m_pDrawWnd = GetDlgItem(IDC_STATIC);//获得静态窗口对象指针
        InitialDBB();// 初始化双缓冲相关的数据

    3、重写函数OnCtlColor()

    //改变按钮颜色
    HBRUSH CDialogDrawDlg::OnCtlColor(CDC *pDC, CWnd *pWnd, UINT nCtlColor)
    {
        HBRUSH hbr=CDialog::OnCtlColor(pDC,pWnd,nCtlColor);
        //
        if(pWnd->GetDlgCtrlID()==IDOK || pWnd->GetDlgCtrlID()==IDCANCEL)
        {
             pDC->SetTextColor(RGB(255,0,0));//设置文本颜色
             pDC->SetBkMode(TRANSPARENT);//文字背景设为透明
             pDC->SetBkColor(RGB(255,0,0));//设置背景色
            return m_brush;
        }
        //
        return hbr;//m_brush;///hbr
    }

    4、添加头文件中声明的函数的定义代码


    // 初始化和双缓冲相关的要素
    void CDialogDrawDlg::InitialDBB()
    {
    CRect rt;
        m_pDrawWnd->GetClientRect(&rt);
        CDC* sDC = m_pDrawWnd->GetDC();
    // 为屏幕DC创建兼容的内存DC
        if(!m_memDC.CreateCompatibleDC(sDC))//
    {              
       ::PostQuitMessage(0);

        
    // 创建位图,不能是m_memDC,否则无颜色
    m_Bmp.CreateCompatibleBitmap(sDC, rt.Width(), rt.Height());//m_memDC
    // 相当于选择画布,m_pDrawWnd->
        ::SelectObject(m_memDC.GetSafeHdc(), m_Bmp); 
        m_pDrawWnd->ReleaseDC(sDC);
    }


    //双缓冲内存上绘图
    void CDialogDrawDlg::DrawOnMem()
    {
        CRect rect;
        m_pDrawWnd->GetClientRect(&rect);

        //COLORREF crl = GetSysColor(COLOR_3DFACE);
        //m_memDC.FillSolidRect(rect, crl); 
        //m_memDC.FillSolidRect(&rect, 0x00FFFFFF);// 白色填充, 注意,这次是画在内存设备环境上
        CPen pen(PS_SOLID,1,RGB(255,0,0));
        m_memDC.SelectObject(&pen);
       
        //画图部分
        m_memDC.MoveTo(rect.right,rect.bottom);
        m_memDC.LineTo(rect.left,rect.top);
    }
    //
    //在静态区域画图
    void CDialogDrawDlg::DrawOnStaticArea()
    {
        CWnd* pWnd = GetDlgItem(IDC_STATIC);//获得静态文本框的窗口对象
        CRect rect;
        pWnd->GetClientRect(&rect);    
        //
        CDC* pDC = pWnd->GetDC();//
        //
         DrawOnMem();
        // 一次性的将内存设备环境上绘制完毕的图形"贴"到屏幕上
        pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &m_memDC, 0, 0, SRCCOPY);
        pWnd->ReleaseDC(pDC);//
        //
    }

    5、OnPaint()函数中添加绘图代码(红色部分为添加内容)

    void CDialogDrawDlg::OnPaint()
    {
    if (IsIconic())
    {
       CPaintDC dc(this); // 用于绘制的设备上下文

       SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

       // 使图标在工作区矩形中居中
       int cxIcon = GetSystemMetrics(SM_CXICON);
       int cyIcon = GetSystemMetrics(SM_CYICON);
       CRect rect;
       GetClientRect(&rect);
       int x = (rect.Width() - cxIcon + 1) / 2;
       int y = (rect.Height() - cyIcon + 1) / 2;

       // 绘制图标
       dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
       CDialog::OnPaint();
         //重绘图像区域
        PAINTSTRUCT ps;
        CRect rt;
        m_pDrawWnd->GetClientRect(&rt);    
         CDC* pDC=m_pDrawWnd->BeginPaint(&ps);
         DrawOnMem();
        pDC->BitBlt(0, 0, rt.Width(), rt.Height(), &m_memDC, 0, 0, SRCCOPY);    
        m_pDrawWnd->EndPaint(&ps);
    }
    }

    这里需要用BeginPaint() 和 EndPaint()函数获得DC。原因是主窗体在处理WM_PAINT消息的时候也需要绘制发送消息给各个控件有机会绘制自己;如果不用BeginPaint() 和 EndPaint()函数获得控件DC,该控件的消息队列中的WM_PAINT消息就不会被删除,控件就还会调用其自身的OnPaint()函数重绘控件,从而我们绘制的图像会被覆盖。

    说明:这里的DrawOnStaticArea()没有用到。这是用作扩展时在该区域绘图的,写在这里是方便日后扩展。如果这样的话,OnPaint()中就不需要DrawOnMem();这句,直接将前面绘好的图贴上来就是。

  • 相关阅读:
    每日一招:个股五种见底特征
    中国互联网或将被世界淘汰 !
    了解这23种设计模式
    用Nikto探测一个网站所用到的技术
    javax.crypto.BadPaddingException: Given final block not properly padded
    java.net.UnknownHostException: promote.cache-dns.local: unknown error
    CentOS 7 Rescure
    最小化安装的CentOS7挂载ntfs格式的U盘
    CentOS 7 最小化安装的无线网络配置
    Mysql Specified key was too long; max key length is 767 bytes
  • 原文地址:https://www.cnblogs.com/yuyuanbaobei521/p/13926905.html
Copyright © 2020-2023  润新知