从 CDC 创建 CBitmap?

Posted

技术标签:

【中文标题】从 CDC 创建 CBitmap?【英文标题】:Create CBitmap from CDC? 【发布时间】:2021-06-07 16:52:39 【问题描述】:

使用 C++/MFC 和 GDI(不是 GDI+),总体目标是创建一个有图案的HBRUSH,将在OnCtlColor 中使用以红色勾勒编辑控件,并能够打开轮廓和关闭。为此,您使用CreatePatternBrush 将位图附加到HBRUSH。这是使用存储的位图资源执行此操作的代码:

CDialog::OnInitDialog();
BOOL ok = redBoxBitmap.LoadBitmap(MAKEINTRESOURCE(IDB_mespe_EditBox_Red));
ok = redBoxBrush.CreatePatternBrush(&redBoxBitmap);

OnCtlColor

HBRUSH CModelEditorSpecies::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

    HBRUSH hbr;
    int ctrlID=pWnd->GetDlgCtrlID();
    if(ctrlID==IDC_MyEditControl)
        hbr=(HBRUSH) redBoxBrush;
    else
        hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    return hbr;

上面的代码都可以按需要工作。但是,这取决于位图的大小调整到编辑控件。我现在需要的是在 C++ 程序中创建位图的能力,其大小与控件的客户区一致,这取决于控件的设计大小(在对话框编辑器中)和用户在Windows 10 设置。

我找不到一种直接的方法来构建位图,或者更好的是,创建一个适当大小的空位图(可以做),将其选择为CDC(可以做),将红色框画入其中(可以做),然后从CDC中提取更新位图(怎么做?)。

任何人都可以建议如何以编程方式创建位图,或者建议一种更好的方法,在程序需要时让编辑控件以红色框起来?

添加以回应@Constantine Georgiou 3/9 的回答:

新代码:

CBitmap redBoxBitmap; // member variables of class CModelEditorSpecies
CBrush redBoxBrush;

BOOL CModelEditorSpecies::OnInitDialog()

    CDialog::OnInitDialog();
    BOOL ok;
    CRect r; defaultSpecies1Ctrl.GetClientRect(&r);
    xx(r.Width(), r.Height()/*, redBoxBrush*/);
    ok = redBoxBrush.CreatePatternBrush(&redBoxBitmap);
    //...


void CModelEditorSpecies::xx(const int w, const int h)

    CDC *pDC=GetDC();
    redBoxBitmap.CreateCompatibleBitmap(pDC, w, h);
    
    // Create a red pen
    CPen redPen;
    redPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
    
    // Draw the bitmap - red pen & default background brush
    CBitmap *pOldBitmap=pDC->SelectObject(&redBoxBitmap);
    pDC->SelectObject(&redPen);
    CBrush editBoxBrush;
    editBoxBrush.CreateSysColorBrush(COLOR_WINDOW);
    pDC->SelectObject(&editBoxBrush);
    pDC->Rectangle(0, 0, w, h);
        
    pDC->SelectObject(pOldBitmap);
    
    // Create the edit-control custom brush
    redBoxBrush.CreatePatternBrush(&redBoxBitmap);
    return;

此代码生成一个全黑的编辑控件,就好像正在使用的位图是单色的。如果在 dc 中绘制不会影响位图,或者在与 dc 兼容的位图中绘制不使用@IInspectable 建议的redPeneditBoxBrush 中的颜色,这是可以预期的。

【问题讨论】:

使用WM_CTLCOLOR(或OnCtlColor())事件会影响编辑控件的背景,而不是轮廓。我猜你到目前为止所做的是创建一个大小等于控件客户区的位图,边缘为红色,并用它创建一个图案画笔。您可以使用CreateCompatibleBitmap() 简单地创建位图,用背景画笔填充它并调用MoveTo()/LineTo() 来绘制轮廓。画笔可以“缓存”(创建一次,只有在对话框类实例被销毁时才被销毁)。 要考虑的替代实现: 1. 处理WM_NCPAINT 消息,并改为对控件的边框执行自定义绘制。 2. 处理WM_PAINT 消息用于对话框 (CModelEditorSpecies::OnPaint()) 以在控件周围 绘制一个框架(FrameRect() 函数)。 @Constantine Georgiou:您的第一条评论建议创建位图,但该位图中有什么?背景颜色为白色。 OnCtlColor() 使用画笔填充控件,但没有机会绘制红色边框线。正如您在现有代码中看到的那样,我已经在缓存画笔了。 您可以创建一个内存设备上下文 (CreateCompatibleDC),在其中选择您的位图,执行您的渲染,然后在将其拆除之前将位图从其中选择回来。您的位图现在包含您在其上渲染的任何内容。不过,如果需要的话,我不相信您可以将 alpha 透明度与图案画笔一起使用。 在发布之前,当我尝试创建memDC、选择位图、绘制和选择时,我得到了一个全黑的位图。我会再试一次。 【参考方案1】:

