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






4.75/5 (7投票s)
2000 年 1 月 10 日

123581

4001
一个内存 DC,允许您旋转图形
导航
[ 插图 | 示例 | 函数参考 | 技巧与窍门 | 待办事项 | 下载 | 最新 ]
内存设备上下文 (CMemDC)
我最近下载了 Keith Rule 发布的 CMemDC
类(请在此处查看他在 codeguru 上的文章)。
它面临以下问题:如果您在设备上下文中执行相当大的操作(例如,在 OnPaint()
中),您的显示有时会非常闪烁。
为了规避这个问题,Keith 设计了 CMemDC
,它创建一个兼容的“内存”设备上下文,您可以在其中交替绘制。
完成在此设备上下文(在内存中)的绘制后,您将其剪辑到原始设备上下文 - 只有一次位图复制操作发生在可见的上下文中。因此,输出不再闪烁(至少几乎不会)。
但是,他的实现假设您总是希望绘制整个剪辑区域 - 即使它已经被 WM_ERASEBKGND
或其他内容填充了。
为什么旋转文本并不容易
此外,我最近设计了一个显示旋转文本的新按钮。
现在,您可以创建一个输出旋转文本的 CFont
(使用 LOGFONT
并设置 lfOrientation
和 lfEscapement
),但据我所知,这仅适用于 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
)
您可能认识到,旋转不仅限于文本 - 您可以随意在内存设备上下文中绘制,结果将被放入您的原始设备上下文中,就像已旋转绘制一样 - 遗憾的是,您不能使用原始矩形在 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
,如果您想将destDC
的rectDC
的内容复制到您新创建的设备上下文中。
如果已经有任何(非旋转的)图形,而您不希望在内存设备上下文剪辑回其位图时覆盖它们,这将非常有用。
请注意,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
您已完成工作 - 它会将位图复制回目标设备上下文。
如果以下情况,析构函数会自动调用此函数
- Create() 成功并且
- 您没有使用 Invalidate()并且
- 您没有自己调用
Finish()
。
请注意,再次调用 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;
以上函数组的快捷运算符。
如果 bFwd
为 true
,则使用 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 的代码来设置需要这些函数的对象(尽管可以轻松地为它们编写快捷方式)。:)
待办事项
- 支持打印
- 将以下新类发布到 codeguru
cdxCRotButton
:一个带有旋转文本和图标的CButton
。cdxCRotStatic
:一个带有旋转文本和图标的CStatic
。cdxCRotBevelLine
:一个带有旋转文本的斜边线。
这些类已经完成,但我没有时间为 codeguru 撰写合适的文本……希望很快能找到时间。
- 添加
cdxCRotDC
,它允许旋转任何角度 :)
最新
Llew Goodstadt(来自英国牛津大学生理学实验室)提供了一个更新的项目,该项目解决了原始代码的一些问题,它不再使用 PlgBlt
,而是使用 DIB 来执行旋转。Llew 没有时间来撰写他的解决方案,也没有让他满意地完成代码,因此演示项目在此“按原样”提供,以防其他人希望进一步扩展。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。