問題描述:windows下,GDI可以調用Ellipse直接繪製一個水平或垂直方向的橢圓,但是無法按照一定的角度自由旋轉。
解決方法:
1. 使用世界座標系。通過旋轉世界座標系,達到旋轉橢圓的目的。需要使用到SetWorldTransform等一系列的函數。
關於這些函數的使用方法參見如下資料:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd145104%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd145174(v=vs.85).aspx
簡單介紹一下旋轉橢圓時,如何計算世界座標系的轉換。
橢圓的中心點(x0,y0),從點(x1,y1)旋轉到點(x2,y2),旋轉角度爲q(單位爲弧度)。
x2 = cos(q)*(x1-x0) – sin(q)*(y1-y0) + x0;
y2 = sin(q)*(x1-x0) + cos(q)*(y1-y0) + y0;
當使用世界座標系時,從點(x,y)變換到點(x', y')時,SetWorldTransform需要一個XFORM的參數,其成員的計算關係爲:
x’ = x * eM11 + y * eM12 + eDx;
y’ = x * eM12 + y * eM22 + eDy;
因此可以得出:
xform.eM11 = cos(q);
xform.eM12 = sin(q);
xform.eM21 = -sin(q);
xform.eM22 = cos(q);
xform.eDx = x0 – cos(q) * x0 + sin(q) * y0;
xform.eDy = y0 – cos(q) * y0 - sin(q) * x0;
此外,GDI+把上面的一系列世界座標系的函數封裝到了Graphics類裏了(頭文件爲GdiPlusGraphics.h)。
方法缺陷:win95/98不支持世界座標系,有平臺限制。
2. 使用4條貝賽爾曲線來模擬繪製橢圓,而貝塞爾曲線是可以旋轉的。
從一個水平或垂直方向的橢圓(未旋轉的)的外接邊界矩形,計算出13個控制點(0-12)用以控制4條貝塞爾曲線。
下面的計算方法是以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)
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
// 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
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);
void EllipseToBezier(CRect& r, CPoint* cCtlPt)
{
// MAGICAL CONSTANT to map ellipse to beziers
// 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
// 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
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);