【C#】n乗根の計算

n乗根の計算で、例えば8の3乗根( \(\sqrt [ 3 ]{ 8 } =2\) )を計算するにはMathクラスのPowメソッドを用いて

 

Math.Pow(8.0, 1.0 / 3.0);

 

と、すれば答え  が求められます。

しかし、負の値のn乗根、例えば \(\sqrt [ 3 ]{ -8 }\)、を求めようと

 

Math.Pow(-8.0, 1.0 / 3.0);

 

とすると、答えは -2 を期待するところですが、C#ではNaNとなり、答えが求まりません。

しかし、負の値であっても、奇数乗根の場合は答えを求めたい。

 

という事で、こんなメソッドを作ってみました↓

private double NthRoot(double x, int n) 
{ 
	if (x >= 0) 
	{ 
		// 通常のn乗根 
		return Math.Pow(x, 1.0 / (double)n); 
	} 
	else 
	{ 
		if ((n % 2) != 0) 
		{ 
			// 奇数乗根の場合 
			return -Math.Pow(-x, 1.0 / (double)n); 
		} 
		else 
		{ 
			// 偶数乗根の場合 
			return double.NaN; 
		} 
	} 
}

(参考)Math.Powメソッド

https://msdn.microsoft.com/ja-jp/library/system.math.pow(v=vs.110).aspx

 

C#へ戻る

【C#】アンチエイリアスの設定

アンチエイリアス(antialias)とは、特に斜めの線を描画した時にギザギザになるのをなめらかにする処理ですが、C#ではGraphicsクラスのSmoothingModeプロパティSystem.Drawing.Drawing2D.SmoothingMode列挙型で指定することができます。

 

【指定例】

e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

 

【アンチエイリアス処理なし】

 

【アンチエイリアス処理あり】

 

指定するSmoothingMode列挙型ごとの描画結果を以下に示します。

 

【SmoothingMode.None】

 

【SmoothingMode.Default】

 

【SmoothingMode.HighSpeed】

 

【SmoothingMode.AntiAlias】

 

【SmoothingMode.HighQuality】

 

補足

このアンチエイリアスの設定は、線や文字を描画する際の設定となります。
画像を描画する際のギザギザを滑らかにしたい場合は、補間モード(InterpolationMode)となります。
この補間モードについては、下記ページを参照ください。

【C#】補間モード(InterpolationMode)の設定

 

画像処理のためのC#へ戻る

【C#】寸法線の描画

GraphicsPathを使うことで、文字を任意角度で表示することが出来るのを知った元メカ屋な私。

これは寸法線の描画に使えそう!

と思い、寸法線の描画部分をクラスにまとめたものを作成してみました。

 

実行画面

 

寸法線描画のクラス↓

public class Dimension
{
    ///<summary>
    /// 線の色を取得設定します。
    /// </summary>
    public static Color LineColor { get; set; } = Color.Green;

    /// <summary>
    /// 文字の色を取得設定します。
    /// </summary>
    public static Color TextColor { get; set; } = Color.DeepSkyBlue;

    /// <summary>
    /// フォントの名前を取得設定します。
    /// </summary>
    public static FontFamily Family { get; set; } = new FontFamily("Arial");

    /// <summary>
    /// フォントのスタイルを取得設定します。
    /// </summary>
    public static FontStyle Style { get; set; } = FontStyle.Regular;

    /// <summary>
    /// 文字のサイズ(emスクエア)を取得設定します。
    /// </summary>
    public static float EmSize { get; set; } = 18.0f;

    /// <summary>
    /// 文字の書式設定を取得設定します。
    /// </summary>
    public static StringFormat Format { get; set; } = new StringFormat();

    /// <summary>
    /// 寸法値の表示位置の中心からのズレを取得設定します。
    /// </summary>
    public static float TextOffsetX { get; set; } = 0.0f;

    /// <summary>
    /// 寸法値の表示位置の寸法線からの距離を取得設定します。
    /// </summary>
    public static float TextOffsetY { get; set; } = 8.0f;

    /// <summary>
    /// 寸法線の線幅を取得設定します。
    /// </summary>
    public static float LineWidth { get; set; } = 1f;

    /// <summary>
    /// 矢印の大きさを取得設定します。
    /// </summary>
    public static float ArrowSize { get; set; } = 8f;


