`
helpbs
  • 浏览: 1172117 次
文章分类
社区版块
存档分类
最新评论

用vc怎么画旋转(非线性)椭圆

 
阅读更多

介绍
窗口中的矩形,带圆角的矩形和椭圆只能由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只支持实体宽线。然而,虚线或点椭圆轮廓能容易的由一系列贝塞尔曲线段模拟。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics