图形旋转和歪斜的椭圆  作者 codeguru 
关键字 图形 椭圆 旋转
原作者姓名 codeguru 正文
图形旋转和歪斜的椭圆介绍  窗口中的矩形,带圆角的矩形和椭圆只能由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    //              2/3*(sqrt(2)-1)    // 把椭圆映射为贝塞尔曲线的常量 2/3*(sqrt(2)-1)    const double EToBConst =    0.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;}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, 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);Filled EllipsesOf 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只支持实体宽线。然而,虚线或点椭圆轮廓能容易的由一系列贝塞尔曲线段模拟。 

解决方案 »

  1.   

    用GDI+使文字轻松旋转
    --------------------------------------------------------------------------------
    下载本文代码
    见资源
     
    在老式的Windows图形设备接口中制作旋转文字会是一件痛苦费力的工作,但在.NET中它会变得很简单。
    by Bill Wagner
     
    图1.在.NET中旋转文字 
    还记得在典型的Windows程序中用GDI(一种老式的图形设备API)来实现文字旋转是件多么麻烦的事吗?首先你得设置旋转字体 - 它只能在几种平台下实现。而当你想要将文字居中显示的时候会更麻烦,因为显示该段文字大小的函数对旋转字体不起作用。你必须将文字水平放置,再测量其大小,然后建一个新的旋转字体,最后把它画出来。 使用GDI+会大大地简化操作过程。Microsoft在新的图形设备接口实现中创建了一种一致的图形模型,它通过一套C++类来实现。我写了一个简单的例子来说明用GDI+实现文字旋转是多么地容易。在例子中我把文字画在窗体的中间位置,它能够以任何角度进行旋转。GDI+中的两个特性使绘制过程变得简单:图形平移(graphics translations)和图形旋转(graphics rotations)。要将文字居中显示,只需转换坐标系(coordinates)使原点(origin)置于窗体的中间位置:Size sz = MyWindow.Size;
    Point Middle = new Point (sz.Width / 2, 
       sz.Height / 2);
    e.Graphics.TranslateTransform 
       (Middle.X, Middle.Y); 
    通过继续变换来实现简单的旋转: e.Graphics.RotateTransform (_angle);
     
    当位置调整好以后,你就可以绘制文字了: StringFormat format = new StringFormat 
       (StringFormatFlags.NoClip);
    format.Alignment = 
       StringAlignment.Center;
    format.LineAlignment = 
       StringAlignment.Center;
    e.Graphics.DrawString 
       ("A simple TextString ", f, 
       Brushes.Black, 0, 0, format);
     
    StringFormat对象控制着该字符串的对齐方式。这里,我选中了居中位置。现在你能够以任何角度旋转字体了。在运行这段代码时,你会发现当它旋转到正45度角的时候,看起来却像是负45度角(见图1)。记住在GDI中标准的坐标系是y坐标向下的,在这种坐标系上,正角度的旋转是沿水平轴顺时针旋转的。从这个例子可以看出,使用.NET的GDI+ API使得图形处理变得更简单更一致。点此下载源代码并修改转换角度。你会发现你能够以任何角度在任何平台绘制文字,这同样适用于以任何角度旋转任何其他形状的图形。