    /// <summary>
    /// 寸法線の描画
    /// </summary>
    /// <param name="g">描画先のGraphicsオブジェクトを指定します。</param>
    /// <param name="StartPoint">寸法を引き出す位置を指定します。(開始点側)</param>
    /// <param name="EndPoint">寸法を引き出す位置を指定します。(終了点側)</param>
    /// <param name="Offset">指定した点から寸法線を表示するまでの距離を指定します。</param>
    /// <param name="Text">寸法値に表示する文字を指定します。</param>
    public static void DrawDimension(Graphics g, PointF StartPoint, PointF EndPoint, float Offset, string Text)
    {
        // 2点間の中心座標
        PointF center = new PointF((StartPoint.X + EndPoint.X) / 2f, (StartPoint.Y + EndPoint.Y) / 2f);
        // 文字の回転角度
        float thRad = (float)Math.Atan2(EndPoint.Y - StartPoint.Y, EndPoint.X - StartPoint.X);  // ラジアン
        float th = thRad * 180.0f / (float)Math.PI;

        // パスの作成
        var pathText = new System.Drawing.Drawing2D.GraphicsPath();

        //////////////////////////////////////////////////////////////////
        // 寸法値の描画
        pathText.AddString(
                    Text,
                    Family,
                    (int)Style,
                    EmSize,
                    new PointF(0, 0),
                    Format);

        // 文字の領域取得
        RectangleF rect = pathText.GetBounds();

        // アフィン変換行列の計算
        var mat = new System.Drawing.Drawing2D.Matrix();

        // いったん文字を原点へ移動(文字領域の中心下側が基準)
        mat.Translate(-rect.Width / 2f, -rect.Height, System.Drawing.Drawing2D.MatrixOrder.Append);
        // オフセット分の移動(Y方向は逆に移動する)
        mat.Translate(TextOffsetX, -TextOffsetY - Offset, System.Drawing.Drawing2D.MatrixOrder.Append);
        // 寸法線に合わせた回転
        mat.Rotate(th, System.Drawing.Drawing2D.MatrixOrder.Append);
        // 表示位置まで移動
        mat.Translate(center.X, center.Y, System.Drawing.Drawing2D.MatrixOrder.Append);

        // パスをアフィン変換
        pathText.Transform(mat);

        // 寸法値用ブラシの作成
        var brushText = new SolidBrush(TextColor);

        // 描画
        g.FillPath(brushText, pathText);

        //////////////////////////////////////////////////////////////////
        // 寸法補助線の描画

        // 寸法線用ペンの作成
        var penLine = new Pen(LineColor, LineWidth);

        float lineLength = Offset + 5f; // 少し飛び出させる

        // StartPoint側の描画
        var StartPointDst = new PointF(
            StartPoint.X + lineLength * (float)Math.Cos(thRad - Math.PI / 2.0),
            StartPoint.Y + lineLength * (float)Math.Sin(thRad - Math.PI / 2.0)
            );
        g.DrawLine(penLine, StartPoint, StartPointDst);

        // EndPoint側の描画
        var EndPointDst = new PointF(
            EndPoint.X + lineLength * (float)Math.Cos(thRad - Math.PI / 2.0),
            EndPoint.Y + lineLength * (float)Math.Sin(thRad - Math.PI / 2.0)
            );
        g.DrawLine(penLine, EndPoint, EndPointDst);

        //////////////////////////////////////////////////////////////////
        // 寸法線(矢印)の描画
        System.Drawing.Drawing2D.AdjustableArrowCap  arrow
                    = new System.Drawing.Drawing2D.AdjustableArrowCap(ArrowSize, ArrowSize, false);

        penLine.CustomStartCap = arrow;
        penLine.CustomEndCap = arrow;

        var StartPointOffset = new PointF(
            StartPoint.X + Offset * (float)Math.Cos(thRad - Math.PI / 2.0),
            StartPoint.Y + Offset * (float)Math.Sin(thRad - Math.PI / 2.0)
            );

        var EndPointOffset = new PointF(
            EndPoint.X + Offset * (float)Math.Cos(thRad - Math.PI / 2.0),
            EndPoint.Y + Offset * (float)Math.Sin(thRad - Math.PI / 2.0)
            );

        // 矢印の描画
        g.DrawLine(penLine, StartPointOffset, EndPointOffset);
    }
}

使用する側はこんな感じで↓

private void Form1_Paint(object sender, PaintEventArgs e)
{
    var StartPoint = new PointF();
    var EndPoint = new PointF();

    // 四角形の描画
    var p = new Pen(Brushes.Black, 2);
    e.Graphics.DrawRectangle(p, 100, 100, 200, 200);
    e.Graphics.DrawRectangle(p, 100, 100, 400, 300);

    ///////////////////////////////////////////////////////////////
    // 横方向の寸法線の描画
    StartPoint.X = 100; StartPoint.Y = 100;
    EndPoint.X = 300; EndPoint.Y = 100;
    Dimension.DrawDimension(e.Graphics, StartPoint, EndPoint, 20, "200");

    StartPoint.X = 100; StartPoint.Y = 100;
    EndPoint.X = 500; EndPoint.Y = 100;
    Dimension.DrawDimension(e.Graphics, StartPoint, EndPoint, 50, "400");

    ///////////////////////////////////////////////////////////////
    // 縦方向の寸法線の描画
    StartPoint.X = 100; StartPoint.Y = 300;
    EndPoint.X = 100; EndPoint.Y = 100;
    Dimension.DrawDimension(e.Graphics, StartPoint, EndPoint, 20, "200");

    StartPoint.X = 100; StartPoint.Y = 400;
    EndPoint.X = 100; EndPoint.Y = 100;
    Dimension.DrawDimension(e.Graphics, StartPoint, EndPoint, 50, "300");

    ///////////////////////////////////////////////////////////////
    // 斜めの寸法線の描画
    StartPoint.X = 100; StartPoint.Y = 100;
    EndPoint.X = 500; EndPoint.Y = 400;
    Dimension.DrawDimension(e.Graphics, StartPoint, EndPoint, 20, "500");
}

プログラムはこちら↓に置いておきました。

DrawDimensionLine.zip(Visual Studio 2015)

 

機械製図的には寸法線の引き出し方向など、怪しい部分もありそうなので、良い感じになるように修正してみて下さい。

 

画像処理のためのC#へ戻る

【C#】GraphicsPathの領域取得

GraphicsPathを囲む外接四角形の領域はGetBoundsメソッドで取得することができます。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    // パスの作成
    var path = new System.Drawing.Drawing2D.GraphicsPath();

    // 四角形の追加
    path.AddRectangle(new Rectangle(30, 50, 50, 80));
    // 多角形の追加
    path.AddPolygon(
        new Point[]{
            new Point(100, 20),
            new Point(150, 200),
            new Point(60, 70)
        }
        );
    // 円の追加
    path.AddEllipse(150, 30, 50, 50);

    // 描画
    e.Graphics.DrawPath(Pens.Red, path);

    // パスに外接する四角形領域
    RectangleF rect = path.GetBounds();

    // 領域の描画
    e.Graphics.DrawRectangle(Pens.Blue, rect.X, rect.Y, rect.Width, rect.Height);

    ////////////////////////////////////////////////////////////////////////////
    // 文字の追加

    // 文字用にパスの作成
    var pathString = new System.Drawing.Drawing2D.GraphicsPath();
    pathString.AddString(
        "GraphicsPath",
        new FontFamily("Arial"),
        (int)FontStyle.Regular,
        48.0f,
        new Point(20, 250), // 文字の表示位置(左上の座標)
        new StringFormat()
        );

    // パスに外接する四角形領域
    RectangleF rectString = pathString.GetBounds();

    e.Graphics.DrawPath(Pens.Red, pathString);

    // 領域の描画
    e.Graphics.DrawRectangle(Pens.Blue, rectString.X, rectString.Y, rectString.Width, rectString.Height);
    // 文字の表示位置(20, 250)を描画
    e.Graphics.FillEllipse(Brushes.Blue, 15, 245, 10, 10);

}

実行結果

 

GraphicsPathを使うと複数の領域の外接四角形の領域が簡単に取得できるので、パスの領域を描画せずとも、領域の最大/最小の範囲を取得するのに便利です。

 

また、文字の領域も実際に描画している領域を取得できるので、これはこれで便利かも??

 

画像処理のためのC#へ戻る

【C#】GraphicsPathの描画

