パス
パスは直線や曲線を組み合わせて図形を作成する機能になります。
MFC、SVG ともに似たような描画方法となりますが、曲線の判定方法や円弧の描き方に違いがありますので差分を含めながらコードを書きます。
MFC のパス
MFC の GDI+ にて使用されるパスデータは以下のサイトに記載があります。
https://learn.microsoft.com/ja-jp/windows/win32/gdiplus/-gdiplus-paths-about
https://learn.microsoft.com/ja-jp/windows/win32/api/gdiplusenums/ne-gdiplusenums-pathpointtype
この中で SVG と共通しているのは以下の通りです。
- 開始点の指定
- ライン
- 三次ベジェ曲線
- パスクローズ
SVG でパスとして二次ベジェ曲線と円弧が含まれていますが、MFC には二次ベジェ曲線と円弧が含まれていません。
MFC の GDI+ のパスには様々な曲線や円なども入力することができるようになっています。しかし、パスに入力した段階で全てベジェ曲線に近似されてしまいます。このため、曲線は三次ベジェ曲線のみとなっています。
GDI+ では円弧を表示する方法が別途用意されています。こちらについては別の項目として解説します。
本稿では上記の4種に対応したパスのコードを作成します。
PathData の SVG 出力
Gdiplus::Graphics::Path を Gdiplus::Graphics::PathData に出力した後、SVG 出力するものとします。
座標は常に絶対座標となりますので SVG でのパスの記載は全て英語大文字の記載になることに注意してください。
それぞれのポイントとポイントタイプは path タグの d 属性値にまとめて記載します。
class SvgImage
{
public:
+ void AddPathData(Gdiplus::PathData* pathData, int nDepth = 0);
}
void SvgImage::AddPathData(Gdiplus::PathData* pathData, int nDepth)
{
SvgTag *svgPath = new SvgTag();
svgPath->m_strName = L"path";
svgPath->m_nDepth = nDepth + 1;
svgPath->m_bOne = TRUE;
// pathを作る
CString csStr;
CString csPath = L"";
int nCount = 0;
int startIndex = 0;
while (nCount < pathData->Count)
{
csStr = L"";
BYTE bTypes = pathData->Types[nCount];
if (bTypes == PathPointType::PathPointTypeStart)
{
// if (nCount > 0)
// {
// // パスクローズ処理
// double dValueX = pathData->Points[startIndex].X - m_rectAll.left;
// double dValueY = m_rectAll.bottom - pathData->Points[startIndex].Y;
// csStr.Format(_T(" L %lf %lf"), dValueX, dValueY);
// }
// 開始ポイント
double dValueX = pathData->Points[nCount].X - m_rectAll.left;
double dValueY = m_rectAll.bottom - pathData->Points[nCount].Y;
csStr.Format(_T(" M %lf %lf"), dValueX, dValueY);
// クローズする際の初期一の保持
startIndex = nCount;
nCount++;
}
else if (bTypes == PathPointType::PathPointTypeLine)
{
double dValueX = pathData->Points[nCount].X - m_rectAll.left;
double dValueY = m_rectAll.bottom - pathData->Points[nCount].Y;
csStr.Format(_T(" L %lf %lf"), dValueX, dValueY);
nCount++;
}
else if ( ((nCount + 2) < pathData->Count) && (pathData->Types[nCount] == PathPointType::PathPointTypeBezier) && (pathData->Types[nCount + 1] == PathPointType::PathPointTypeBezier))
{
// 三次ベジェ曲線
csStr.Format(_T(" C %lf %lf %lf %lf %lf %lf"),
pathData->Points[nCount].X - m_rectAll.left, m_rectAll.bottom - pathData->Points[nCount].Y,
pathData->Points[nCount + 1].X - m_rectAll.left), m_rectAll.bottom - pathData->Points[nCount + 1].Y,
pathData->Points[nCount + 2].X - m_rectAll.left, m_rectAll.bottom - pathData->Points[nCount + 2].Y);
nCount += 3;
}
else if (bTypes == PathPointTypeCloseSubpath)
{
double dValueX = pathData->Points[nCount].X - m_rectAll.left;
double dValueY = m_rectAll.bottom - pathData->Points[nCount].Y;
csStr.Format(_T(" L %lf %lf"), dValueX, dValueY);
nCount++;
}
else
{
nCount++;
}
csPath += csStr;
}
svgPath->m_arrAttr.Add(L"d");
svgPath->m_arrAttrValue.Add(csPath);
m_arrayTag.Add(svgPath);
m_nDrawCount++;
}
入力された PathData の数だけ処理を繰り返します。
nCount は参照するデータが読み込む際にとびとびになってしまうため、for 文を使うループができないため、while を使用しています。
// pathを作る
CString csStr;
CString csPath = L"";
int nCount = 0;
int startIndex = 0;
while (nCount < pathData->Count)
{
追加する文字列を初期化し、参照する PathData のポイントタイプを取得しておきます。
csStr = L"";
BYTE bTypes = pathData->Types[nCount];
ポイントを移動するパターンの処理になります。
コメントアウトしている部分はパスクローズがないパターンを考慮したものとなります。クローズがされている場合は処理に追加する必要はありません。ストロークを綺麗に閉じる処理のため、必要な場合は追加してください。
SVG では
- M x,y
の形で記載します。
if (bTypes == PathPointType::PathPointTypeStart)
{
// if (nCount > 0)
// {
// // パスクローズ処理
// double dValueX = pathData->Points[startIndex].X - m_rectAll.left;
// double dValueY = m_rectAll.bottom - pathData->Points[startIndex].Y;
// csStr.Format(_T(" L %lf %lf"), dValueX, dValueY);
// }
// 開始ポイント
double dValueX = pathData->Points[nCount].X - m_rectAll.left;
double dValueY = m_rectAll.bottom - pathData->Points[nCount].Y;
csStr.Format(_T(" M %lf %lf"), dValueX, dValueY);
// クローズする際の初期一の保持
startIndex = nCount;
nCount++;
}
ラインを作成する処理です。
SVG では
- L x,y
の形で記載します。
連続するラインの場合は "L" が必要ないのですが、明示するため逐次表示する形をとっています。
必要なければ "L" は追記しなくてもかまいません。
else if (bTypes == PathPointType::PathPointTypeLine)
{
double dValueX = pathData->Points[nCount].X - m_rectAll.left;
double dValueY = m_rectAll.bottom - pathData->Points[nCount].Y;
csStr.Format(_T(" L %lf %lf"), dValueX, dValueY);
nCount++;
}
三次ベジェ曲線の処理です。
念のため、三次ベジェ曲線かどうか最後のポイントまで確認してから処理を開始しています。
SVG では
- C x1,y1 x2,y2 x3,y3
(x1,y1):制御点1
(x2,y2):制御点2
(x3,y3):終点
の形で記載します。
else if ( ((nCount + 2) < pathData->Count) && (pathData->Types[nCount] == PathPointType::PathPointTypeBezier) && (pathData->Types[nCount + 1] == PathPointType::PathPointTypeBezier))
{
// 三次ベジェ曲線
csStr.Format(_T(" C %lf %lf %lf %lf %lf %lf"),
pathData->Points[nCount].X - m_rectAll.left, m_rectAll.bottom - pathData->Points[nCount].Y,
pathData->Points[nCount + 1].X - m_rectAll.left), m_rectAll.bottom - pathData->Points[nCount + 1].Y,
pathData->Points[nCount + 2].X - m_rectAll.left, m_rectAll.bottom - pathData->Points[nCount + 2].Y);
nCount += 3;
}
パスクローズの処理です。
SVG は "Z" を使用することでパスクローズとなりますが、ブラウザによりそこでパスの読み込みが終わってしまうことがあるため、あえてラインでクローズを行います。
else if (bTypes == PathPointTypeCloseSubpath)
{
double dValueX = pathData->Points[nCount].X - m_rectAll.left;
double dValueY = m_rectAll.bottom - pathData->Points[nCount].Y;
csStr.Format(_T(" L %lf %lf"), dValueX, dValueY);
nCount++;
}
上記のパターン以外の処理です。
カウントアップだけを行います。
else
{
nCount++;
}
属性値に追加します。
csPath += csStr;