【C#】市松模様を描画する（Bitmap）

# はじめに

これまでにC#を使った画像処理の記事を2つほど書きましたが、画像の描画について「1ピクセルずつ描画する」というレベルに留まっていました。
そこで今回は「複数の図形を一定のパターンで描画する」という、これまでとは違ったアプローチで画像を生成してみました。

* [【C#】画像生成の基本（Bitmap）](https://qiita.com/nkojima/items/ab776e03ae80a2b2e3bb)
* [【C#】2つの画像の差分を得る（Bitmap）](https://qiita.com/nkojima/items/1fb901c13f9707808bc0)

# コード

* 処理は大まかに以下のようになっています。
 1. キャンバスとなるBitmapオブジェクトを作り、背景色を設定する。
 1. 前景色で塗りつぶされた正方形を一定間隔で描画する。
* `キャンバスサイズ÷格子サイズ` が割り切れない場合には、キャンバス外にはみ出る部分は描画されないので「キャンバスサイズを超えて格子柄が描画される」ということはありません。

```c#:IchimatsuDrawer.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpStudy.Image
{
    /// <summary>
    /// 市松模様を描くクラス。
    /// </summary>
    public class IchimatsuDrawer
    {
        /// <summary>
        /// 市松模様を描画するキャンバス。
        /// </summary>
        private Bitmap bmp;

        /// <summary>
        /// 市松模様の「格子」のサイズ。
        /// </summary>
        private int gridSize;

        /// <summary>
        /// 背景色。
        /// デフォルトは白。
        /// </summary>
        private Brush backgroundColor;

        /// <summary>
        /// 前景色。
        /// デフォルトは黒。
        /// </summary>
        private Brush foregroundColor;

        /// <summary>
        /// コンストラクタ。
        /// グリッド（格子）のサイズがキャンバスサイズを超える時は例外を発生させる。
        /// </summary>
        /// <param name="canvasWidth">キャンバスの幅（px）</param>
        /// <param name="canvasHeight">キャンバスの高さ（px）</param>
        /// <param name="gridSize">グリッド（格子）のサイズ（px）</param>
        public IchimatsuDrawer(int canvasWidth = 300, int canvasHeight = 200, int gridSize = 20)
        {
            if (canvasHeight < gridSize || canvasWidth < gridSize)
            {
                new Exception("グリッド（格子）のサイズが大きすぎます。");
            }
            this.bmp = new Bitmap(canvasWidth, canvasHeight);
            this.gridSize = gridSize;
            this.backgroundColor = Brushes.White;
            this.foregroundColor = Brushes.Black;
        }

        /// <summary>
        /// 背景色をセットする。
        /// </summary>
        /// <param name="backgroundColor">背景色。Brushesクラスのプロパティを指定する。</param>
        public void SetBackgroundColor(Brush backgroundColor)
        {
            this.backgroundColor = backgroundColor;
        }

        /// <summary>
        /// 前景色をセットする。
        /// </summary>
        /// <param name="foregroundColor">前景色。Brushesクラスのプロパティを指定する。</param>
        public void SetForegroundColor(Brush foregroundColor)
        {
            this.foregroundColor = foregroundColor;
        }

        /// <summary>
        /// 市松模様を描画する。
        /// </summary>
        /// <param name="filePath">出力先のファイルパス。</param>
        public void Draw(string filePath)
        {
            using (Graphics g = Graphics.FromImage(bmp))
            {
                // キャンバスの背景色を設定する。
                g.FillRectangle(this.backgroundColor, g.VisibleClipBounds);

                // 縦・横の格子柄の繰り返し回数を求める。
                // 繰り返し回数に余りが出る場合は格子柄がはみ出る形になるが、キャンバス外は描画されないので仕上がりの画像には影響しない。
                double widthCount = (double)this.bmp.Width / (double)this.gridSize;
                double heightCount = (double)this.bmp.Height / (double)this.gridSize;

                for (int i=0; i< widthCount; i++)
                {
                    for (int j=0; j< heightCount; j++)
                    {
                        if ((i + j) % 2 == 0)
                        {
                            // 前景色で塗りつぶされた長方形（格子柄）を描画する。
                            g.FillRectangle(this.foregroundColor, (i * this.gridSize), (j * this.gridSize), this.gridSize, this.gridSize);
                        }
                    }
                }
            }

            // 画像をPNG形式で保存する。
            bmp.Save(filePath, System.Drawing.Imaging.ImageFormat.Png);
        }
    }
}
```

# 実行用のコード

```c#:IchimatsuDrawerTest.cs
using System;
using CSharpStudy.Image;
using System.Drawing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CSharpStudyTest.Image
{
    [TestClass]
    public class IchimatsuDrawerTest
    {
        private const string BITMAP_PATH1 = @"C:\Users\NKOJIMA\source\repos\CSharpStudy\CSharpStudy\Image\ichimatsu1.png";

        private const string BITMAP_PATH2 = @"C:\Users\NKOJIMA\source\repos\CSharpStudy\CSharpStudy\Image\ichimatsu2.png";

        /// <summary>
        /// キャンバスサイズが格子サイズで割り切れるパターン。
        /// </summary>
        [TestMethod]
        public void TestMethod1()
        {
            IchimatsuDrawer drawer = new IchimatsuDrawer();
            drawer.SetBackgroundColor(Brushes.DarkGreen);
            drawer.SetForegroundColor(Brushes.Black);
            drawer.Draw(BITMAP_PATH1);
        }

        /// <summary>
        /// キャンバスサイズが格子サイズで割り切れないパターン。
        /// </summary>
        [TestMethod]
        public void TestMethod2()
        {
            IchimatsuDrawer drawer = new IchimatsuDrawer(310, 210);
            drawer.SetBackgroundColor(Brushes.DarkGreen);
            drawer.SetForegroundColor(Brushes.Black);
            drawer.Draw(BITMAP_PATH2);
        }
    }
}
```

# 生成された画像

* 最近一大ブームを起こしたキャラクターの着物の柄を真似してみました。

## キャンバスサイズが格子サイズで割り切れるパターン

![ichimatsu1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/539c52f4-4241-20c9-06d6-13612a7fda6c.png)

## キャンバスサイズが格子サイズで割り切れないパターン

![ichimatsu2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/b63aa884-1895-7ba8-9b27-1d6122c7ece8.png)

# まとめ

* 長方形（正方形）、円などは、.NETで用意されているメソッドを使えば簡単に描画できます。
 * 多角形については、全ての頂点の座標を指定しないといけないので少々勝手が違いますが...