GraphicsPathの特長の一つでもあるアフィン変換を駆使した描画をしてみたいと思います。

 

パスの描画は、パスをnewしてAddLineなどのメソッドで図形を描画し、DrawPath(輪郭の描画)やFillPath(塗りつぶした描画)で描画を行います。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    var path = new System.Drawing.Drawing2D.GraphicsPath();

    // 線の追加
    path.AddLine(0, 75, 300, 75);
    // 円の追加
    path.AddEllipse(75, 75, 50, 50);
    // 文字の追加
    path.AddString(
        "GraphicsPath",
        new FontFamily(System.Drawing.Text.GenericFontFamilies.Serif),
        (int)FontStyle.Regular,
        48.0f,
        new Point(10, 20),
        new StringFormat()
        );

    // 描画(塗りつぶす)
    e.Graphics.FillPath(Brushes.Red, path);

    ///////////////////////////////////////////////////////////////////
    // アフィン変換行列
    var mat = new System.Drawing.Drawing2D.Matrix();
    // せん断
    mat.Shear(-2, 0, System.Drawing.Drawing2D.MatrixOrder.Append);
    // 拡大
    mat.Scale(1.5f, 2.0f, System.Drawing.Drawing2D.MatrixOrder.Append);
    // 回転
    mat.Rotate(-30, System.Drawing.Drawing2D.MatrixOrder.Append);
    // 平行移動
    mat.Translate(100, 150, System.Drawing.Drawing2D.MatrixOrder.Append);
    ///////////////////////////////////////////////////////////////////

    // パスをアフィン変換
    path.Transform(mat);
    // Penの作成
    var p = new Pen(Brushes.Blue, 3);
    // 描画(輪郭を線幅3で描画)
    e.Graphics.DrawPath(p, path);
}

実行結果

 

実際に実行して気が付いたのですが、線を塗りつぶし(FillPath)で描画すると、線は表示されないようです。

 

一度作成したPathは使いまわして描画できるので、星みたいな多角形をいくつも書くのにも便利かと思います。

 

文字を歪めたり、回転されられるのは、なんか楽しい!

 

画像処理のためのC#へ戻る

【C#】GraphicsPath

GraphicsPath(名前空間:System.Drawing.Drawing2D)ですが、線や丸を書くだけで、DrawXXX系のメソッドでも出来るしなんか面倒臭いやつ?!

と、思っていたのですが、GraphicsPathに含まれているメソッドを見ていたら、少し認識が変わりました。

 

とりあえず気になったメソッドは

GetBounds パスの領域に外接する四角形の取得
IsVisible 指定した点がパス領域内に含まれるかどうか
Transform パスをアフィン変換します。
Widen パスの領域を囲むパスへ変換します。

 

など。

他にも気になるメソッドはあるのですが、まだ、使い方が分からず。。

 

上記のメソッドを使う事で、下図のようにライン上をマウスポインタが移動したか?などのイベント処理を行う事ができます。

 

このGraphicsPathを使うことで、以下のような使い方が便利かと思います。

 

●図形編集用のマーカーなど

●枠線の表示

●寸法線の表示

 

【C#】寸法線の描画

 

画像処理のためのC#テクニックへ戻る

【C#】Bitmap画像データの拡大縮小

Bitmap画像を拡大縮小するには、Bitmapクラスのコンストラクタで、

Bitmap bmpOrijinal = new Bitmap("sample.bmp");

int scale = 50;
Bitmap bmpResize = new Bitmap(
    bmpOrijinal,
    bmpOrijinal.Width * scale,
    bmpOrijinal.Height * scale
    );

pictureBox1.Image = bmpResize;

(処理結果)

 

のようにすると簡単に画像を拡大縮小をすることができますが、このやり方では、いくつか不都合があります。

 

●モノクロ8bit画像に対応していない。

●補間モードを指定できない。

●画像を拡大すると、0.5画素分、位置がズレている。

●リサイズ後のBitmapは32bitになってしまう。

 

ということで、これらに対応したメソッドを作ってみました。

