65.9K
CodeProject 正在变化。 阅读更多。
Home

旋转图形 - 高级内存设备上下文

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (7投票s)

2000 年 1 月 10 日

viewsIcon

123581

downloadIcon

4001

一个内存 DC,允许您旋转图形

导航

[ 插图 | 示例 | 函数参考 | 技巧与窍门 | 待办事项 | 下载 | 最新 ]

内存设备上下文 (CMemDC)

我最近下载了 Keith Rule 发布的 CMemDC 类(请在此处查看他在 codeguru 上的文章)。
它面临以下问题:如果您在设备上下文中执行相当大的操作(例如,在 OnPaint() 中),您的显示有时会非常闪烁。
为了规避这个问题,Keith 设计了 CMemDC,它创建一个兼容的“内存”设备上下文,您可以在其中交替绘制。
完成在此设备上下文(在内存中)的绘制后,您将其剪辑到原始设备上下文 - 只有一次位图复制操作发生在可见的上下文中。因此,输出不再闪烁(至少几乎不会)。
但是,他的实现假设您总是希望绘制整个剪辑区域 - 即使它已经被 WM_ERASEBKGND 或其他内容填充了。

为什么旋转文本并不容易

此外,我最近设计了一个显示旋转文本的新按钮。
现在,您可以创建一个输出旋转文本的 CFont(使用 LOGFONT 并设置 lfOrientationlfEscapement),但据我所知,这仅适用于 TrueType 字体 - 至少对于 MS Sans Serif 不起作用。
至少,CDC 的文本函数在您设置了这样的字体后似乎会变得混乱 - 它们会产生“随机”结果……如果您不相信,可以试试 :O

因此,我查看了 Keith 的 CMemDC,然后看了看 CDC::PlgBlt() 函数。我决定将 CMemDC 扩展为一个“可旋转”内存上下文,即,一个不仅将一个内存位图复制到输出设备上下文,而且还可以根据您的喜好旋转它的内存上下文。

cdxCRot90DC - 可旋转内存 DC

好吧,cdxCRot90DC 解决了这些问题。除了指定一个“目标”设备上下文(通常是您的输出设备)之外,它还允许您从中提供一个矩形(CMemDC 始终使用当前输出上下文的剪辑框)和一个“偶数角度”,即 0°、90°、180°、270°...(这就是为什么它被称为 cdxCRot90DC 而不是 cdxCRotDC :)。
注意:我使用“偶数角度”一词来表示可以被 90 整除的角。
此外,请注意,当我谈论角度 x 时,x+n*360 和 x-n*360(n 为正整数)也适用。

