F#からWPFのGUIを扱う その3
WPF の Image コントロールに、動的に生成したビットマップ画像を表示させるコードを書いてみます。
生成する画像については「Windows Presentation Foundation 4.5 Cookbook」Chapter 9 : Graphics and Animation / Manipulating a bitmap programmatically にあるマンデルプロ集合のビットマップ画像表示サンプルを一部お手本にします。
WPF の GUI 部分はC#のプロジェクトで作成し、それをF#のプロジェクトから操作出来るように「F#からWPFのGUIを扱う その1」と同様の手順で Visual Studio 2013 のソリューションを作成します。
「F#からWPFのGUIを扱う その1」に合わせて、 WPF を F# で操作するためのソリューションを作る作業手順は以下の図のような流れで行っています。
今回も同様の手順で C#のプロジェクト名は WpfTest としています。
新規作成されたC#のプロジェクトのXAMLファイルを以下のように修正します。
600 × 600 ピクセルのサイズの Image コントロールを1つ配置しNameプロパティを「 TestImage1 」とします。F#のコードからアクセスできるようにプロパティ「 x:FieldModifier="public" 」も追加します。
Window のサイズはクライアント領域の Image コントロールのサイズにフィットさせる設定「 SizeToContent="WidthAndHeight" 」とします。
- <Window x:Class="WpfTest.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Mandelbrot Set" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
- <Grid>
- <Image x:FieldModifier="public" Width="600" Height="600" x:Name="TestImage1" />
- </Grid>
- </Window>
F#のプロジェクト WinAppTest において、今回は Bitmap クラス (System.Drawing) オブジェクトへ描画処理を行うのでさらにSystem.Drawing へのアセンブリ参照を追加します。最終的にF#のプロジェクトは以下のような参照設定となります。
ファイル Program.fs に書くコードは以下の通り。
- open System.Numerics
- let mandelbrotColor(c: Complex): int =
- let colorInit = 256.0
- let rec calcColor (z: Complex) (col: double) : int =
- if (z.Real + z.Imaginary < 4.0) && (col > 0.0) then
- calcColor (z * z + c) (col - 1.0)
- else
- int col
- calcColor Complex.Zero colorInit
- let createMandelbrotBitmap(): System.Drawing.Bitmap =
- let drawingBmp = new System.Drawing.Bitmap(600, 600,
- System.Drawing.Imaging.PixelFormat.Format24bppRgb)
- let from = Complex(-1.5, -1.0)
- let dest = Complex(1.0, 1.0)
- let deltax = (dest.Real - from.Real) / (double drawingBmp.Width)
- let deltay = (dest.Imaginary - from.Imaginary) / (double drawingBmp.Height)
- for y = 0 to (drawingBmp.Height - 1) do
- for x = 0 to (drawingBmp.Width - 1) do
- let col = mandelbrotColor(from + Complex(double(x) * deltax, double(y) * deltay))
- drawingBmp.SetPixel(x, y, System.Drawing.Color.FromArgb(col, col, col))
- do
- use g = System.Drawing.Graphics.FromImage(drawingBmp)
- use font = new System.Drawing.Font("Meiryo UI", 25.0f, System.Drawing.GraphicsUnit.Pixel)
- use brush = new System.Drawing.SolidBrush(System.Drawing.Color.Red)
- g.DrawString("マンデルブロ集合", font, brush, 230.0f, 200.0f)
- drawingBmp
- [<System.Runtime.InteropServices.DllImport("gdi32")>]
- extern bool DeleteObject(nativeint hObject)
- [<System.Windows.Data.ValueConversion(typeof<System.Drawing.Bitmap>, typeof<System.Windows.Media.ImageSource>)>]
- type MyBitmapConverter() =
- interface System.Windows.Data.IValueConverter with
- override x.Convert(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
- let drawingBmp = value :?> System.Drawing.Bitmap
- assert(not <| obj.ReferenceEquals(drawingBmp, null))
- let hBitmap: nativeint = drawingBmp.GetHbitmap() // GDIビットマップオブジェクトへのハンドルを取得
- let sourceRect = System.Windows.Int32Rect.Empty
- let sizeOptions = System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions()
- let bmpSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, System.IntPtr.Zero, sourceRect, sizeOptions)
- bmpSource.Freeze()
- DeleteObject(hBitmap) |> ignore // GDIビットマップオブジェクトの後始末
- bmpSource :> obj
- override x.ConvertBack(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
- failwith("not implemented")
- [<EntryPoint; System.STAThread>]
- let main _ =
- let app = new System.Windows.Application()
- let win = new WpfTest.MainWindow()
- use bitmap: System.Drawing.Bitmap = createMandelbrotBitmap()
- let binding = new System.Windows.Data.Binding(
- Source = bitmap,
- Converter = new MyBitmapConverter())
- win.TestImage1.SetBinding(System.Windows.Controls.Image.SourceProperty, binding) |> ignore
- app.Run win
3行目:mandelbrotColor 関数は複素平面上の指定座標における描画色(輝度、256階調)を計算する関数です。
13行目:createMandelbrotBitmap 関数は GDI+ ビットマップ(System.Drawing.Bitmap)を生成します。ビットマップのサイズは 600 × 600 ピクセルです。ビットマップ上すべてのピクセルの色を計算してその値を書き込みます。また、「マンデルブロ集合」という文字列を描き加えます。
ビットマップは、1ピクセルあたり 24 bit、つまりR・G・Bそれぞれに 8 bit を割り当てるカラー画像フォーマットです。ただし、24行目で各ピクセル中のR・G・Bそれぞれに対して等しい輝度の値を与えているのでここで描画されるマンデルプロ集合は見かけ上はモノクロ画像に見えます。
34~35行目: Win32 API の DeleteObject 関数を直接呼び出すための宣言です。MyBitmapConverter クラスのメソッド Convert 内で DeleteObject 関数を呼び出すため必要になります。
38行目:WPF の Image コントロールへのデータバインディングに今回必要な型変換クラス MyBitmapConverter の定義です。System.Drawing.Bitmap オブジェクトを Image コントロールの Source プロパティ(ImageSource クラス)に適合するように型変換を行うクラスです。IValueConverter インターフェイス を実装しています。37行目の ValueConversion アトリビュートによって変換前の型と変換後の型を予め指定する必要があります。
55行目以降がエントリーポイントとなる main 関数です。
57行目:C#のプロジェクトの名前空間「 WpfTest 」にある WPFウィンドウとなる MainWindow クラスのオブジェクトを生成しています。
59行目:System.Drawing.Bitmap オブジェクトの生成。ただしそのままでは Image コントロールの Source プロパティとしては使えない型なので、
60~62行目でBinding オブジェクトを生成する際に、System.Drawing.Bitmap 型を System.Windows.Media.Imaging.BitmapSource 型に変換する MyBitmapConverter クラスオブジェクトを Converter プロパティとして渡します。
63行目:SetBinding メソッドを使って Image コントロール(今回の例では「TestImage1」という名前)の Source プロパティに Binding オブジェクトを付与します。
参考資料:「Windows Presentation Foundation 4.5 Cookbook」Chapter 9 : Graphics and Animation / Manipulating a bitmap programmatically
実行結果
以下のようなウィンドウが表示されます。