/// <summary>
/// Bitmap画像データのリサイズ
/// </summary>
/// <param name="original">元のBitmapクラスオブジェクト</param>
/// <param name="width">リサイズ後の幅</param>
/// <param name="height">リサイズ後の高さ</param>
/// <param name="interpolationMode">補間モード</param>
/// <returns>リサイズされたBitmap</returns>
private Bitmap ResizeBitmap(Bitmap original, int width, int height, System.Drawing.Drawing2D.InterpolationMode interpolationMode)
{
    Bitmap bmpResize;
    Bitmap bmpResizeColor;
    Graphics graphics = null;

    try
    {
        System.Drawing.Imaging.PixelFormat pf = original.PixelFormat;

        if (original.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
        {
            // モノクロの時は仮に24bitとする
            pf = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
        }

        bmpResizeColor = new Bitmap(width, height, pf);
        var dstRect = new RectangleF(-0.5f, -0.5f, width, height);
        var srcRect = new RectangleF(-0.5f, -0.5f, original.Width, original.Height);
        graphics = Graphics.FromImage(bmpResizeColor);
        graphics.Clear(Color.Transparent);
        graphics.InterpolationMode = interpolationMode;
        graphics.DrawImage(original, dstRect, srcRect, GraphicsUnit.Pixel);

    }
    finally
    {
        if (graphics != null)
        {
            graphics.Dispose();
        }
    }

    if (original.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
    {
        // モノクロ画像のとき、24bit→8bitへ変換

        // モノクロBitmapを確保
        bmpResize = new Bitmap(
            bmpResizeColor.Width, 
            bmpResizeColor.Height, 
            System.Drawing.Imaging.PixelFormat.Format8bppIndexed
            );

        var pal = bmpResize.Palette;
        for (int i = 0; i < bmpResize.Palette.Entries.Length; i++)
        {
            pal.Entries[i] = original.Palette.Entries[i];
        }
        bmpResize.Palette = pal;

        // カラー画像のポインタへアクセス
        var bmpDataColor = bmpResizeColor.LockBits(
                new Rectangle(0, 0, bmpResizeColor.Width, bmpResizeColor.Height),
                System.Drawing.Imaging.ImageLockMode.ReadWrite,
                bmpResizeColor.PixelFormat
                );

        // モノクロ画像のポインタへアクセス
        var bmpDataMono = bmpResize.LockBits(
                new Rectangle(0, 0, bmpResize.Width, bmpResize.Height),
                System.Drawing.Imaging.ImageLockMode.ReadWrite,
                bmpResize.PixelFormat
                );

        int colorStride = bmpDataColor.Stride;
        int monoStride = bmpDataMono.Stride;

        unsafe
        {
            var pColor = (byte*)bmpDataColor.Scan0;
            var pMono = (byte*)bmpDataMono.Scan0;
            for (int y = 0; y < bmpDataColor.Height; y++)
            {
                for (int x = 0; x < bmpDataColor.Width; x++)
                {
                    // R,G,B同じ値のため、Bの値を代表してモノクロデータへ代入
                    pMono[x + y * monoStride] = pColor[x * 3 + y * colorStride];    
                }
            }
        }

        bmpResize.UnlockBits(bmpDataMono);
        bmpResizeColor.UnlockBits(bmpDataColor);

        // 解放
        bmpResizeColor.Dispose();
    }
    else
    {
        // カラー画像のとき
        bmpResize = bmpResizeColor;
    }

    return bmpResize;
}

このResizeBitmapメソッドを使って、このようなプログラムを書くと

Bitmap bmpOrijinal = new Bitmap("sample.bmp");

int scale = 50;
Bitmap bmpResize = ResizeBitmap(
    bmpOrijinal,
    bmpOrijinal.Width * scale,
    bmpOrijinal.Height * scale,
    System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor
    );

pictureBox1.Image = bmpResize;

(処理結果)

 

となります。

モノクロの処理は少しまどろっこしい感じもしますが、画像データのリサイズや補間の部分を自前で書くよりは簡単!?

 

画像処理のためのC#へ戻る

【C#】画像の座標系

画像を描画するにはDrawImageメソッドを用いますが、DrawImageメソッドはいくつものオバーロードが定義されていますが、画像の拡大縮小を考慮すると、個人的には以下の定義をよく用います。

public void DrawImage(
     Image image,
     Rectangle destRect,
     int srcX,
     int srcY,
     int srcWidth,
     int srcHeight,
     GraphicsUnit srcUnit
)
public void DrawImage(
     Image image,
     Rectangle destRect,
     float srcX,
     float srcY,
     float srcWidth,
     float srcHeight,
     GraphicsUnit srcUnit
)

この時の座標系は下図の用になり、元の画像に対して、画像の描画先(destRect)を大きくすると画像の拡大となり、描画先を小さくすると画像の縮小となります。

 

 

しかしながら、以下のように単純に画像の拡大のプログラムを実行すると、

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
     var srcImage = new Bitmap("sample.bmp");
     Graphics g = e.Graphics;
     g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;

     g.DrawImage(
          srcImage,
          new Rectangle(0, 0, srcImage.Width * 50, srcImage.Height * 50),
          0,
          0,
          srcImage.Width,
          srcImage.Height,
          GraphicsUnit.Pixel
     );
}

 

このように画素の半分だけ画像が左上にずれて表示されてしまいます。

 

これは、元の画像の座標系の原点が画素の中心部分にあるためで、このようになります。

 

このズレを無くすには、やり方は2つ

 

●PixelOffsetModeを指定する方法

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
     var srcImage = new Bitmap("sample.bmp");
     Graphics g = e.Graphics;
     g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
     g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;

     g.DrawImage(
          srcImage,
          new Rectangle(0, 0, srcImage.Width * 50, srcImage.Height * 50),
          0,
          0,
          srcImage.Width,
          srcImage.Height,
          GraphicsUnit.Pixel
     );
}

●元の画像の座標を0.5画素ズラす方法

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    var srcImage = new Bitmap("sample.bmp");

    Graphics g = e.Graphics;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;


    g.DrawImage(
        srcImage,
        new Rectangle(0, 0, srcImage.Width * 50, srcImage.Height * 50),
        -0.5f,
        -0.5f,
        srcImage.Width,
        srcImage.Height,
        GraphicsUnit.Pixel
        );
}

このようにすると、表示のズレが無くなります。

 

単に画像を拡大/縮小表示するだけなら、PixelOffsetModeを指定した方が簡単だと思いますが、アフィン変換などの座標変換を伴う場合は、元の画像の座標を-0.5分だけ図ずらしたやり方の方が良いかと思います。

 

画像処理のためのC#へ戻る

【C#】SplitContainerのPanel固定方法

SplitContainerのパネルのサイズは、フォームのリサイズやスピリッター(仕切り線)をマウスで操作することで、サイズが変更されますが、このパネルを固定する方法です。

 

 

フォームのリサイズに合わせて、パネルのサイズが変わらないようにするには FixedPanelプロパティで固定する方のパネルを選択します。

 

また、マウス操作でパネルのサイズが変更しないようにするには IsSplitterFixedプロパティTrueに設定します。

 

C#へ戻る

【C#.NET】マイクロソフト仕様のアフィン変換

.NETでは座標のアフィン変換用にMatrixクラス(名前空間:System.Drawing.Drawing2D)が用意されています。

 

しかしながら、やっかいな事に、私の思う普通のアフィン変換の行列の表現が行と列が逆(転置されている)だし、行列の掛ける順番も逆になります。

 

つまり、私の思う普通のアフィン変換の行列は変換前の座標が\(\left( x,\quad y \right) \)、変換後の座標が\(\left( { x }^{ ‘ },\quad { y }^{ ‘ } \right) \)だとすると、

 

【普通のアフィン変換】