一个例子可以说明该设备上下文的作用(使用 90° 的 cdxCRot90DC

'rotated_dc.gif (7491 Byte)

您可能认识到,旋转不仅限于文本 - 您可以随意在内存设备上下文中绘制,结果将被放入您的原始设备上下文中,就像已旋转绘制一样 - 遗憾的是,您不能使用原始矩形在 cdxCRot90DC 中绘制 - 您需要使用自动为您转换的矩形(使用 GetRotRect() 获取)。
如果您选择“偶数角度”0°,cdxCRot90DC 类基本上就像 CMemDC 一样,但有一些优势

  • 您可以自由定义目标 DC 中您想工作的矩形 [请参阅 构造函数/Create()]
  • 您可以将原始设备上下文的内容初始复制到您的内存位图中(这会保留 WM_ERASEBKGND 或所有者绘制控件在其 DrawItem() 函数中绘制的内容)[请参阅 构造函数/Create()]
  • 您可以丢弃您的更改。
  • CMemDC 始终将其位图复制回您的原始设备上下文 - 这可能不合适(例如,您发现文本为空,位图中没有任何内容)[函数 Invalidate()]

旋转输出的其他功能

  • 角度的透明使用。
    您的代码无需更改即可适应不同的角度(如果您使用 GetRotRect())。
  • 自动克隆原始 DC 的字体、背景色、文本模式和文本颜色(并在使用后恢复它们)。
  • 一个对象可以重复使用另一个目标上下文矩形(不会创建新的 DC,并且如果可能,将重用旧的位图)。
    [请参阅 Create()]

请注意

  • 此设备上下文不支持打印(与 CMemDC 不同)。
    如果有人能帮助我实现它,我将非常高兴。
    我自己没有能够支持旋转输出的打印机……

如何使用 cdxCRot90DC

一个简单的 OnPaint(),它在设备上下文中绘制旋转文本
[此示例使用 构造函数GetRotRect()IsCreated()调用 Finish() 的析构函数]。

void MyWnd::OnPaint()
{
    // create "destDC"
    CPaintDC    destDC(this);
    
    // get client rect
    CRect       rectClient;
    GetClientRect(rectClient);
    
    {
        cdxCRot90DC rotDC(destDC,rectClient,90 /*90 degrees*/ );
        if(!rotDC.IsCreated())
            return;
        
        // get client rect in rotated coordinates
        CRect rectRot = dc.GetRotRect();
        
        // example: print programmer's most loved text into the
        // the center of the dest DC - rotated and centered
        CString s = _T("Hello world");
        CSize  sz = rotDC.GetTextExtent(s);
        rotDC.TextOut(rectRot.left + (rectRot.Width() - sz.cx) / 2,
            rectRot.top   + (rectRot.Height() - sz.cy) / 2,
            s);
    } 
    
    // destructor of rotDC calls rotDC.Finish()
}

一个示例 OnPaint(),它在您的旋转内容周围绘制 3D 边框,其外观如下(此处,我们直接使用 Finish() 函数)
[此示例使用 构造函数GetRotRect()IsCreated()Finish()]。

void MyWnd::OnPaint()
{
  // create "destDC"
  CPaintDC    destDC(this);

  // get entire client area
  CRect       rectClient;
  GetClientRect(rectClient);

  // get rectangle you want to draw to except borders
  CRect rectInner = rectClient;
  rectInner.DeflateRect(::GetSystemMetrics(SM_CXEDGE),::GetSystemMetrics(SM_CYEDGE));

  // construct rotated device context
  cdxCRot90DC rotDC(origDC,rectInner,90/*90 degrees*/);

  // check whether there is a non-empty visible rectangle
  if(rotDC.IsCreated())
  {
    // get the rectangle rectReal of rotDC that matches rectRotated in destDC
    CRect rectRotClient = rotDC.GetRotRect();

    // draw nice things into my device context using rectRotClient
    ...
    rotDC.TextOut(rectRotClient.left,rectRotClient.top,"Left-top text"));
    ...
  }

  // now copy bitmap back to destDC
  rotDC.Finish();

  // and draw a border around rectRotated
  destDC.DrawEdge(rectClient,EDGE_RAISED,BF_RECT);
}

函数参考

请注意,以下文本仅涵盖最重要的函数

cdxCRot90DC();
cdxCRot90DC(CDC & destDC, const CRect & rectDC, int iAngle, bool bCopy = false);

cdxCRot90DC(CDC *pdestDC, const CRect & rectDC, int iAngle, bool bCopy = false);
cdxCRot90DC(CDC & destDC, int iAngle, bool bCopy = false);
cdxCRot90DC(CDC *pdestDC, int iAngle, bool bCopy = false);

构造一个新对象。
最后四个创建者立即调用 Create(),它为您创建一个新的设备上下文。
如果您使用它们,请使用 IsCreated() 检查设备上下文是否已成功设置(否则,如果 Create() 失败,cdxCRot90DC::m_hDC(来自 CDC)将不会正确设置,您会得到数千个 ASSERT…)。
有关更多信息,请参阅 Create()

请注意,析构函数会在设备上下文正确设置后自动调用 Finish()

