クリスマスリースを描く

こんにちは、きうちです!
久々に投稿します^^

今回はクリスマスということで、プログラムでクリスマスリースを描いてみたいと思います。

使用するのは例によってC#です。Windowsフォームアプリケーションとして作成します。

★ ★ ★

この季節になるとそこかしこで見かけますよね、クリスマスリース。

あれって円形をしているし、モールっぽいものを円状に並べればいけるんじゃね?と思っていました。

長らく思っていましたが、ついにプログラムにしてみました。

まずは基本の、円。

三角関数を使って描きます。

フォームを512×512にして、FormBorderStyleをFixedToolWindowにして、背景を黒にして・・・

描画はフォームのPaintイベントで。※以下は描画部分の抜粋です。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Black);

    DrawWreath(e.Graphics);
}

private void DrawWreath(Graphics g)
{
    double xx = -1, yy = -1, r = 30;

    for (int i = 0; i <= 360; i += 5) {
        double theta = i * Math.PI / 180;
        double x = Math.Cos(theta) * 200 + 245;
        double y = Math.Sin(theta) * 200 + 240;
        if (xx > 0)
        {
            g.DrawLine(Pens.AliceBlue, (float)xx, (float)yy, (float)x, (float)y);
        }
        xx = x;
        yy = y;
        DrawCircle(g, (float)x, (float)y, (float)r);
    }
}

private void DrawCircle(Graphics g, float x, float y, float r)
{
    g.DrawEllipse(Pens.AliceBlue, x - r, y - r, r * 2, r * 2);
}

そうするとこうなります。

そしたら、今度は各箇所に書いている円をモールっぽいものにします。

DrawCircleメソッドをそのように改造します。

やり方としては、各々の中心点から放射状にランダムに緑と白の線を伸ばします。

やってみたら白と緑の数を均等にするとバランスが悪かったので、緑2:白1にします。

また、白は二重線、緑は黒い線を隣に描きます。

private void DrawCircle(Graphics g, float x, float y, float r)
{
    for (int j = 0; j < 50; j++)
    {
        double theta2 = random.NextDouble() * Math.PI * 2;
        double x1 = x + Math.Cos(theta2) * r;
        double y1 = y + Math.Sin(theta2) * r;
        Pen color = new Pen[] { Pens.Green, Pens.Green, Pens.White }[j % 3];
        Pen color2 = new Pen[] { Pens.Black, Pens.Black, Pens.White }[j % 3];
        g.DrawLine(color, (float)x, (float)y, (float)x1, (float)y1);
        g.DrawLine(color2, (float)x + 1, (float)y, (float)x1 + 1, (float)y1);
    }
}

1個だけ描かせると

こんな感じ。

円形に並べると・・・

こう!

ほら、だいぶクリスマスリースっぽくなった^^

★ ★ ★

こうなると、なんかもう少し飾りが欲しくなりますね。

リボン付けましょうか。

というわけで、ちょっとこしらえてきました。

Inkscapeを使ってSVGを作りました。

SVGファイルを開いて、Pathのデータだけ持ってきて、ちょっとSVGパーサ(時間がなかったので手抜きですが)を書いて・・・

private string data = "M 18.898809,103.1875 9.8273807,90.525294 20.410715,76.351189 52.916666,70.303572 83.910712,89.958331 122.84226,69.925593 l 27.97024,5.669645 6.80357,13.040178 -6.04762,13.040174 -25.3244,6.4256 -35.15179,-5.66964 27.97024,24.19047 13.60714,24.19048 13.22917,53.67262 -12.09524,-6.80357 -9.82738,13.22916 -10.58333,-48.38095 -11.33929,-38.93154 -18.520832,-18.14286 -13.229168,15.49702 -9.827381,41.1994 -1.133929,49.13691 -11.339285,-11.33929 -14.363095,4.91369 9.071428,-54.05059 8.315478,-21.54464 22.300593,-27.21429 -32.127976,7.18155 z";

private List list = new List();