$$\left( \begin{matrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{matrix} \right) =\left( \begin{matrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{matrix} \right) \left( \begin{matrix} x \\ y \\ 1 \end{matrix} \right) $$

 

【マイクロソフトのアフィン変換】

$$\left( \begin{matrix} { x }^{ ‘ } & { y }^{ ‘ } & 1 \end{matrix} \right) =\left( \begin{matrix} x & y & 1 \end{matrix} \right) \left( \begin{matrix} a & d & 0 \\ b & e & 0 \\ c & f & 1 \end{matrix} \right) $$

 

となります。

さらにアフィン変換を行列で表現するときに、拡大縮小、回転、移動を行列で連続的に計算するときは、一般的なアフィン変換では行列の左側から掛けていきますが、マイクロソフト仕様では行列の右側から掛けてきます。

つまり、拡大縮小、回転、移動の行列をそれぞれ、S、R, T とするとし、拡大縮小S→回転R→移動Tの順番でアフィン変換をする場合、アフィン変換行列は

 

【普通のアフィン変換】

$$\left( \begin{matrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{matrix} \right) =TRS\left( \begin{matrix} x \\ y \\ 1 \end{matrix} \right)$$

 

【マイクロソフトのアフィン変換】

$$\left( \begin{matrix} { x }^{ ‘ } & { y }^{ ‘ } & 1 \end{matrix} \right) =\left( \begin{matrix} x & y & 1 \end{matrix} \right) SRT$$

 

のようになります。

さらに厄介なのが、アフィン変換行列にさらに変換行列を掛け合わせるメソッドが用意されていて、

 

拡大縮小:Scaleメソッド

回転:Rotateメソッド

移動:Translateメソッド

 

が用意されているのですが、例えば下記のようなコード

var mat = new Matrix(

                  1, 2,

                  3, 4,

                  5, 6

                  );
mat.Scale(7, 8);

を書くと、内部の計算は

$$\left( \begin{matrix} 7 & 0 & 0 \\ 0 & 8 & 0 \\ 0 & 0 & 1 \end{matrix} \right) \left( \begin{matrix} 1 & 2 & 0 \\ 3 & 4 & 0 \\ 5 & 6 & 1 \end{matrix} \right) =\left( \begin{matrix} 7 & 14 & 0 \\ 24 & 32 & 0 \\ 5 & 6 & 1 \end{matrix} \right) $$

 

となっています。

ここで問題なのが、最初にアフィン変換の行列はマイクロソフト仕様では行列の右側から掛けると言いましたが、単に

 

mat.Scale(7, 8);

 

と書くと、行列の左側から行列を掛けてしまいます。

これだと計算が合わないので、

 

mat.Scale(7, 8, MatrixOrder.Append);

 

と書くことで、

$$\left( \begin{matrix} 1 & 2 & 0 \\ 3 & 4 & 0 \\ 5 & 6 & 1 \end{matrix} \right) \left( \begin{matrix} 7 & 0 & 0 \\ 0 & 8 & 0 \\ 0 & 0 & 1 \end{matrix} \right) =\left( \begin{matrix} 7 & 16 & 0 \\ 21 & 32 & 0 \\ 35 & 48 & 1 \end{matrix} \right)$$

 

となります。

つまり、実質的にScaleメソッド、Rotateメソッド、Translateメソッドは必ずMatrixOrder.Appendを指定する必要があります。

私は、この事にハマったのですが、これさえ理解できれば

 

指定した点周りの回転:RotateAtメソッド

逆行列:Invertメソッド

 

なども用意されているので、アフィン変換をする分には使えなくは無い感じです。

 

座標の値をアフィン変換行列で変換した後の座標を求めるには TransformPointsメソッド を用います。
一連のソースコードは

var mat = new Matrix(

                  1, 2,

                  3, 4,

                  5, 6,

                  );
mat.Scale(7, 8, MatrixOrder.Append);
var poi = new Point[]{ new Point(10, 20), new Point(30, 40) };
mat.TransformPoints( poi );

とすると、
(10, 20) → (525, 848)
(30, 40) → (1085, 1808)

と変換されます。

ただ、さらに残念なのが、アフィン変換で用いられる行列は

 

$$\left( \begin{matrix} { m }_{ 11 } & { m }_{ 12 } & 0 \\ { m }_{ 21 } & { m }_{ 22 } & 0 \\ { d }_{ x } & { d }_{ y } & 1 \end{matrix} \right) $$

 

となり、3列目の値はアフィン変換では用いられないので、設定することはできません。

というのが、マイクロソフトの仕様なのですが、設定できればもう少し汎用的に使えたのに...

ここで、わざわざマイクロソフト仕様と書いたのは、他にもDirect3Dでも同様の計算となるためです。

Direct3Dに近いOpenGLでは、ここで言っている一般的なアフィン変換となるので、混同しないように注意してください。

 

【参考情報】

Matrixクラス:https://msdn.microsoft.com/ja-jp/library/system.drawing.drawing2d.matrix(v=vs.110).aspx

変換の行列表現:https://msdn.microsoft.com/ja-jp/library/8667dchf(v=vs.110).aspx

一般的なアフィン変換:https://imagingsolution.net/imaging/affine-transformation/

 

C#へ戻る

【C#】ファイルを開くダイアログボックスの表示

C#でファイルを開くときに使うOpenFileDiaologクラス(名前空間  System.Windows.Forms)の例です。(主に自分のコピペ用)

 

下記のファイルは画像ファイルを開く例です。

//ファイルを開くダイアログボックスの作成 
var ofd = new OpenFileDialog(); 
//ファイルフィルタ 
ofd.Filter = "Image File(*.bmp,*.jpg,*.png,*.tif)|*.bmp;*.jpg;*.png;*.tif|Bitmap(*.bmp)|*.bmp|Jpeg(*.jpg)|*.jpg|PNG(*.png)|*.png"; 
//ダイアログの表示 (Cancelボタンがクリックされた場合は何もしない) 
if (ofd.ShowDialog() == DialogResult.Cancel) return; 
// 取得したファイル名 
var fileName = ofd.FileName;

C#へ戻る

【OpenCvSharp】サンプルプログラムの公開

少し前に、とある記事向けに書いたOpenCvSharpのサンプルプログラム。

少しバージョンが古くなっていますが、眠らせておくのも、もったいないので公開しておきます。

 

 

ダウンロードはこちら↓

OpenCvSharp サンプルプログラム

注)zipファイルを解凍してから使って下さい。

zipのプレビューからslnファイルを実行すると、プロジェクトファイルの読込に失敗します。

 

このサンプルだけで、

 

平滑化(Blur、GaussianBlur、Median)

二値化(普通の二値化、大津の二値化)

輪郭処理(ソーベルフィルタ、Cannyエッジ)

モフォロジー(膨張、収縮、オープニング、クロージング)

ヒストグラム表示

 

ができます。

OpenCVでなかなかカラーのヒストグラムまでのサンプルプログラムを書いてあるのは、あまりないので、そこそこ良いサンプルプログラムだと思っていたのですが、なんでボツったんだろう??

【C#】引数の値渡し、参照渡し(ref, out)

メソッドに値を引数で渡す場合、値渡し参照渡しというものがあります。

メソッド内で処理した結果を処理後に必要な場合には参照渡しにする必要があるのですが、まずはコード例から。

static void Main(string[] args) 
{ 
	int a1; 
	int a2; 
	int a3; 
	
	a1 = 1; // 値を代入しておく必要がある 
	Method1(a1); // 処理後:a = 1 

	a2 = 1; // 値を代入しておく必要がある 
	Method2(ref a2); // 処理後:a = 11 

	//a3 = 1; // 値を代入しておく必要がない(意味がない) 
	Method3(out a3); // 処理後:a = 10 
} 

// 値渡し 
public static void Method1(int a) 
{ 
	a += 10; // メソッド内では a = 11 となる 
} 

// 参照渡し(ref) 
public static void Method2(ref int a) 
{ 
	a += 10; 
} 

// 参照渡し(out) 
public static void Method3(out int a) 
{ 
	//a += 10; // エラー:未割り当てのoutパラメータ'a'が使用されました。 
	a = 10; 
}

Method1は値渡しの例ですが、aの値はメソッドで受け取れるものの、処理後のaは処理前のaの値と同じままです。

 

Method2は参照渡し(ref)の例ですが、aの値はメソッドで受け取れ、処理後のaもメソッド内で処理した値が繁栄されます。

参照渡し(ref)で渡す引数はメソッドに渡す前に必ず初期化(値を代入)しておく必要があります。

 

Method3は参照渡し(out)の例ですが、aの値を受け取れないものの、処理後の値はメソッド内で処理した値となります。

参照渡し(out)で渡す引数はメソッドに渡す前に初期化しておく必要はありません。

 

これらの動きは私のイメージではこんな感じ↓なのですが、伝わるでしょうか?

 

使い分け的には、一般的な処理では値渡しで十分な場合が多いかと思いますが、処理した結果が必要な場合には参照渡しとなります。

refとoutの違いについては、refでは引数の初期化が必要になりますが、outではその必要がありません。

そのため、メソッド内で引数で渡された値が必要な場合には、ref、必要の無い場合にはoutを使うとよいかと思います。

 

C#へ戻る

【C#】NumericUpDownコントロール

パラメータの設定など、数値を入力するコントロールとして、これまでTextBoxを使うことが多かったのですが、TextBoxを使うと、TryParseを使い、文字列入力のエラー処理や、整数、小数の確認、最小値、最大値の確認などのエラー処理を行う必要が出てきます。

 

しかし、NumericUpDownコントロールを使うと、これらのエラー処理をコントロール側でやってくれます。

 

 

NumericUpDownコントロールは▲▼のボタンで、数値を上下させるだけ?と思い込んでいたのですが、数値の部分に直接、数値を入力する事もできます。

 

NumericUpDownコントロールには、以下のようなプロパティが用意されています。

 

 

主なプロパティは以下の通り

 

■DecimalPlacesプロパティ

小数点以下の桁数を指定します。

0にすると、整数に制限できます。

例)DicimalPlaces = 3のとき

 

■Incrementプロパティ

▲▼のボタンをクリックするごとに増減する量を指定します。

 

■Maximumプロパティ

入力する値の最大値を指定します。

 

■Minimumプロパティ

入力する値の最小値を指定します。

 

 

■ThousandsSeparatorプロパティ

3ケタごとにカンマ(,)区切り表示するか?を指定します。

表示例)

 