bool Create(CDC & destDC, const CRect & rectDC, int iAngle, bool bCopy = false);
bool Create(CDC * pdestDC, const CRect & rectDC, int iAngle, bool bCopy = false);
bool Create(CDC & destDC, int iAngle, bool bCopy = false);
bool Create(CDC * pdestDC, int iAngle, bool bCopy = false);

创建设备上下文。
如果此函数成功返回,您就可以开始在其上绘制。
调用 Finish()旋转 DC 的位图复制回您的目标 DC(析构函数会自动完成此操作 - 有关更多信息,请参阅 Finish())。

注意 #1

由于您的矩形(请参阅下面的参数)可能已旋转,因此您不能使用 rectDC 的坐标来绘制数据(它们可能位于旋转矩形之外)。
因此,使用 GetRotRect() 函数来获取与您的 rectDC 匹配的设备上下文的矩形。

注意 #2

您可以多次调用 Create() - 每次调用时,它都会为您创建一个“新”的 cdxCRot90DC(如果以前的位图满足新的要求,则不会分配新位图)。
但是,如果您想将以前的位图复制回其目标设备上下文,您必须先调用 Finish()

参数

  • destDC, pdestDC
    “原始”设备上下文;pDestDC 不能为 NULL
  • rectDC
    您想使用此内存上下文绘制的原始设备上下文中的矩形。
    如果未提供,将使用当前剪辑框。
    请注意,在这两种情况下,rectDC 都将与原始设备当前的剪辑框相交。
    如果结果矩形为空,此函数将失败。
  • iAngle
    输出的旋转角度。
    也就是说,如果您提供“90”,则从左到右绘制的箭头将指向从下到上(请参阅上面的 示例图像)。
    如果您使用 0,cdxCRot90DC 将充当 CMemDC(不发生旋转)。
  • bCopy
    设置为 true,如果您想将 destDCrectDC 的内容复制到您新创建的设备上下文中。
    如果已经有任何(非旋转的)图形,而您不希望在内存设备上下文剪辑回其位图时覆盖它们,这将非常有用。
    请注意,CDC::PlgBlt 不是最快的函数之一,因此您可能需要先创建旋转数据,再将其剪辑回来,然后再创建其他数据。

如果以下情况,此函数返回 false

  • destDC 用于打印(我没有能够打印旋转图像的打印机……)
  • rectDC 与您的 destDC 的剪辑框相交为空 - 如果您未提供 rectDC - 您的 destDC 的剪辑框为空。

bool IsCreated() const;

如果设备上下文成功创建,则返回 true,否则返回 false

const CRect & GetRotRect() const;
operator const CRect & () const { return GetRotRect(); }

可用于获取目标(“输出”)设备上下文的矩形,该矩形与旋转内存设备上下文的矩形匹配。
如果您参考示例图像,此函数将返回 (-120,10,-20,250) 作为目标矩形 (10,20,250,120)(rotate() 可用于转换其他 2D 对象)。

如果设备上下文未成功设置,此函数将返回 (0,0,0,0)

bool Finish();

通知 cdxCRot90DC 您已完成工作 - 它会将位图复制回目标设备上下文。
如果以下情况,析构函数会自动调用此函数

请注意,再次调用 finish 不会再次复制位图。

void Invalidate();

使 cdxCRot90DC 设备上下文无效 - Finish() 不会将位图绘制回目标 DC(因此析构函数也不会)。
此外,此函数会将当前剪辑区域设置为一个空矩形 - 所有进一步的绘制操作都不会影响内存设备上下文。
如果您发现您的旋转图像将为空或需要避免设备上下文将其 blit 回目标设备上下文(这样会更快!),则可以使用此函数。

CRect rotate(const CRect & r) const;
CPoint rotate(const CPoint & p) const;
CSize rotate(const CSize & s) const;
void rotate(POINT *pPnts, UINT nCnt) const;

目标 2D 对象转换为旋转 2D 对象。
示例:如果您将矩形“r”传递给 Create()GetRotRect() 将返回 rotate(r)