以下是创建画笔的方法 - 使用 Win32 函数而不是它们的 MFC 包装器,但您可以弄清楚。

BOOL CModelEditorSpecies::OnInitDialog()

    CDialogEx::OnInitDialog();

    // Get edit-control's size
    RECT rc;
    GetDlgItem(IDC_MyEditControl)->GetClientRect(&rc);

    // Create the bitmap and a memory-DC
    HDC hDC = ::GetDC(HWND_DESKTOP);
    HDC mDC = CreateCompatibleDC(hDC);
    HBITMAP hBmp = CreateCompatibleBitmap(hDC, rc.right, rc.bottom);
    ::ReleaseDC(HWND_DESKTOP, hDC);

    // Create a red pen
    HPEN hPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));

    // Draw the bitmap - red pen & default background brush
    HBITMAP hOldBmp = (HBITMAP) SelectObject(mDC, hBmp);
    HPEN hOldPen = (HPEN) SelectObject(mDC, hPen);
    HBRUSH hOldBr = (HBRUSH) SelectObject(mDC, GetSysColorBrush(COLOR_WINDOW));
    Rectangle(mDC, rc.left, rc.top, rc.right, rc.bottom);
    SelectObject(mDC, hOldBr);
    SelectObject(mDC, hOldPen);
    SelectObject(mDC, hOldBmp);

    // Create the edit-control custom brush
    redBoxBrush = CreatePatternBrush(hBmp);

    // Clean-up - the SysColorBrush doesn't need to be deleted
    DeleteObject(hPen);
    DeleteObject(hBmp);
    DeleteDC(mDC);

    return TRUE;


HBRUSH CModelEditorSpecies::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

    return (nCtlColor == CTLCOLOR_EDIT && pWnd->GetDlgCtrlID() == IDC_MyEditControl) ?
        redBoxBrush : CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);

还要检查我在 cmets 中建议的替代方案。这个将绘制到编辑控件的客户区。


编辑:

我在上面发布的代码确实有效,在它运行后剩下的就是HBRUSH 句柄(您的编辑框的模式画笔)。我不明白你为什么坚持使用 MFC 包装器。它们是“更高级别”的瘦包装器,所以......“瘦”实际上,您仍然必须执行几乎完全相同的操作(创建、选择到 DC、执行某些绘图操作、从 DC 中选择) GDI 的那些。您唯一不需要执行的操作是删除资源(对象的析构函数会为您调用DeleteObject())。

无论如何,如果您更喜欢 MFC 而不是 GDI,那么让我们看看您的代码有什么问题。它有很多问题:

首先,您需要memory DC 才能在位图上绘图,通过调用GetDC() 获得的窗口DC 将在窗口表面上绘图。 调用GetDC()得到的DC必须返回给系统(ReleaseDC()),因为pDC只是一个指针,编译器不会调用析构函数。 您选择到 DC 中的对象必须在销毁之前将其选中,否则可能会发生内存泄漏。

所以你的代码应该如下所示进行更改:

void CModelEditorSpecies::xx()

    CRect r;
    GetDlgItem(IDC_MyEditControl)->GetClientRect(&r);
    
    // Create the bitmap and a memory-DC
    CBitmap redBoxBitmap;
    CDC mDC, *pDC = GetDC();
    redBoxBitmap.CreateCompatibleBitmap(pDC, r.Width(), r.Height());
    mDC.CreateCompatibleDC(pDC);
    ReleaseDC(pDC);

    // Create a red pen and get the default background brush
    CPen redPen;
    redPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
    CBrush editBoxBrush;
    editBoxBrush.CreateSysColorBrush(COLOR_WINDOW);

    // Draw the bitmap - red pen & default background brush
    CBitmap *pOldBitmap = mDC.SelectObject(&redBoxBitmap);
    CPen *pOldPen = mDC.SelectObject(&redPen);
    CBrush *pOldBrush =mDC.SelectObject(&editBoxBrush);
    mDC.Rectangle(r);
    mDC.SelectObject(pOldBrush);
    mDC.SelectObject(pOldPen);
    mDC.SelectObject(pOldBitmap);

    // Create the edit-control custom brush
    redBoxBrush.CreatePatternBrush(&redBoxBitmap);

与GDI版本基本相同。

【讨论】:

您的 MFC 代码运行良好。我错误地假设 GetDC 返回了一个内存 DC。 Tnx 为您的耐心等待。顺便说一句,我为此使用 MFC,因为项目的其余部分使用它。

以上是关于从 CDC 创建 CBitmap?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 CBitmap 上使用具有透明背景的 CDC 绘制文本?

VC++图像处理中对BItmap文件结构的读取

如何使用 MFC 正确地将 ICON 转换为 BITMAP?

MFC中,如何将CBitmap中的图片复制到另一CBitmap的一块区域中,即如何读取一个CBit

C# Marshal / Pinvoke CBitmap?

如何从 CRect 中的数据创建 CBitmap 对象?