■Valueプロパティ

表示されている数値の値をdecimal型で取得/設定します。

値取得のコード例)

var a = (int)numericUpDown1.Value;

var b = (float)numericUpDown1.Value;

など。

 

使い始めると、むしろ▲▼のアップダウン部分だ邪魔に感じますが、入力の文字列チェック、最小最大値の制限などをしてくれるのは、とても便利!

 

C#へ戻る

【C#】ImageDataクラスライブラリ公開

画像処理プログラムの基本は画像の輝度値(画素値)を取得して、様々な処理をすることとなりますが、C#では輝度値を取得するメソッドにSetPixel/GetPixelのメソッドが用意されていますが、これは処理が遅いことで有名。

 

そこで、OpenCVのIplImage構造体やMatクラスのエッセンスを取り入れつつ、.NETのBitmapクラスっぽく、さらに画像データを簡単に扱えるようにすることを目指したImageDataクラスなるライブラリを作成してみました。

 

基本コンセプトは

●画像の輝度値の取得/設定が簡単

●SetPixel/GetPixelよりは高速

●シンプルなプログラムが書けること

 

といったところです。

 

画像処理をこれから学ぼうとする人、とりあえず簡単にアルゴリズムの評価プログラムを作って見たい人向けを想定しています。パフォーマンス重視の方はポインタ(Scan0)を参照してください。

 

というスタンスです。

今のところフリーですので、ご興味があれば使ってみてください。

ただし、本ライブラリを使用した事による不具合等の責任を負えませんので、ご了承願います。

 

ファイル:ImagingSolution.Imaging.ImageData.dll

.NET Frameworkバージョン:4.5.2

Visual Studio 2015 C#を使用

 

ダウンロード

公開日 バージョン サンプルプログラム
2016.3.19 Ver.0.1.1 ImageDataSample011.zip Bitmapファイル読込部分のバグ修正

Reaginプロパティに影響

2016.3.17 Ver.0.1.0 ImageDataSample010.zip 初版

バグなどが含まれる可能性があります。

今後、仕様が変更される可能性もあります。

 

コンストラクター

名前 説明
ImageData(int, int, int, int) 画像の幅、高さ、画像のビット数、メモリのビット数を指定して、ImageDataクラスの新しいインスタンスを初期化します。
ImageData(int, int, int, System.Drawing.Imaging.PixelFormat, System.IntPtr) 画像の幅、高さ、メモリの幅のバイト数、ピクセルフォーマット、外部メモリのポインタを指定して、ImageDataクラスの新しいインスタンスを初期化します。
ImageData(int, int, System.Drawing.Imaging.PixelFormat) 画像の幅、高さ、ピクセルフォーマットを指定して、ImageDataクラスの新しいインスタンスを初期化します。
ImageData(string) 画像ファイル名を指定して、ImageDataクラスの新しいインスタンスを初期化します。

 

プロパティ

名前 説明
BiCubicVal biCubiによる補間の計算時に用いる変数を取得/設定します。

初期値:-0.75

BorderType 画像の外側の輝度値の参照方法をBorderTypeEnumにより取得/設定します。