CRect rotateBack(const CRect & r) const;
CPoint rotateBack(const CPoint & p) const;
CSize rotateBack(const CSize & s) const;
void rotateBack(POINT *pPnts, UINT nCnt) const;

旋转 2D 对象转换为目标 2D 对象。

CRect operator()(const CRect & r, bool bFwd = true) const;
CPoint operator()(const CPoint & p, bool bFwd = true) const;
CSize operator()(const CSize & sz, bool bFwd = true) const;
void operator()(POINT *pPnts, UINT nCnt, bool bFwd = true) const;

以上函数组的快捷运算符。
如果 bFwdtrue,则使用 rotate(),否则使用 rotateBack()

static CRect rotate(const CRect & r, int iAngle);
static CPoint rotate(const CPoint & p, int iAngle);
static CSize rotate(const CSize & s, int iAngle);
static void rotate(POINT *pPnts, UINT nCnt, int iAngle);

目标 2D 对象转换为使用偶数角度 iAngle 旋转的旋转 2D 对象。如果您想将对象从旋转 DC 转换为目标 DC,请使用 rotate(...,-iAngle)
您可以使用这些函数进行计算,而无需为此任务创建设备上下文。

operator cdxCRot90DC * ();
cdxCRot90DC *operator->();
const cdxCRot90DC *operator->() const;

cdxCRot90DC 类用作指针。
这可能对于期望 CDC 指针的 CDC 成员函数很有用。

一些技巧与窍门

  • 在旋转设备上下文中绘制 3D 文本可能需要您知道目标的右下角在旋转设备上下文中的方向。
    例如,如果您想绘制一些禁用文本(如禁用的 CStatic),您通常会使用
    ...
    dc.SetBkMode(TRANSPARENT);
    dc.SetTextColor(::GetSysColor(COLOR_3DHILIGHT));
    dc.TextOut(pnt + CPoint(1,1),strText);
    dc.SetTextColor(::GetSysColor(COLOR_3DSHADOW));
    dc.TextOut(pnt,strText);
    ...

    但是如果您在旋转 DC 中这样做会发生什么?
    之前,您的“阴影”文本会在左上角。但是如果设备上下文将您的输出旋转 180°,它会在右下角……很难看!
    解决方案是使用 rotate() 将向量 (1,1) 转换为旋转设备上下文中的相应向量

    ...
    rotDC.SetBkMode(TRANSPARENT);
    rotDC.SetTextColor(::GetSysColor(COLOR_3DHILIGHT));
    rotDC.TextOut(pnt + rotDC.rotate( CPoint(1,1) ),strText);
    rotDC.SetTextColor(::GetSysColor(COLOR_3DSHADOW));
    rotDC.TextOut(pnt,strText);
    ...

    ……一切都会好起来的 :)

    请注意,有一个成员函数 DrawControl()(此处未描述),它可能会解决您的大部分问题 :)

  • CDC::DrawEdge() 和 CDC::Draw3dRect() 可能也有同样的问题!
    我建议使用最初显示为示例 2 的代码来设置需要这些函数的对象(尽管可以轻松地为它们编写快捷方式)。:)

待办事项

  1. 支持打印
  2. 将以下新类发布到 codeguru
    • cdxCRotButton:一个带有旋转文本和图标的 CButton
    • cdxCRotStatic:一个带有旋转文本和图标的 CStatic
    • cdxCRotBevelLine:一个带有旋转文本的斜边线。

这些类已经完成,但我没有时间为 codeguru 撰写合适的文本……希望很快能找到时间。

  • 添加 cdxCRotDC,它允许旋转任何角度 :)

最新

Llew Goodstadt(来自英国牛津大学生理学实验室)提供了一个更新的项目,该项目解决了原始代码的一些问题,它不再使用 PlgBlt,而是使用 DIB 来执行旋转。Llew 没有时间来撰写他的解决方案,也没有让他满意地完成代码,因此演示项目在此“按原样”提供,以防其他人希望进一步扩展。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.