图形旋转和歪斜的椭圆
介绍
窗口中的矩形,带圆角的矩形和椭圆只能由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
//使用贝塞尔曲线创建点,模拟椭圆
voidEllipseToBezier(CRect& r, CPoint* cCtlPt)
{
// MAGICAL CONSTANT to map ellipse to beziers
// 2/3*(sqrt(2)-1)
// 把椭圆映射为贝塞尔曲线的常量 2/3*(sqrt(2)-1)
const double EToBConst = 0.[1**********]54;
CSizeoffset((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));
CPointcentre((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;
}
Rotation of the Ellipse can be accomplished using code similar to:
使用与下面近似的代码可完成椭圆的旋转
// LDPoint is an equivalent type to CPoint but with floating point precision
// LDPoint是一个和CPoint 相当的类型,不过它还具有浮点精度。
void Rotate(double radians, constCPoint& c, CPoint* vCtlPt, unsigned Cnt) {
doublesinAng = sin(radians);
doublecosAng = cos(radians);
LDPointconstTerm(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
// 创建椭圆
CRectrect; GetClientRect(&rect);
CPointellipsePts[13];
EllipseToBezier(ellipseR, ellipsePts);
// Rotate
// 旋转
Rotate(m_Radians, midPoint, ellipsePts, 13);
Filled Ellipses
Of course, four bezier curves together only make up the outline of an ellipse, whether rotated or not. Thankfully, Win32 Path functions are there for filled ellipses. One only needs to enclose the PolyBezier(...) call in a Path Bracket. The resulting path can be stroked and filled to one's satisfaction. If one is feeling adventurous, further special fills like gradients, custom bitmaps or fractals etc. can be achieved by first setting the clipping region to the path via SelectClipPath(...).
填充椭圆
当然,无论是不是旋转,四条贝塞尔曲线只完成了椭圆的轮廓。幸运的是, 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只支持实体宽线。然而,虚线或点椭圆轮廓能容易的由一系列贝塞尔曲
线段模拟。
图形旋转和歪斜的椭圆
介绍
窗口中的矩形,带圆角的矩形和椭圆只能由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
//使用贝塞尔曲线创建点,模拟椭圆
voidEllipseToBezier(CRect& r, CPoint* cCtlPt)
{
// MAGICAL CONSTANT to map ellipse to beziers
// 2/3*(sqrt(2)-1)
// 把椭圆映射为贝塞尔曲线的常量 2/3*(sqrt(2)-1)
const double EToBConst = 0.[1**********]54;
CSizeoffset((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));
CPointcentre((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;
}
Rotation of the Ellipse can be accomplished using code similar to:
使用与下面近似的代码可完成椭圆的旋转
// LDPoint is an equivalent type to CPoint but with floating point precision
// LDPoint是一个和CPoint 相当的类型,不过它还具有浮点精度。
void Rotate(double radians, constCPoint& c, CPoint* vCtlPt, unsigned Cnt) {
doublesinAng = sin(radians);
doublecosAng = cos(radians);
LDPointconstTerm(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
// 创建椭圆
CRectrect; GetClientRect(&rect);
CPointellipsePts[13];
EllipseToBezier(ellipseR, ellipsePts);
// Rotate
// 旋转
Rotate(m_Radians, midPoint, ellipsePts, 13);
Filled Ellipses
Of course, four bezier curves together only make up the outline of an ellipse, whether rotated or not. Thankfully, Win32 Path functions are there for filled ellipses. One only needs to enclose the PolyBezier(...) call in a Path Bracket. The resulting path can be stroked and filled to one's satisfaction. If one is feeling adventurous, further special fills like gradients, custom bitmaps or fractals etc. can be achieved by first setting the clipping region to the path via SelectClipPath(...).
填充椭圆
当然,无论是不是旋转,四条贝塞尔曲线只完成了椭圆的轮廓。幸运的是, 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只支持实体宽线。然而,虚线或点椭圆轮廓能容易的由一系列贝塞尔曲
线段模拟。