【C#】複素数の計算(Complex構造体)

C#では複素数を扱う Complex構造体(名前空間:System.Numerics)が用意されています。

ただし、プロジェクトの初期状態では、使えないため、プロジェクトの参照を右クリックし、参照の追加System.Numerics を追加してください。

 

 

コード例

var complex1 = new System.Numerics.Complex(Math.Sqrt(3), 1.0);
Console.WriteLine($"complex1.Magnitude = {complex1.Magnitude}");
Console.WriteLine($"complex1.Phase = {complex1.Phase * 180.0 / Math.PI}");

var complex2 = new System.Numerics.Complex(1.0, Math.Sqrt(3));
Console.WriteLine($"complex2.Magnitude = {complex2.Magnitude}");
Console.WriteLine($"complex2.Phase = {complex2.Phase * 180.0 / Math.PI}");

var complex3 = complex1 * complex2;
Console.WriteLine($"complex3.Magnitude = {complex3.Magnitude}");
Console.WriteLine($"complex3.Phase = {complex3.Phase * 180.0 / Math.PI}");

var complex4 = complex1 + complex2;
Console.WriteLine($"complex4.Magnitude = {complex4.Magnitude}");
Console.WriteLine($"complex4.Phase = {complex4.Phase * 180.0 / Math.PI}");

// 結果
//complex1.Magnitude = 2
//complex1.Phase = 30
//complex2.Magnitude = 2
//complex2.Phase = 60
//complex3.Magnitude = 4
//complex3.Phase = 90
//complex4.Magnitude = 3.86370330515627
//complex4.Phase = 45

 

複素数の大きさは Magnitudeプロパティで、位相はPhaseプロパティで拾えるし、複素数の演算(加算、減算、乗算、除算)も可能です。

複数の演算は、複素数と複素数、複素数と実数の演算が可能です。

 

これが出来るという事はDFT(離散フーリエ変換)も簡単に出来ちゃいますね。

 

関連ページ

複素数のイメージ

複素数の計算

離散フーリエ変換(DFT)の直感的理解

 

参考ページ

https://docs.microsoft.com/ja-jp/dotnet/api/system.numerics.complex?view=netframework-4.8

 

← C# へ戻る

【C#3.0~】自動実装プロパティ

C#3.0(Visual Studio 2008)からは、自動実装プロパティなる物が使えるようになりました。

 

C#2.0までのプロパティの実装は、こんな感じ↓

class Contrast
{
    private double _scale = 1.0;
    public double Scale
    {
        get
        {
            return _scale;
        }
        set
        {
            _scale = value;
        }
    }

    private double _offset = 0.0;
    public double Offset
    {
        get
        {
            return _offset;
        }
        set
        {
            _offset = value;
        }
    }
}

 

で行っていたかと思いますが、C#3.0からは、自動実装プロパティでは、

class Contrast
{
    public double Scale { get; set; }

    public double Offset { get; set; }
}

のように書くことができます。

これで、ずいぶんシンプルで見やすくなるかと思います。

 

また、プロパティを読み取り専用にするには set; の前に private を付けて

class Contrast
{
    public double Scale { get; set; }

    public double Offset { get;  private set; }
}

とします。

 

C#6.0(Visual Studio 2015)からは自動プロパティを以下のようにすることで、初期化を行う事ができます。

class Contrast
{
    public double Scale { get; set; } = 1.0;

    public double Offset { get; set; } = 0.0;
}

さらにC#6.0からは、読み取り専用にプロパティを get; のみの記述で可能となります。

ただし、値を変更できるのは、コンストラクタのみとなります。(他のメソッド等からは変更できません。)

class Contrast
{
    public double Scale { get; set; } = 1.0;

    public double Offset { get; } = 0.0;

    public Contrast(double scale, double offset)
    {
        this.Scale = scale;
        this.Offset = offset;
    }
}

 

パフォーマンスの比較

C#2.0まで、処理速度が必要な部分では、プロパティの参照は遅いので、クラス内での処理ではプロパティを参照せず、フィールドの値を参照するようにしていたのですが、フィールドの無い、自動プロパティではどうか?確認してみました。

 

以下のように、C#2.0の時のプロパティを使った Class1 と、自動実装プロパティを使った Class2 を作成し、ただ、値を取得するだけのメソッド(GetValue)を作成しました。

class Class1
{
    private double _a = 1.0;
    public double A
    {
        get
        {
            return _a;
        }
        set
        {
            _a = value;
        }
    }

    public double GetValue()
    {
        return _a;
    }
}

class Class2
{
    public double A { get; set; } = 1.0;

    public double GetValue()
    {
        return this.A;
    }
}

このGetValue()メソッドをそれぞれ、10億回繰り返した時の処理時間を5回計測したときの平均処理時間は、以下の通りでした。

 

Class1 273.6 msec
Class2 552 msec

 

プロパティ参照は、プロパティの内部でエラー処理などを行っている場合も多いので、そのために遅い場合もありますが、単純にプロパティ参照するだけでも遅い結果でした。

 

そのため、パフォーマンスが必要な場合は、昔の書き方もありですね。

 

参考ページ

