介绍
窗口中的矩形,带圆角的矩形和椭圆只能由GDI在轴向上绘制。假如有人希望在Windows NT下绘制旋转或歪斜的图形,他可以使用世界坐标系变换。很不幸的是在Windows 95/98下,是没有世界坐标系变换的。作为一个跨平台的解决方案,就需要自己做更多的工作。矩形能由四边形模拟,这样它就能旋转和歪斜了。然而,椭圆又该怎么办呢?基本上有三个选择。
两种选择
使用一个定制的函数来画椭圆。
椭圆的数学模型相对简单,而且还有用于在标准文本中旋转椭圆的修改过的Bresenham方程。然而,这种方法必须自己执行光栅操作,这样在绘制宽线时就变得复杂了。这种努力只有在向一个脱离屏幕的表面(比如DirectDraw)或位图上绘制视才是值得的 用连接的线段来绘制椭圆。
实际的线条可以通过LineTo(...)或Polyline(...)图形设备接口调用。你可以自己完成椭圆的近似,或者使用GDI的FlattenPath(...) 函数。
使用贝塞尔曲线来近似绘制椭圆。
这里就举例说明这种方法。
用贝塞尔曲线绘制椭圆
使用四条贝塞尔曲线,每条代表原轴向椭圆的90度,这样就能获得一个相当近似的椭圆,最大误差只有0.027%。这个最大误差相当于长径3700的椭圆的误差小于一个像素,这已经超出我们所要求的准确度了。
优点
简单。
只需要有四个GDI调用。贝塞尔曲线控制点的计算代价是很小的。
快速
你可以利用现在新的显卡对曲线绘制的硬件支持。在我的系统上,这和调用GDI函数Ellipse(...)绘制椭圆的速度比,如果不是更快,至少也是一样快。
变化
因为贝塞尔曲线在旋转、缩放和平移时是不变的,在对椭圆做同样的变化时就只需要传送控制点。更巧的是,因为在一个三次贝塞尔曲线上的每个点都是控制点的重心组合,在仿射映射中曲线上控制点之间的关系是不变的。
设备无关性
假如想自己把椭圆转化为线段或光栅,那么每次表面的分辨率和设备描述表改变时(例如向一个打印机设备描述表绘制时),就必须重新光栅化。而使用贝塞尔曲线时就不需要这样做。还有一个好处就是椭圆能通过图元文件输出到绘画程序,例如CORELDRAW,在其中可以没有失真的缩放图形。
过程
首先以一个轴向椭圆的外接边界矩形开始(使用一个普通的GDI调用)。13个定义4条组成椭圆的贝塞尔曲线的控制点(以下标为0-12)可使用一个经验常量计算得出。下列代码为Y轴正方向向下的的映射模式产生控制点(例如MM_TEXT)。在Y轴正方向向上时,只要如注释中所示,把偏移量设为负值就行了。
// Create points to simulate ellipse using beziers
//使用贝塞尔曲线创建点,模拟椭圆
void EllipseToBezier(CRect& r, CPoint* cCtlPt)
// MAGICAL CONSTANT to map ellipse to beziers
//
/3*(sqrt(2)-1)
// 把椭圆映射为贝塞尔曲线的常量 2/3*(sqrt(2)-1)
const double EToBConst =
.2761423749154;
CSize offset((int)(r.Width() * EToBConst), (int)(r.Height() * EToBConst));
// Use the following line instead for mapping systems where +ve Y is upwards
// 在Y轴正方向向上时,使用下面一行
// CSize offset((int)(r.Width() * EToBConst), -(int)(r.Height() * EToBConst));
CPoint centre((r.left + r.right) / 2, (r.top + r.bottom) / 2);
cCtlPt[0].x = //------------------------/
cCtlPt[1].x = // /
cCtlPt[11].x = // 2___3___4 /
cCtlPt[12].x = r.left; // 1 5 /
cCtlPt[5].x = // | | /
cCtlPt[6].x = // | | /
cCtlPt[7].x = r.right; // 0,12 6 /
cCtlPt[2].x = // | | /
cCtlPt[10].x = centre.x - offset.cx; // | | /
cCtlPt[4].x = // 11 7 /
cCtlPt[8].x = centre.x + offset.cx; // 10___9___8 /
cCtlPt[3].x = // /
cCtlPt[9].x = centre.x; //------------------------*
cCtlPt[2].y =
cCtlPt[3].y =
cCtlPt[4].y = r.top;
cCtlPt[8].y =
cCtlPt[9].y =
cCtlPt[10].y = r.bottom;
cCtlPt[7].y =
cCtlPt[11].y = centre.y + offset.cy;
cCtlPt[1].y =
cCtlPt[5].y = centre.y - offset.cy;
cCtlPt[0].y =
cCtlPt[12].y =
cCtlPt[6].y = centre.y;
使用与下面近似的代码可完成椭圆的旋转
// LDPoint is an equivalent type to CPoint but with floating point precision
// LDPoint是一个和CPoint相当的类型,不过它还具有浮点精度。
void Rotate(double radians, const CPoint& c, CPoint* vCtlPt, unsigned Cnt)
double sinAng = sin(radians);
double cosAng = cos(radians);
LDPoint constTerm( c.x - c.x * cosAng - c.y * sinAng,
c.y + c.x * sinAng - c.y * cosAng);
for (int i = Cnt-1; i>=0; --i)
{
vCtlPt[i] = (LDPoint( vCtlPt[i].x * cosAng + vCtlPt[i].y * sinAng,
-vCtlPt[i].x * sinAng + vCtlPt[i].y * cosAng) + constTerm).GetCPoint();
}
// Create Ellipse
// 创建椭圆
CRect rect; GetClientRect(&rect);
CPoint ellipsePts[13];
EllipseToBezier(ellipseR, ellipsePts);
// Rotate
// 旋转
Rotate(m_Radians, midPoint, ellipsePts, 13);
填充椭圆
当然,无论是不是旋转,四条贝塞尔曲线只完成了椭圆的轮廓。幸运的是,Win32路径功能可用于填充椭圆。你只在需要调用PolyBezier(...)来封闭路径。完成的路径是一笔画出的,而且能被让人满意的填充。假如有人觉得还不够,比如更特殊的填充,比如斜线、用户位图或不规则碎片等。这些能由SelectClipPath(...)来把剪贴区域设置到路径上来而获得。
dc.BeginPath();
dc.PolyBezier(ellipsePts);
dc.EndPath();
dc.StrokePath;
// or FillPath();
// or StrokeAndFillPath();
// or PathToRegion(dc.m_hDC);
//
//或者 FillPath();
//或者StrokeAndFillPath();
//或者PathToRegion(dc.m_hDC);
在Win95/8下宽的虚线或点椭圆轮廓。Win95/8只支持实体宽线。然而,虚线或点椭圆轮廓能容易的由一系列贝塞尔曲线段模拟。
用vc怎么画旋转(非线性)椭圆
最新推荐文章于 2023-04-01 11:20:44 发布