初期値:BorderTypeEnum.Mirror

BufferBit 画像を格納するメモリのビット数を取得します。

8、16、24、32のいづれを指定します。

ByteAlignment 画像を格納するメモリのアライメントのバイト数を取得します。
Channel 画像のチャンネル数(色数)を取得します。

1, 3, 4のいづれか

Height 画像の高さ(画素数)を取得します。
ImageBit 画像のビット数を取得します。

8~16、24、30、32のいづれを指定します。

InterpolationMode 画素間の輝度値の取得時の補間モードをInterpolationModeEnumにより取得/設定します。

初期値:InterpolationModeEnum.Bilinear

MinusValueMode 輝度値(画素値)に負の値を設定時の処理方法をMinusValueModeEnumにより取得/設定します。

初期値:MinusValueModeEnum.Zero

PixelFormat 画像のピクセルフォーマットをSystem.Drawing.Imaging.PixelFormatにより取得します。
Region 画像処理処理範囲(参照範囲)をSystem.Drawing.Rectangleにより取得/設定します。
Sacn0 画像データを格納するメモリのポインタをSystem.IntPtrにより取得します。
Stride 画像データを格納するメモリの幅をバイト数で取得します。
Width 画像の幅(画素数)を取得します。

 

インデクサ

名前 説明
this[float, float, int] 画像の行位置(Y座標)、列位置(X座標)、チャンネル番号(B[0]、G[1]、R[2])を指定して輝度値を取得します。
this[float, float] 画像データの行位置(Y座標)、列位置(X座標)を指定して輝度値を取得します。
this[int, int, int] 画像の行位置(Y座標)、列位置(X座標)、チャンネル番号(B[0]、G[1]、R[2])を指定して輝度値を取得/設定します。
this[int, int] 画像データの行位置(Y座標)、列位置(X座標)を指定して輝度値を取得/設定します。

 

列挙型

名前 説明
BorderTypeEnum BorderTypeプロパティを指定するのに用います。

Clamp:最外周の輝度値を用いる

Mirror:(初期値)画像の外側から折り返した輝度値を用いる

ToZero:画像の外側は輝度値0にする

InterpolationModeEnum InterpolationModeプロパティで画素間の輝度値を取得する際の補間方法を指定するのに用います。

Bicubic:バイキュービック

Bilinear:(初期値)バイリニア

NearestNeighbor:ニアレストネイバー

MinusValueModeEnum インデクサで負の値を指定した時の処理方法を指定します。

Absolute:負の場合は絶対値をとります。

Zero:(初期値)負の場合は0にします。

 

メソッド

名前 説明
Clone() ImageDataクラスオブジェクトのクローンを作成します。画像データもコピーされます。
Clone(bool) 画像データをコピーする(true)/しない(false)を指定してImageDataクラスオブジェクトのクローンを作成します。
CopyMemory(System.IntPtr, System.IntPtr, int) コピー先のポインタ、コピー元のポインタ、コピーサイズを指定して画像データをコピーします。
CopyTo(ImagingSolution.Imaging.ImageData) 画像データを指定したImageDataクラスオブジェクトへコピーします。
Dispose() ImageDataクラスで使用されているリソースを解放します。
ImageData.IsEqualImageSize(

ImagingSolution.Imaging.ImageData, ImagingSolution.Imaging.ImageData)

2つのImageDataクラスの画像サイズが等しい(true)か等しくない(false)かを確認します。
ResetRegion()  指定した領域(Regionプロパティ)を解除します。
Save(string) ImageDataクラスの画像データをファイルに保存します。
ToBitmap() ImageDataクラスオブジェクトをSystem.Drawing.Bitmapクラスへ変換します。
ZeroMemory(System.IntPtr, int) ポインタで示されているメモリから指定バイト数分、0に設定します。

 

