MFC と SVG の円弧の描き方の違い
MFC では円を描くのと同じ要領で四角形で範囲を指定し、円弧の開始角度と終点までの間の角度を指定することで描画できます。
一方で、SVG は現在の点から点 (x, y) へ楕円形の弧を描く形となります。
円の X 軸の半径と Y 軸の半径に、x-axis-rotation で示した角度の分を描きます。
このとき、円の中心点は large-arc-flag と sweep-flag を含めた値から自動計算されます。
SVG の円弧は SVG 仕様の以下に記載があります
https://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataEllipticalArcCommands
https://triple-underscore.github.io/SVG11/paths.html#PathDataEllipticalArcCommands
(日本語版)
演算式は SVG 仕様の以下に記載があります。
https://www.w3.org/TR/2011/REC-SVG11-20110816/implnote.html#ArcImplementationNotes
https://triple-underscore.github.io/SVG11/implnote.html#ArcImplementationNotes
(日本語版)
MFC から SVG への出力はこれらの計算と上下反転(角度反転)を考慮しながら演算結果を記載しなければなりません。
楕円
楕円の処理は以下の通りです。
class SvgImage
{
public:
+ void AddArc(double dCenterX, double dCenterY, double dA, double dB, double dStartArc, double dSweepArc, int nDepth = 0);
}
void SvgImage::AddArc(double dCenterX, double dCenterY, double dA, double dB, double dStartArc, double dSweepArc, int nDepth)
{
SvgTag *svgPath = new SvgTag();
svgPath->m_tagType = SVG_TAG_PATH;
svgPath->m_strName = L"path";
svgPath->m_nDepth = nDepth + 1;
svgPath->m_bOne = TRUE;
// pathを作る
CString csPath = L"";
double dStart = dStartArc;
dStart = dStart > 180.0 ? dStart - 360.0 : dStart;
dStart = dStart < -180.0 ? 360.0 + dStart : dStart;
// 楕円の傾き φ は 0 に固定
double dStartX = dA * cos(PI * dStart / 180.0) + dCenterX;
double dStartY = dB * sin(PI * dStart / 180.0) + dCenterY;
double dEndArc = dStart + dSweepArc;
dEndArc = dEndArc > 180.0 ? (dStart + dSweepArc) - 360.0 : dEndArc;
dEndArc = dEndArc < -180.0 ? 360.0 + dEndArc : dEndArc;
double dEndX = dA * cos(PI * dEndArc / 180.0) + dCenterX;
double dEndY = dB * sin(PI * dEndArc / 180.0) + dCenterY;
int nOver180 = abs(dSweepArc) > 180 ? 1 : 0;
int nSweepFlag = (dSweepArc) < 0 ? 1 : 0;
csPath.Format(_T("M %lf %lf A %lf %lf 0 %d %d %lf %lf"),
dStartX - m_rectAll.left, m_rectAll.bottom - dStartY,
dA, dB,
nOver180, nSweepFlag,
dEndX - m_rectAll.left, m_rectAll.bottom - dEndY);
svgPath->m_arrAttr.Add(L"d");
svgPath->m_arrAttrValue.Add(csPath);
m_nDrawCount++;
}
スタートする角度を -180 ~ 180 度におさめます。
double dStart = dStartArc;
dStart = dStart > 180.0 ? dStart - 360.0 : dStart;
dStart = dStart < -180.0 ? 360.0 + dStart : dStart;
中心点 c と X 軸とY軸の半径 r を入れた頂点は以下の計算式であらわされます。
x = cos(\phi)r_xcos(\theta) - sin(\phi)r_ysin(\theta)+c_x
y = sin(\phi)r_xcos(\theta) + cos(\phi)r_ysin(\theta) + c_y
このとき、楕円の傾きφが必要なければ(φ = 0のとき)以下の計算式になります。
x = r_xcos(\theta)+c_x
y = r_ysin(\theta) + c_y
コード内は φ = 0 固定として計算しました。
楕円の半径 r は (dA,dB) としています。
// 楕円の傾き φ は 0 に固定
double dStartX = dA * cos(PI * dStart / 180.0) + dCenterX;
double dStartY = dB * sin(PI * dStart / 180.0) + dCenterY;
double dEndArc = dStart + dSweepArc;
dEndArc = dEndArc > 180.0 ? (dStart + dSweepArc) - 360.0 : dEndArc;
dEndArc = dEndArc < -180.0 ? 360.0 + dEndArc : dEndArc;
double dEndX = dA * cos(PI * dEndArc / 180.0) + dCenterX;
double dEndY = dB * sin(PI * dEndArc / 180.0) + dCenterY;
表示角度が 180 度を越えているかのフラグと回転角がどちらの方向を向いているかのフラグを設定します。
int nOver180 = abs(dSweepArc) > 180 ? 1 : 0;
int nSweepFlag = (dSweepArc) < 0 ? 1 : 0;
開始点を "M" で配置して "A" で円弧情報を追記します。
csPath.Format(_T("M %lf %lf A %lf %lf 0 %d %d %lf %lf"),
dStartX - m_rectAll.left, m_rectAll.bottom - dStartY,
dA, dB,
nOver180, nSweepFlag,
dEndX - m_rectAll.left, m_rectAll.bottom - dEndY);