自動実装するプロパティ (C# プログラミング ガイド)

自動実装するプロパティを使用して簡易クラスを実装する方法 (C# プログラミング ガイド)

 

← C#2.0からの脱却 へ戻る

【C#4.0~】Parallel.Forによる並列処理

C#4.0(Visual Studio 2010)からはParallel.For(名前空間:System.Threading.Tasks)による並列処理が可能となります。

2コアや4コアは当たり前の時代なので、C#のParallel.Forを使った方が良いかは別としても、並列処理はしないとCPUの無駄遣い状態になってしまいます。

 

Parallel.Forの構文については、定番の下記 ++C++のページ↓

https://ufcpp.net/study/csharp/lib_parallel.html#parallel

 

を見て頂くと分かりやすいかと思いますが、通常のfor文では

for (int i = 0; i < N; i++)
{
    // 通常の処理
}

と書くところを

Parallel.For(0, N, i =>
{
    // この部分の処理が並列化される
});

のように書くと、Parallel.Forの{ }内では並列処理が行われます。

 

ここでは、Parallel.Forを画像処理的に使うと、どうなるか?やってみます。

 

まず、通常のfor文でコントラスト調整をする以下のようなメソッドを作ります。

private void ContrastImage(Bitmap bmp, double scale, double offset)
{
    var width = bmp.Width;
    var height = bmp.Height;

    // Bitmapをロック
    var bmpData = bmp.LockBits(
            new Rectangle(0, 0, width, height),
            System.Drawing.Imaging.ImageLockMode.ReadWrite,
            bmp.PixelFormat
        );

    // メモリの幅のバイト数を取得
    var stride = Math.Abs(bmpData.Stride);

    int channel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;

    // 画像データ格納用配列
    var picData = new byte[stride * bmpData.Height];

    // Bitmapデータを配列へコピー
    System.Runtime.InteropServices.Marshal.Copy(
        bmpData.Scan0,
        picData,
        0,
        stride * bmpData.Height
        );

    for (int y = 0; y < height; y++)
    {
        int lineIndex = stride * y;

        for (int x = 0; x < width * channel; x++)
        {
            int value = picData[lineIndex + x];
            value = (int)(value * scale + offset);
            value = (value < 0) ? 0 : (value > 255) ? 255
                    : value
                    ;
            picData[lineIndex + x] = (byte)value;
        }
    }

    // 配列をBitmapデータへコピー
    System.Runtime.InteropServices.Marshal.Copy(
        picData,
        0,
        bmpData.Scan0,
        stride * bmpData.Height
        );

    // アンロック
    bmp.UnlockBits(bmpData);
}

このメソッドの二重ループの部分

for (int y = 0; y<height; y++)
{
    int lineIndex = stride * y;

    for (int x = 0; x<width* channel; x++)
    {
        int value = picData[lineIndex + x];
        value = (int)(value* scale + offset);
        value = (value< 0) ? 0 : (value > 255) ? 255
                : value
                ;
        picData[lineIndex + x] = (byte)value;
    }
}

をParallel.Forを使って並列処理にすると

Parallel.For(0, height, y =>
{
    int lineIndex = stride * y;

    for (int x = 0; x<width* channel; x++)
    {
        int value = picData[lineIndex + x];
        value = (int)(value* scale + offset);
        value = (value< 0) ? 0 : (value > 255) ? 255
                : value
                ;
        picData[lineIndex + x] = (byte)value;
    }
}
);

となります。

実際に、このメソッドを使って処理を行い、5回の平均処理時間を比べたところ

通常のfor文 Parallel.For
197.2msec 92.4msec

CPU:i7-7700K@4.2GHz(4コア)

処理画像サイズ:6000 x 4000 x 24bitカラー

という結果になりました。

 

評価ソフトのイメージ

(処理前) (処理後)

 

Parallel.Forを使うとCPUのコア数分ぐらいは速くなるのか?と期待していたのですが、他の処理を行っても、だいたい2倍ちょっと速くなるようです。

 

また、Parallel.Forを使い始めたばかりだとやってしまいがちな定番のミスですが、
ここでのサンプルの lineIndexvalue をParallel.Forよりも前で定義してしまうと、正しい結果を得られなくなるので、ご注意下さい。

 

← C#2.0からの脱却 へ戻る

【C#】24bitと32bitカラー画像の表示速度の比較

カラー画像の24bitカラーと32bitカラーの画像はR,G,Bの成分はそれぞれ8bitで表現され、32bitの時に、残りの8bit部分で画像の透過率として使われる場合もありますが、画像処理的には、あまり使われる事はありません。

そのため、カラー画像と言えばメモリ容量の少ない24bitカラー画像を扱う事が、個人的には多かったのですが、GPU処理やSIMD処理などを考慮すると、メモリアクセス的に有利な32bitカラーの方が都合が良い場合もあります。

 

そこで今回は、24bitカラーと32bitカラーとで、表示速度に違いがあるか?調べてみました。

 

評価に用いたプログラムのイメージはこちら↓

 

プログラムは以下のような物で、PictureBoxのImageに指定するBitmapクラスオブジェクトのPixelFormatが

  • Format24bppRgb
  • Format32bppRgb
  • Format32bppArgb
  • Format32bppPArgb

の違いによって違うか?

また、表示する画像データが24bitのカラー画像と32bitのカラー画像で違いが出るか?を検証してみました。

 

 

表示に用いた画像サイズは1024×1024画素で24bitと32bitのカラー画像となります。

この画像を1000回表示した時の表示時間から1秒あたりに表示できた画像枚数(フレームレート[fps])を算出し、この値を5回計測し平均したものが以下の結果となります。

 

24bitカラー画像 32bitカラー画像
Format24bppRgb 139.7 142.3
Format32bppRgb 140.3 150.1
Format32bppArgb 123.6 126.6
Format32bppPArgb 182.0 187.8

 

これを見ると、32bitカラー画像の方が少し表示速度が速く、Graphicsオブジェクトに用いたPixelFormatは Format32bppPArgb が少し突出して速い事が分かりました。

 

この Format32bppPArgb とは何か?調べてみると

 

1 ピクセルあたり 32 ビットの形式であることを指定します。つまり、アルファ、赤、緑、および青のコンポーネントに、それぞれ 8 ビットを使用します。 アルファ コンポーネントに応じて、赤、緑、および青のコンポーネントが事前乗算されます。

 

とのこと。

いまいち理解できませんが、事前乗算している事で速いのか??

 

という事で、FromImageメソッドで使うGraphicsオブジェクトは Format32bppPArgb を使うと良さそうです。

 

少し前(32bitOSがメインだった時代)では、搭載できるメモリサイズは2GBだし、実質的にプログラムが使用できるのは1GB程度しか無かったため、メモリサイズはできるだけ節約したかったため、カラー画像と言えば24bitと思っていたのですが、今では64bitOSが当たり前で、メモリも4~8GBぐらいは普通に搭載されているので、32bitカラーデータもありですね。

 

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

【C#3.0~】拡張メソッド

C#3.0(Visual Studio 2008)からは、拡張メソッドよ呼ばれる、既存の型やクラスにメソッドを追加することが出来るようになりました。

 

と、聞くと、クラスを継承すれば出来るでしょ!という意見があろうかと思いますが、拡張メソッドでは、そもそも継承できない sealed のクラスに対しても、元の型やクラスを変更することなく、メソッドを追加することが出来ます。

 

この拡張メソッドの記述方法は

staticクラスに、staticのメソッド(これが拡張メソッドとなる)を追加し、最初の引数に拡張元の型やクラスの引数を書き、型やクラスの前に this を付けます。

 

具体的には以下のようになります。

static class ExtensionMethod
{
    public static void ScaleAt(
        this System.Drawing.Drawing2D.Matrix matrix, 
        float scaleX, 
        float scaleY, 
        System.Drawing.PointF point
        )
    {
        // 原点へ移動
        matrix.Translate(-point.X, -point.Y, System.Drawing.Drawing2D.MatrixOrder.Append);
        // 倍率変換
        matrix.Scale(scaleX, scaleY, System.Drawing.Drawing2D.MatrixOrder.Append);
        // 元の位置へ戻す
        matrix.Translate(point.X, point.Y, System.Drawing.Drawing2D.MatrixOrder.Append);
    }
}

この例は、System.Drawing.Drawing2D.Matrixクラスに、指定した点を中心に拡大縮小を行うメソッド(ScaleAt)を追加しています。

(指定した点を中心に回転するRotateAtメソッドはあるのに何で??と思っていたんですよ)

 

この拡張メソッドを使用する側では、以下のように、通常のMatrixクラスのRotateAtメソッドと同じように、今回拡張したメソッド(ScaleAt)を使う事が出来るようになります。


var mat = new System.Drawing.Drawing2D.Matrix();

// (20,30)を中心に30°回転(通常のメソッド)
mat.RotateAt(30, new PointF(20, 30), System.Drawing.Drawing2D.MatrixOrder.Append);

// (100, 150)を中心にX方向に2倍、Y方向に3倍(拡張メソッド)
mat.ScaleAt(2, 3, new PointF(100, 150));

 

← C#2.0からの脱却 へ戻る

【C#6.0~】文字列補間($を使った文字列書式設定)

C#6.0(Visual Studio 2015)からは$を使った文字列の書式設定を行う事ができます。

 

C#6.0より前では、String.Format を使って

double a = 355;
double b = 113;
double c = a / b;
var str = String.Format("a = {0}, b = {1}, a / b = {2}", a, b, c);
Console.WriteLine(str); // a = 355, b = 113, a / b = 3.14159292035398

と書いていたところを、C#6.0からは $”{xxx}” を使って

double a = 355;
double b = 113;
double c = a / b;
var str = $"a = {a}, b = {b}, a / b = {c}";
Console.WriteLine(str); // a = 355, b = 113, a / b = 3.14159292035398

のように $ の後に ” ” で文字列を囲って、変数部分は {}の中に直接変数を記入すると、文字列として、文字を連結してくれます。

とても直観的だし、こちらの方が自然な書き方ですよね。

(参考)

https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/tokens/interpolated

 

文字列の書式設定もString.Formatで用いていた文字と同じ書式設定子が使えて、{}内の文字列に : (コロン) を付けて書式指定の文字を記入します。

double a = 355;
double b = 113;
double c = a / b;
var str = $"a = {a:F3}, b = {b:F3}, a / b = {c:F6}";
Console.WriteLine(str); // a = 355.000, b = 113.000, a / b = 3.141593

書式の文字については、以下のページが参考になります。

標準の数値形式文字列

カスタム数値形式文字列

標準の日時形式文字列

カスタム日時形式文字列

書式設定操作の実行

 

← C#2.0からの脱却 へ戻る

【C#4.0~】引数の省略と名前付き引数

C#4.0(Visual Studio 2010)からは引数の省略および名前付き引数が使えるようになりました。

 

引数の省略

なにはともあれ、サンプルをご覧ください。

public static double AddScale(double a, double b, 
                              double scale = 1.0, double offset = 0.0)
{
    return (a + b) * scale + offset;
}

このように、 引数=初期値 のように書くと、その引数は省略して呼びだすことができます。

呼び出し側は

double ans;
ans = AddScale(10, 5);            // ans = 15.0
ans = AddScale(10, 5, 1.0);       // ans = 15.0
ans = AddScale(10, 5, 1.0, 0.0);  // ans = 15.0

のようにすると、どれも同じ結果となります。

もちろん、初期値以外を以下のように

var ans = AddScale(10, 5, 2.0, 1.0); // ans = 31.0

指定することもできます。

 

ただし、省略する引き数の次に省略しない引数を続けることはできません。


public static double AddScale(double a, double b, 
                              double scale = 1.0, double offset)  // これはエラー
{
    return (a + b) * scale + offset;
}

 

名前付き引数

今度はメソッドを呼び出す側のテクニックです。

サンプルとして、前回と同じメソッドを用います。

public static double AddScale(double a, double b, 
                              double scale = 1.0, double offset = 0.0)
{
    return (a + b) * scale + offset;
}

名前付き引数では、引数の名前にコロン(:)を付けてメソッドを呼び出します。

var ans = AddScale(a: 10, b: 5, scale: 1.0, offset: 0.0); // ans = 15.0

この例だと面倒なだけですが、名前を付けて呼び出すと、引数を書く順番はどうでもよくなります。

var ans = AddScale(scale: 2.0, offset: 5.0, b: 5, a: 10); // ans = 35.0

また、引数の省略も同様に出来て、

var ans = AddScale(a: 10, b: 5, offset: 2.0); // ans = 17.0

var ans = AddScale(10, 5, offset: 3.0); // ans = 18.0

と書くことができます。

最初の引数の省略だけでは引数の順番を飛ばして省略(scaleだけを省略)することは出来ませんでしたが、名前付き引数では引数を飛ばして省略することが可能になります。

 

(参考)

https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments

 

← C#2.0からの脱却 へ戻る

【C#3.0~】varによる暗黙的な型宣言

C#3.0(Visual Studio 2008)からはvarを用いた暗黙的な型宣言を行う事ができます。

 

例えば、以下のような例

string text = "Sample";

では text の型は = の右辺を見ると 文字列型(string)である事は明白なので、 var を使って

var text = "Sample";

と書くことができます。

一番、使い勝手の良い使い方は、クラスを new する時に、

System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

と書いていたところを var を用いると

var sw = new System.Diagnostics.Stopwatch();

となります。

他にも

List<string> a = new List<string>();

が var だと

var a = new List<string>();

となります。

 

逆に使えないのは、

var a; // これはエラーになる

のように = で初期化されていないと、 var 単独では型がわからないためエラーとなります。

 

他にもクラスのフィールドでは用いる事ができず、メソッド内のローカル変数のみの対応となります。

class Test
{
    var a = "Sample";   // エラー

    public Test()
    {
        var b = "Sample";   // OK
    }
}

 

C#2.0からの脱却 へ戻る

C#2.0(Visual Studio 2005)からの脱却

私が本格的にC#を始めたのはC#2.0(Visual Studio 2005)からなのですが、会社でプログラムをしていると、お客様向けのサンプルプログラムを作成することから、むやみにVisual Studioのバージョンを上げる事もできず、Visual Studio 2005をかなり長い事使用してきました。

現在ではVisual Studio 2015をメインに使用しているのですが、C#の構文はC#2.0をひきずっておりまして。。

最近では、お客様が使っているVisual Studioは古くてもVisual Studio 2010ぐらいでしょうか。

ここでは、少しずつ、便利な機能は取り入れていこう!というのが目的でC#2.0より新しい構文についてまとめていこうかと思います。

 

varによる暗黙的な型宣言 C#3.0~
自動実装プロパティ C#3.0~
拡張メソッド C#3.0~
Parallel.Forによる並列処理 C#4.0~
引数の省略と名前付き引数 C#4.0~
文字列補間($を使った文字列書式設定) C#6.0~

ちなみに、C#のバージョンとVisual Studioのバージョンの対応は以下の通りです。

C#2.0 Visual Studio 2005
C#3.0 Visual Studio 2008
C#4.0 Visual Studio 2010
C#5.0 Visual Studio 2012,2013
C#6.0 Visual Studio 2015
C#7.0 Visual Studio 2017
C#8.0 Visual Studio 2019

(参考)https://en.wikipedia.org/wiki/C_Sharp_(programming_language)#Versions

 

画像処理100本ノックをC#でやってみた

画像処理100本ノック

https://github.com/yoyoyo-yo/Gasyori100knock

https://qiita.com/yoyoyo_/items/2ef53f47f87dcf5d1e14

 

というページがあるのをご存じでしょうか?

最近の画像処理と言えば、OpenCVをPythonでやってみた!

という情報にあふれているのですが、この画像処理100本ノックでは、画像処理の処理部分はOpenCVに頼らずにベタにPythonやC++でプログラムしています。

 

実際、今どきは画像処理のプログラムをベタに組んだところで、車輪の再開発になるだけで意味があるのか??という意見もあろうかと思いますが、1度はやっておくと画像処理の理解が深まるので、おススメです。

 

そこで私も画像処理100本ノックの問題をC#で作ってみました。

ソースコードはGitHubに置いてあります。

https://github.com/ImagingSolution/ImagingDotNet

 

表向きは画像処理100本ノックに沿ってプログラムしていますが、目指すところはC#だけで、そこそこ処理が速くて、OpenCVのPython版のように簡単に使えるC#の画像処理ライブラリ(ImagingDotNet)を作るのが目標です。

本気で使うとなると、エラー処理は甘いし、処理もすごく速い訳ではないので、あくまでも、C#で作る画像処理プログラムのお勉強用です。

 

Q.1.チャンネル入れ替え

var src = new Bitmap("Lena.bmp");
var dst = ImagingDotNet.CvtColor(src, ImagingDotNet.COLOR_BGR2RGB);
入力 出力

C# >> CvtColor.cs cvt_COLOR_BGR2RGB部分

 

Q.2.グレースケール化

var src = new Bitmap("Lena.bmp");
var dst = ImagingDotNet.CvtColor(src, ImagingDotNet.COLOR_BGR2GRAY);
入力 出力

C# >> CvtColor.cs cvt_COLOR_BGR2GRAY部分

 

Q.3.二値化

(参考)https://imagingsolution.net/imaging/binarization/

var src = new Bitmap("Lena.bmp");
var dst = (Bitmap)src.Clone();
ImagingDotNet.Threshold(src, dst, 128, 255, ImagingDotNet.THRESH_BINARY);
入力 出力

C# >> Threshold.cs

 

Q.4.大津の二値化

(参考)https://imagingsolution.net/imaging/discriminant-analysis-method/

var src = new Bitmap("Lena.bmp");
var gray = ImagingDotNet.CvtColor(src, ImagingDotNet.COLOR_BGR2GRAY);
var dst = (Bitmap)src.Clone();
ImagingDotNet.Threshold(gray, dst, 128, 255, ImagingDotNet.THRESH_OTSU);
入力 出力

C# >> Threshold.cs

 

 

4問やったところで、力尽きました…

100問分、問題を考えてプログラムを作成した100本ノックの著者は、ほんとスゴイ!!

 

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

【C#】画像の輝度値の取得設定速度の比較

C#で画像の輝度値を取得/設定を行う場合は、GetPixel、SetPixelメソッドを使うと遅いのでLockBits~UnlockBitsでポインタをむき出して輝度値の取得/設定を行うのが定番となっていますが、自分自身でGetPixel、SetPixelメソッドを使った事が無かったので、いくつかの方法で処理時間を比較してみました。

 

【評価環境】

OS Windows10 64bit ver.1809
CPU Intel Core i7-7700K 4.2GHz
メモリ 32GB
.NET Framework 4.5.2
プラットフォーム Any CPU(32ビットを優先)
評価画像 jpeg 6000×4000画素 24bitカラー

です。

 

評価プログラムとしては、カラー画像の輝度値をR,G,Bそれぞれ取得し、値を明暗反転して輝度値を入れなおすプログラムを作成しています。

 

処理結果は、以下のようにポジ→ネガ→ポジ→ネガと繰り返すようになっています。

 

処理時間は処理を5回行い平均した時間となります。

 

 

GetPixel、SetPixelを使った方法

平均処理時間は27439msec

本当に遅かった。。

しかもGetPixel、SetPixelではモノクロ画像(Format8bppIndexed)に対応していないので、モノクロ画像も多く扱う画像処理には不向きとなります。

 

LockBits~UnlockBitsでポインタ(Scan0)を取得し、データを配列にコピーしてから処理を行い、結果を元にコピーしなおす方法

平均処理時間は101msec

さすがにGetPixel、SetPixelよりはぜんぜん速いです。

 

MarshalクラスのReadByte、WriteByteメソッドでポインタ(Scan0)に直接、値を読み書きする方法

平均処理時間は273.6msec

この方法に少し期待していたのですが、配列にコピーした方が速かった。。

 

unsafeを使ったポインタ(Scan0)を直接読み書きする方法

平均処理時間は25.4msec

やっぱりunsafeはできれば使いたくないのですが、ここまで速いと使うのもあり??

ポインタを使うなら、C言語のライブラリにしておきたい気もしますが、C#だけで完結できるのもちょっと惹かれます。

 

ポインタで読み書きし、Parallel.Forをつかって処理を並列化する方法

平均処理時間は11.4msec

もう、unsafeは使うしかないでしょ!というレベルですが、やっぱり抵抗がある。。

 

処理を呼び出す側のプログラム

 

まとめ

それぞれの処理時間をまとめると以下の通りでした。

 

方法 処理時間(msec)
GetPixel、SetPixel 27439
LockBits、UnlockBitsで配列を介して処理 101
MarshalクラスのReadByte、WriteByte 273.6
unsafeのポインタで参照 25.4
unsafeのポインタの並列処理 11.4

 

結局GetPixel、SetPixelは論外でした。

あとは好みというか、ポリシーというか、意見の分かれるところだと思いますが、ポインタで処理するのは、やはり魅力です。

 

(2019.11.8追記)

.NET Framework4.5.232ビットを優先で評価を行っていたので、この部分を変えると処理速度に差が出るか?確認しました。

 

.NET Framework 4.7.2  32ビットを優先

方法 処理時間(msec)
GetPixel、SetPixel 27293.4
LockBits、UnlockBitsで配列を介して処理 101.8
MarshalクラスのReadByte、WriteByte 290.8
unsafeのポインタで参照 24.4
unsafeのポインタの並列処理 9.8

 

.NET Framework 4.7.2  32ビットを優先なし

方法 処理時間(msec)
GetPixel、SetPixel 21281.2
LockBits、UnlockBitsで配列を介して処理 85.4
MarshalクラスのReadByte、WriteByte 213.6
unsafeのポインタで参照 27.2
unsafeのポインタの並列処理 9.2

 

となりました。

ということで、.NET Frameworkの4.5.2と4.7.2の差はほとんどありませんでしたが、「32ビットを優先」のあるなしでは優先しない(64bitで動作)方が少し速い結果となりました。

 

関連記事

【C#】Bitmap画像データのメモリ構造

 

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

【C#】ボタンの背景色を元に戻す方法

C#でボタンの背景色を元に戻す方法!

とか言って、ボタンのBackColorプロパティをシステムのControlに設定すればいいんでしょ?!

と思っていたのですが、実際にボタンの背景色を別の色に設定し、元のシステムのControlに戻してみると、下図のように初期状態(button2が初期状態)に比べて、少し明るい色になってしまいます。

 

これを回避するには、UseVisualStyleBackColorプロパティをTrueに設定することでボタンの背景色が元に色に戻す事ができます。

 

 

元の色に戻した結果がこちら↓

 

このUseVisualStyleBackColorプロパティはボタンの背景色(BackColorプロパティ)を変更後、勝手にFalseに変わってしまうので、元のTrueに変更する必要があります。

 

そもそもUseVisualStyleBackColorプロパティとは何?

と思い、調べてみたのですが、

 

visual スタイルがサポートされている場合に、visual スタイルを使用して背景を描画するかどうかを決定する値を取得または設定します。

 

という説明だったのですが、結局、良く分からず。。

 

という事で、詳細は分からないのですが、ボタンのBackColorプロパティをシステムカラーのControlに設定する時は、UseVisualStyleBackColorプロパティもTrueに設定すること!と覚えておくしかなさそうです。

 

この設定をコードで書くと、

 

button1.BackColor = SystemColors.Control;
button1.UseVisualStyleBackColor = true;

 

となります。

C#から使うC++ライブラリ(DLL)の作成方法

前回はC#で書かれたライブラリの作成方法でしたが、今回は、C#から使えるC++で書かれたライブラリの作成方法です。

C++と言っても、C++内部でクラスを使う事は可能ですが、C#から直接呼ぶ事が出来るのは、クラスではない関数のみとなります。
そのため、クラスオブジェクトをC++のライブラリとC#間でやり取りすることは出来ないので、注意してください。(構造体を引数で渡す事は可能です。)

 

正直、C++のライブラリの作成方法はあまり深く理解していないのですが、メモ程度に。。

 

事前にVisual Studio をデフォルトのままでインストールするとC++はインストールされないので、C++をインストールしておいてください。

 

まず、関数を呼び出すC#のプロジェクトを作成しておき、ソリューションの右ボタンで、

追加→新しいプロジェクト

を選択します。

 

次にプロジェクトを追加しますが、Visual Studio のバージョンにより画面が異なるため、各バージョンに合わせて確認ください。

Visual Studio 2017, 2019の場合

Visual Studio 2019には、上部に言語、OS別表示のフィルタをかけることが出来るので、C++、Windowsを選択します。

次にプロジェクトの一覧の中から Windowsデスクトップウィザードを選択します。

次にプロジェクト名とプロジェクトの場所を指定します。
プロジェクト名は、デフォルト設定ではライブラリ名になるので、ちゃんとした名前を付ける事をお勧めします。

次に アプリケーションの種類に ダイナミックリンクライブラリ(.dll) を選択し、追加オプションでは、
プリコンパイル済みヘッダー(P)シンボルのエクスポートにチェックを入れます。

→以降は、以下共通を参照ください。

Visual Studio 2015の場合

Visual C++ → Win32 → Win32プロジェクト

と選択し適当な名前(今回は CppDLL とします。)を付けOKボタンをクリックします。

 

表示されたウィザードで次へをクリックします。

 

アプリケーションの種類で DLL を選択し、慣れている人は 空のプロジェクト でいいんでしょうけど、私は慣れていないので、 シンボルのエクスポート と プリコンパイル済みヘッダー にチェックを入れ、完了をクリックします。

以下共通

ウィザードでDLLを作成すると、プログラムのたたき台となるサンプルコードを吐き出してくれるので、慣れないうちは、ウィザードを使った方がお勧めです。(私は、今でも使ってますが。。)

 

次にプロジェクトの設定を行います。

 

今どきのOSはほとんど64bitなので、今回は64bitOS用のライブライを作成するのを前提とします。

Visual Studio の 構成マネージャー をクリックします。

 

プラットフォームに x64 を選択します。

ビルドの部分いチェックが入っていない場合はチェックを入れます。

これを アクティブソリューションの構成のDebugReleaseの両方で行います。

 

これでけで、ソリューションのビルドを行うと、ソリューションフォルダの直下にx64フォルダが作成されます。

さらにその中のReleaseもしくはDebugフォルダの中に、このように↓dllファイルが作成されてますが、C#から、このdllファイルを使うにはさらに細工が必要になります。

 

以降は、C++プロジェクトの作成時にウィザードが吐き出した

  CPPDLL_API int fnCppDll(void);

の関数がC#から使えるようにC++のヘッダファイルを変更します。

 

C++ライブライのヘッダファイル(ここでは CppDll.h)を開き、C#から使う関数を

#ifdef  __cplusplus
extern "C" {
#endif	/* __cplusplus */

// ここに使用する関数を定義する

#ifdef  __cplusplus
}		/* extern "C" */
#endif	/* __cplusplus */

のように  extern “C” { } で関数の定義を挟み込みます。

こんな感じ↓

#ifdefの部分は、あんまり分かってない。。

 

次に作成したC++のライブラリファイル(*.dll)がC#から使用できるようにします。

ここは、いくつかやり方があり、好みが分かれるところだと思いますが、今回は、C++のプロジェクトが作成したDLLファイルの出力先をC#の出力先にに合わせるように変更します。

 

C++のプロジェクト名の部分を右クリックし、

  構成のプロパティ→全般→出力ディレクトリ

  $(SolutionDir)\WindowsFormsApplication1\bin\$(Configuration)\

とします。(※WindowsFormsApplication1はC#のプロジェクト名です。)

 

この出力ディレクトリの設定を 構成の Release と Debug の両方設定します。

 

これで、C#のプログラムから参照できる位置(C#のexeファイルと同じフォルダ)にdllファイルが出力されます。

 

この設定を間違うと、以下のようなエラーが表示されます。

 

今回は64bitOS用のDLLとして作成していますが、C#のプロジェクトはOSが64bitであってもプロジェクトが初期設定状態だと、32bitで動作するので、これを64bitで動作するように変更します。

 

C#のプロジェクト名を右クリックし、 プロパティ をクリックし、以下のウィンドウを表示させ、 左側メニューの ビルド を選択し 32ビットを優先 のチェックを外します。

この設定を 構成 の Debug および Release の両方行います。

 

この設定を間違うと、 BadImageFormatExceptionはハンドルされませんでした。 というエラーメッセージが表示れます。

 

これで、とりあえずC#からC++の関数が動作します。

C#からは、以下のようなコードでC++の関数定義を行います。

using System.Runtime.InteropServices;

[DllImport("CppDll.dll")]
public static extern int fnCppDll();

“CppDll.dll”の部分は適宜、作成したdllファイル名に合わせて下さい。

 

実際に関数を呼び出す部分は、

private void button1_Click(object sender, EventArgs e)
{
    int n = fnCppDll();

    MessageBox.Show(n.ToString());
}

のように、ボタンクリックイベントで呼び出したとすると、以下のように表示されると動作はOKです。

 

さらに、これだけど少し足りなくて、C++のプロジェクトのプロパティで、

構成プロパティ→C/C++→コード生成→ランタイムライブラリ

の部分で、構成が

Releaseのとき:マルチスレッド(/MT)

Debugのとき:マルチスレッドデバッグ(/MTd)

に変更しておきます。

この設定をしないと、別のPCでプログラムを実行した際に、開発を行ったVisual Studioと同じバージョンのC++ランタイム(再頒布可能パッケージ)がインストールされていないと動作してくれないので、いざ、実行環境で動作させようとした時にハマったりもします。

 

以上、少し怪しい部分もあるかと思いますが、こんな感じで。。

 

関連記事

【C#エラー】System.BadImageFormatException 間違ったフォーマットのプログラムを読み込もうとしました。

C#ライブラリ(DLL)の作成方法

C#から使う、C#で書かれたライブラリ(*.dll)の作成方法です。
C#から使う、C言語ライブラリの作成方法はこちらを参照ください。

 

まず、ライブラリを呼び出す側のプロジェクトを作成します。

 

ここでは、Windowsフォームアプリケーションを作成するのに、

テンプレート→Visual C#→Windows→Windowsフォームアプリケーション

と選択し、名前を適当に付けてOKボタンをクリックします。

 

これで、呼び出し側のプロジェクトが作成されました。

 

次に、C#ライブラリ用のプロジェクトを作成します。

今、作成したプロジェクトの1つ上の階層に作成されている ソリューション の文字を右クリックし、

追加→新しいプロジェクト

を選択します。

 

今度はライブラリを作成するので、

  Visual C#→Windows→クラスライブラリ

と選択し、名前を適当に付けてOKボタンをクリックします。

 

すると、最小限のコードが生成されます。

 

通常は、Class1とかいうクラス名ではなく、もっとわかりやすい名前に変えますが、とりあえず、このまま次に進みます。

 

次に、2つの値を足すだけの簡単なメソッドを追加してみます。

 

この状態で、ビルドすると、ライブラリのプロジェクトフォルダのbinフォルダのDebugもしくはReleaseフォルダの中にライブラリファイル(CSharpDll.dll)が作成されます。

 

しかし、それだけだと、ライブラリを呼び出す元のプロジェクト(ここではWindowsFormsApplication1)からは使う事ができないため、参照の設定を行います。

 

参照は呼び出す元の方のプロジェクトにある、参照の文字を右クリックし、参照の追加を選択します。

 

ここで、ライブラリファイルを選択する方法もあるのですが、プログラム作成中はライブラリのプロジェクトを参照した方がデバッグ等が便利なので、プロジェクトの参照を行います。

 

  プロジェクト→ソリューション

 

と選択すると、ライブラリのプロジェクト名が表示されているので、プロジェクト名の左側にあるチェックボックスにチェックを入れ、OKボタンをクリックします。

 

これで、参照元に参照先のライブラリ名(ここではCSharpDll)が追加され、ライブラリが使用できる状態になります。

 

 

試しにフォーム上にボタンを配置します。

 

 

ボタンをダブルクリックし、作成されたクリックイベント内に、今、作成したライブラリのメソッドを追加してみます。

 

これで、ボタンをクリックした時に、以下のように表示されれば、C#ライブラリの作成は成功です。

 

ここでは、ライブラリのプロジェクトの参照として行いましたが、こうすることで、ライブラリのメソッド内へのステップイン実行もできるようになるので、かなり便利です。

 

一つポイントとしては、デフォルトのまま、ライブラリを作成すれば特に問題は無いのですが、ライブラリのプロジェクトのプラットフォームはAnyCPUにしておくのをお勧めします。

 

こうしておく事で、呼び出し元のプロジェクトがx86だろうが、x64であっても、同一のライブラリファイル(ここではCSharpDll.dll)を使用することができるようになります。

これを間違うと、x86用のdllファイル、x64用のdllファイルのそれぞれを作成する必要が出てくるので、少々面等になります。

【C#】文字列の回転描画

文字列を回転して描画するのはGraphicsオブジェクトをワールド変換して描画することも可能ですが、ワールド変換はGraphicsオブジェクト全体の座標系が変換されてしまうため、少々使いづらく感じます。

そこで、文字だけを回転するGraphicsPathを使った方法で関数にまとめてみました。

この関数で、文字の描画位置、回転、回転の基準位置(左上、中心など)を設定できるようにしています。

 

実行結果の画面

 

サンプルプログラム

private void Form1_Paint(object sender, PaintEventArgs e)
{
    var g = e.Graphics;
    // アンチエイリアスの設定
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

    // グリッド線の描画(50画素ピッチ)
    for (int i = 0; i < this.Width; i += 50)
    {
        g.DrawLine(Pens.Black, i, 0, i, this.Height);
    }
    for (int j = 0; j < this.Height; j += 50)
    {
        g.DrawLine(Pens.Black, 0, j, this.Width, j);
    }

    var font = new Font("Arial", 20);

    // 文字の中心の座標が(100, 100)を中心に-30度回転
    var format = new StringFormat();
    format.Alignment = StringAlignment.Center;      // 左右方向は中心寄せ
    format.LineAlignment = StringAlignment.Center;  // 上下方向は中心寄せ
    DrawString(
        g,
        "[文字の中心が基準]",
        font,
        new SolidBrush(Color.Blue),
        100, 100,
        -30,
        format
        );

    // 文字の左上の座標が(200, 100)を中心に45度回転
    format.Alignment = StringAlignment.Near;      // 左右方向は左寄せ
    format.LineAlignment = StringAlignment.Near;  // 上下方向は上寄せ
    DrawString(
        g,
        "[文字の左上が基準]",
        font,
        new SolidBrush(Color.Red),
        200, 100,
        45,
        format
        );
}

/// <summary>
/// 文字列の描画、回転、基準位置指定
/// </summary>
/// <param name="g">描画先のGraphicsオブジェクト</param>
/// <param name="s">描画する文字列</param>
/// <param name="f">文字のフォント</param>
/// <param name="brush">描画用ブラシ</param>
/// <param name="x">基準位置のX座標</param>
/// <param name="y">基準位置のY座標</param>
/// <param name="deg">回転角度(度数、時計周りが正)</param>
/// <param name="format">基準位置をStringFormatクラスオブジェクトで指定します</param>
public void DrawString(Graphics g, string s, Font f, SolidBrush brush, float x, float y, float deg, StringFormat format)
{
    using (var pathText = new System.Drawing.Drawing2D.GraphicsPath())  // パスの作成
    using (var mat = new System.Drawing.Drawing2D.Matrix())             // アフィン変換行列
    {
        // 描画用Format
        var formatTemp = (StringFormat)format.Clone();
        formatTemp.Alignment = StringAlignment.Near;        // 左寄せに修正
        formatTemp.LineAlignment = StringAlignment.Near;    // 上寄せに修正

        // 文字列の描画
        pathText.AddString(
                s,
                f.FontFamily,
                (int)f.Style,
                f.SizeInPoints,
                new PointF(0, 0),
                format);
        formatTemp.Dispose();

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

        // 回転中心のX座標
        float px;
        switch (format.Alignment)
        {
            case StringAlignment.Near:
                px = rect.Left;
                break;
            case StringAlignment.Center:
                px = rect.Left + rect.Width / 2f;
                break;
            case StringAlignment.Far:
                px = rect.Right;
                break;
            default:
                px = 0;
                break;
        }
        // 回転中心のY座標
        float py;
        switch (format.LineAlignment)
        {
            case StringAlignment.Near:
                py = rect.Top;
                break;
            case StringAlignment.Center:
                py = rect.Top + rect.Height / 2f;
                break;
            case StringAlignment.Far:
                py = rect.Bottom;
                break;
            default:
                py = 0;
                break;
        }

        // 文字の回転中心座標を原点へ移動
        mat.Translate(-px, -py, System.Drawing.Drawing2D.MatrixOrder.Append);
        // 文字の回転
        mat.Rotate(deg, System.Drawing.Drawing2D.MatrixOrder.Append);
        // 表示位置まで移動
        mat.Translate(x, y, System.Drawing.Drawing2D.MatrixOrder.Append);

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

        // 描画
        g.FillPath(brush, pathText);
    }
}

プログラムの解説

回転中心の座標(表示位置の座標)をx, y で指定します。

文字の回転角度(deg)は時計方向が正となります。

文字列の回転の基準位置(回転の中心の位置)はStringFormatクラスで指定します。

StringFormat.Alignmentプロパティが左右方向の基準位置で、

StringAlignment.Near 左寄せ
StringAlignment.Center 中心寄せ
StringAlignment.Far 右寄せ

となります。

同様にStringFormat.LineAlignmentプロパティが上下方向の基準位置で、

StringAlignment.Near 上寄せ
StringAlignment.Center 中心寄せ
StringAlignment.Far 下寄せ

となります。

 

これを使うと、こんな時計の文字盤のように文字を配置するのも簡単に書くことができます。

 

関連記事

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

【C#】GraphicsPath

【C#】GraphicsPathの描画

【C#】寸法線の描画

 

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

【C#】メモリの値コピー、ポインタ、1次元、2次元、3次元配列間

C#でメモリのポインタ(IntPtr)と一次元配列間の値のコピーにはMarshal.Copyメソッド(名前空間:System.Runtime.InteropServices)を用います。

Marshal.Copyにはポインタから一次元配列へのコピー および 一次元配列からポインタへのコピーが用意されています。

一次元配列の型は、Single[], IntPtr[], Byte[], Single[], Int64[] , Int32[] , Int32[] , Int16[] , Double[]が指定可能です。

Mershal.Copyが可能なのは、あくまでも一次元配列が対象となります。

 

多次元配列(1次元、2次元、3次元…)と多次元配列間の値のコピーにはBuffer.BlockCopyメソッド(名前空間:System)を用います。

このメソッドでは、異なる次元の配列間、異なる型の配列間でメモリをコピーしてくれます。

異なる型の場合は、値のコピーではありません。

どちらかというと、C言語のCopyMemoryの挙動に似ています。

 

サンプルプログラム

// 一次元配列、二次元配列、三次元配列を確保(全て24バイト)
var arr1D = new byte[24] ;
var arr2D = new byte[6, 4];
var arr3D = new byte[2, 3, 4];
var arrUS = new ushort[12];

// メモリの確保(24バイト)
IntPtr ptr = Marshal.AllocHGlobal(24);

// 値の代入
for (byte i = 0; i < 24; i++)
{
    Marshal.WriteByte(ptr, i, i);
}
// ポインタから一次元配列へコピー
Marshal.Copy(ptr, arr1D, 0, arr1D.Length);

// 一次元配列から二次元配列へコピー
Buffer.BlockCopy(arr1D, 0, arr2D, 0, arr1D.Length * sizeof(byte));
// 二次元配列から三次元次元配列へコピー
Buffer.BlockCopy(arr2D, 0, arr3D, 0, arr2D.Length * sizeof(byte));
// byte配列からushort配列へコピー
Buffer.BlockCopy(arr1D, 0, arrUS, 0, arr1D.Length * sizeof(byte));

// メモリの解放
Marshal.FreeHGlobal(ptr);

 

実行後の配列の値は以下のようになります。

 

ushort配列の結果が少しわかりづらいですが、byte配列の2バイト分が1つのushortの値として格納されます。

 

 

参考ページ

【C#】配列の中身をメモリで確認する方法