サンプルプログラム

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ImageDataSample
{
    public partial class Form1 : Form
    {

        /// 
        ImagingSolution.Imaging.ImageData _img;

        public Form1()
        {
            InitializeComponent();
        }

        /// 
        /// 
        private void mnuFileOpen_Click(object sender, EventArgs e)
        {
            //ファイルを開くダイアログボックスの作成  
            var ofd = new OpenFileDialog();
            //ファイルフィルタ  
            ofd.Filter = "Image File(*.bmp,*.jpg,*.png,*.tif)|*.bmp;*.jpg;*.png;*.tif|Bitmap(*.bmp)|*.bmp|Jpeg(*.jpg)|*.jpg|PNG(*.png)|*.png";
            //ダイアログの表示 (Cancelボタンがクリックされた場合は何もしない)
            if (ofd.ShowDialog() == DialogResult.Cancel) return;

            if (_img != null) _img.Dispose();

            _img = new ImagingSolution.Imaging.ImageData(ofd.FileName);

            picImage.Image = _img.ToBitmap();
        }

        /// 
        /// 
        private void mnuFileSave_Click(object sender, EventArgs e)
        {
            // 画像データが設定されていない場合は何もしない
            if (_img == null) return;

            //名前を付けて保存ダイアログボックスの作成  
            var sfd = new SaveFileDialog();
            //ファイルフィルタ  
            sfd.Filter = "Bitmap(*.bmp)|*.bmp|Jpeg(*.jpg)|*.jpg|PNG(*.png)|*.png|CSV(*.csv)|*.csv";
            //ダイアログの表示 (Cancelボタンがクリックされた場合は何もしない)
            if (sfd.ShowDialog() == DialogResult.Cancel) return;

            // 名前を付けて画像データを保存
            _img.Save(sfd.FileName);
        }

        /// 
        /// 
        /// /param>
        private void mnuFileExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// 

 

ImageDataクラスライブラリへ戻る

【C#】Chartを使ったヒストグラム表示

Chartコントロールを使いたかった理由の一つに画像処理ではおなじみのヒストグラムをChartコントロールで表示したかったのですが、その簡単なプログラムです。

 

フォームにはChartコントロールを配置し、Chartの名前がchart1とした事を前提として、

// /////////////////////////////////////////////////////
// Chartコントロール内のグラフ、凡例、目盛り領域を削除
chart1.Series.Clear();
chart1.Legends.Clear();
chart1.ChartAreas.Clear();

// /////////////////////////////////////////////////////
// 目盛り領域の設定
var ca = chart1.ChartAreas.Add("Histogram");

// X軸
ca.AxisX.Title = "Brightness";  // タイトル
ca.AxisX.Minimum = 0;           // 最小値
ca.AxisX.Maximum = 256;         // 最大値
ca.AxisX.Interval = 64;         // 目盛りの間隔
// Y軸
ca.AxisY.Title = "Count";
ca.AxisY.Minimum = 0;

// /////////////////////////////////////////////////////
// データの追加
var hist = new Point[] {
    new Point(32, 10),
    new Point(96, 30),
    new Point(160, 50),
    new Point(224, 20)
};

// グラフの系列を追加
var s = chart1.Series.Add("Histogram");
// 棒グラフの隙間を無くす
s.SetCustomProperty("PointWidth", "1.0");
// データ設定
for (int i = 0; i < hist.Length; i++) {
    s.Points.AddXY(hist[i].X, hist[i].Y);
}

と書くだけで、モノクロ画像用のヒストグラムが簡単に作成できます。

 

(実行結果)

 

ここでのポイントとしては、棒グラフ(ChartTypeColumn)の時に、棒グラフの間隔を無くすにはPointWidthプロパティを1.0にすればよい(デフォルトは0.8)のですが、各グラフ特有のプロパティ設定は以下のように設定する必要があります。

(直接プロパティの設定ができません。)

// 棒グラフの隙間を無くす
s.SetCustomProperty("PointWidth", "1.0");

モノクロ画像の時はこれでもよいのですが、カラーだと、R,G,Bの3つのグラフを書かないといけないので、折れ線では表現しにくいので、折れ線(ChartTypeLine)を使ってみたいと思います。

 

要領は同じで、SeriesにR,G,B用の3つを追加することぐらいです。

// /////////////////////////////////////////////////////
// Chartコントロール内のグラフ、凡例、目盛り領域を削除
chart1.Series.Clear();
chart1.Legends.Clear();
chart1.ChartAreas.Clear();

// /////////////////////////////////////////////////////
// 目盛り領域の設定
var ca = chart1.ChartAreas.Add("Histogram");

// X軸
ca.AxisX.Title = "Brightness";  // タイトル
ca.AxisX.Minimum = 0;           // 最小値
ca.AxisX.Maximum = 256;         // 最大値
ca.AxisX.Interval = 64;         // 目盛りの間隔
// Y軸
ca.AxisY.Title = "Count";
ca.AxisY.Minimum = 0;

// /////////////////////////////////////////////////////
// データの追加
var histR = new Point[] {
    new Point(0, 10),
    new Point(63, 30),
    new Point(127, 80),
    new Point(191, 30),
    new Point(255, 15)
};
var histG = new Point[] {
    new Point(0, 70),
    new Point(63, 60),
    new Point(127, 40),
    new Point(191, 20),
    new Point(255, 10)
};
var histB = new Point[] {
    new Point(0, 10),
    new Point(63, 20),
    new Point(127, 30),
    new Point(191, 70),
    new Point(255, 90)
};

// グラフの系列を追加
var sR = chart1.Series.Add("HistogramR");
var sG = chart1.Series.Add("HistogramG");
var sB = chart1.Series.Add("HistogramB");

// グラフの種類を折れ線に設定する
sR.ChartType = sG.ChartType = sB.ChartType 
    = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;

// データ設定
for (int i = 0; i < histR.Length; i++)
{
    sR.Points.AddXY(histR[i].X, histR[i].Y);
    sG.Points.AddXY(histG[i].X, histG[i].Y);
    sB.Points.AddXY(histB[i].X, histB[i].Y);
}

(実行結果)

 

これでカラーのヒストグラムも表示できる事ができます!

とか言っても、見た目があまりにもショボイので、もう少し手を入れます。

 

今度はグラフの種類にスプライン面グラフ(SeriesChartType.SplineArea)を使ってみます。

// /////////////////////////////////////////////////////
// Chartコントロール内のグラフ、凡例、目盛り領域を削除
chart1.Series.Clear();
chart1.Legends.Clear();
chart1.ChartAreas.Clear();

// /////////////////////////////////////////////////////
// 目盛り領域の設定
var ca = chart1.ChartAreas.Add("Histogram");

// X軸
ca.AxisX.Title = "Brightness";  // タイトル
ca.AxisX.Minimum = 0;           // 最小値
ca.AxisX.Maximum = 256;         // 最大値
ca.AxisX.Interval = 64;         // 目盛りの間隔
// Y軸
ca.AxisY.Title = "Count";
ca.AxisY.Minimum = 0;

// /////////////////////////////////////////////////////
// データの追加
var histR = new Point[] {
    new Point(0, 10),
    new Point(63, 30),
    new Point(127, 80),
    new Point(191, 30),
    new Point(255, 15)
};
var histG = new Point[] {
    new Point(0, 70),
    new Point(63, 60),
    new Point(127, 40),
    new Point(191, 20),
    new Point(255, 10)
};
var histB = new Point[] {
    new Point(0, 10),
    new Point(63, 20),
    new Point(127, 30),
    new Point(191, 70),
    new Point(255, 90)
};

// グラフの系列を追加
var sR = chart1.Series.Add("HistogramR");
var sG = chart1.Series.Add("HistogramG");
var sB = chart1.Series.Add("HistogramB");

// グラフの種類をスプライン面グラフに設定する
sR.ChartType = sG.ChartType = sB.ChartType
    = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.SplineArea;

// 輪郭線の太さ
sR.BorderWidth = sG.BorderWidth = sB.BorderWidth = 2;

// 輪郭線の色
sR.BorderColor = Color.Red;
sG.BorderColor = Color.Green;
sB.BorderColor = Color.Blue;

// 塗りつぶしの色の設定(半透明)
sR.Color = Color.FromArgb(150, Color.Red);
sG.Color = Color.FromArgb(150, Color.Green);
sB.Color = Color.FromArgb(150, Color.Blue);

// データ設定
for (int i = 0; i < histR.Length; i++)
{
    sR.Points.AddXY(histR[i].X, histR[i].Y);
    sG.Points.AddXY(histG[i].X, histG[i].Y);
    sB.Points.AddXY(histB[i].X, histB[i].Y);
}

(実行結果)

 

これで、かなりそれっぽくないですか?

プログラム的に書くと、塗りつぶしの色に半透明の色を設定できるところがポイントでしょうかね。

 

Chartコントロールの使用方法へ戻る