private void MakeRibbon()
{
    string[] ds = data.Split(' ');
    int mode = 0;
    float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
    float xmax = 0, ymax = 0, xmin = 999, ymin = 999;

    for (int i = 0; i < ds.Length; i++) {
        if (ds[i] == "M")
        {
            mode = 1;
        }
        else if (ds[i] == "l")
        {
            mode = 2;
        }
        else if (ds[i].IndexOf(",") > -1)
        {
            string[] xy = ds[i].Split(',');
            float x = float.Parse(xy[0]) * 2F;
            float y = float.Parse(xy[1]) * 1.5F;
            if (mode == 1)
            {
                x2 = x;
                y2 = y;
            }
            else if (mode == 2)
            {
                x2 = x1 + x;
                y2 = y1 + y;
            }
            x1 = x2;
            y1 = y2;
            list.Add(new PointF(x1 + 80, y1 - 80));
            if (xmin > x1 + 80) xmin = x1 + 80;
            if (xmax < x1 + 80) xmax = x1 + 80;
            if (ymin > y1 - 80) ymin = y1 - 80;
            if (ymax < y1 - 80) ymax = y1 - 80;
        }

    }
    list.Add(new PointF(list[0].X, list[0].Y));

    float w = xmax - xmin;
    float h = ymax - ymin;
    float halfW = w / 2F;
    float halfH = h / 2F;

    for (int i = 0; i < list.Count; i++)
    {
        list[i] = new PointF((list[i].X - halfW) / halfW * 200 + 110, (list[i].Y - halfH) / halfH * 100 + 80);
    }
}

これをコンストラクタから呼んでおきます。

public Form1()
{
    InitializeComponent();
    MakeRibbon();
}

それで、Paintイベントメソッドから、輪っかを描いた後にリボンを描くメソッドを呼び出します。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Black);

    DrawWreath(e.Graphics);
    DrawRibbon(e.Graphics);

}

private void DrawRibbon(Graphics g)
{
    g.FillPolygon(Brushes.Red, list.ToArray());
}

するとこうなります。

うーん・・・なんか、取って付けた感満載w

ちょっとサイズと色を変えながら重ねますか。
で、真ん中に円を描いて。

private List list2 = new List();
private List list3 = new List();

private void MakeRibbon()
{
    ...

    for (int i = 0; i < list.Count; i++)
    {
        list2.Add(new PointF((list[i].X - halfW) / halfW * 190 + 115, (list[i].Y - halfH) / halfH * 80 + 70));
        list3.Add(new PointF((list[i].X - halfW) / halfW * 180 + 120, (list[i].Y - halfH) / halfH * 60 + 60));
        list[i] = new PointF((list[i].X - halfW) / halfW * 200 + 110, (list[i].Y - halfH) / halfH * 100 + 80);
    }
}

private void DrawRibbon(Graphics g)
{
    g.FillPolygon(Brushes.Red, list.ToArray());
    g.FillPolygon(Brushes.White, list2.ToArray());
    g.FillPolygon(Brushes.Red, list3.ToArray());
    g.FillEllipse(Brushes.Red, 220, 10, 60, 80);
    g.FillEllipse(Brushes.White, 225, 15, 50, 70);
    g.FillEllipse(Brushes.Red, 230, 20, 40, 60);
}

そうすると。

・・・まだなんかごまかし切れていない気がしますが、まあよしとしましょう。

じゃあ、あとは字を入れてみようかな。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Black);

    DrawWreath(e.Graphics);
    DrawRibbon(e.Graphics);
    DrawString(e.Graphics);
}

private Font font = new Font("Comic Sans MS", 64);

private void DrawString(Graphics g)
{
    g.DrawString("Merry", font, Brushes.Black, 104, 204);
    g.DrawString("Merry", font, Brushes.Yellow, 100, 200);
    g.DrawString("Christmas!", font, Brushes.Black, 24, 304);
    g.DrawString("Christmas!", font, Brushes.Yellow, 20, 300);
}

うん、いいんじゃないですかね♪

本当は字を光らせたりBGM流したりしたいけど・・・それはまた機会があれば!・・・って来年か!?w

ではまた!

P.S. 完成したソースはまた後でGitHubに載せておきます^^

★ ★ ★

2024/12/24夜 追記

ソースをGitHubに載せました。こちら→ここをクリック
リボンの位置がもう少し左だなーと思ったので、少し左に寄せてあります。
VisualStudio2022で作成しています。

あと本文の方ですが、最初のリボンを描くメソッドが抜けていたので追加しました。