Getting a Good Grasp of F# (仮)

関数型言語F#をもっと楽しみたい

F#からWPFのGUIを扱う その3

WPFImage コントロールに、動的に生成したビットマップ画像を表示させるコードを書いてみます。

生成する画像については「Windows Presentation Foundation 4.5 Cookbook」Chapter 9 : Graphics and Animation / Manipulating a bitmap programmatically にあるマンデルプロ集合のビットマップ画像表示サンプルを一部お手本にします。

WPFGUI 部分はC#のプロジェクトで作成し、それをF#のプロジェクトから操作出来るように「F#からWPFのGUIを扱う その1」と同様の手順で Visual Studio 2013 のソリューションを作成します。

F#からWPFのGUIを扱う その1」に合わせて、 WPF を F# で操作するためのソリューションを作る作業手順は以下の図のような流れで行っています。

image/svg+xml C#のプロジェクト WPF アプリケーションとして新規作成 F#のプロジェクト コンソール アプリケーションとして新規作成 クラスライブラリに変更 App.xaml を削除App.config を削除 Windows アプリケーションに変更 スタートアッププロジェクトに設定 C#のプロジェクトをアセンブリ参照に追加 以下をアセンブリ参照に追加 ・WindowsBase・PresentationCore・PresentationFramework・System.Xaml WPF のコントロールを操作するコードを書く XAML デザイナーにてコントロール(UI要素)を配置 XAML ファイル内のコントロールに必要なプロパティを追加 ・x:Name="識別子となる文字列"・x:FieldModifier="public"

今回も同様の手順で C#のプロジェクト名は WpfTest としています。

新規作成されたC#のプロジェクトのXAMLファイルを以下のように修正します。

600 × 600 ピクセルのサイズの Image コントロールを1つ配置しNameプロパティを「 TestImage1 」とします。F#のコードからアクセスできるようにプロパティ「 x:FieldModifier="public" 」も追加します。

Window のサイズはクライアント領域の Image コントロールのサイズにフィットさせる設定「 SizeToContent="WidthAndHeight" 」とします。

  1. <Window x:Class="WpfTest.MainWindow"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         Title="Mandelbrot Set" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
  5.     <Grid>
  6.         <Image x:FieldModifier="public" Width="600" Height="600" x:Name="TestImage1" />
  7.     </Grid>
  8. </Window>

 

F#のプロジェクト WinAppTest において、今回は Bitmap クラス (System.Drawing) オブジェクトへ描画処理を行うのでさらにSystem.Drawing へのアセンブリ参照を追加します。最終的にF#のプロジェクトは以下のような参照設定となります。

ファイル Program.fs に書くコードは以下の通り。

  1. open System.Numerics
  2.  
  3. let mandelbrotColor(c: Complex): int =
  4.     let colorInit = 256.0
  5.     let rec calcColor (z: Complex) (col: double) : int =
  6.         if (z.Real + z.Imaginary < 4.0) && (col > 0.0) then
  7.             calcColor (z * z + c) (col - 1.0)
  8.         else
  9.             int col
  10.  
  11.     calcColor Complex.Zero colorInit
  12.  
  13. let createMandelbrotBitmap(): System.Drawing.Bitmap =
  14.     let drawingBmp = new System.Drawing.Bitmap(600, 600,
  15.                                                 System.Drawing.Imaging.PixelFormat.Format24bppRgb)
  16.     let from = Complex(-1.5, -1.0)
  17.     let dest = Complex(1.0, 1.0)
  18.     let deltax = (dest.Real - from.Real) / (double drawingBmp.Width)
  19.     let deltay = (dest.Imaginary - from.Imaginary) / (double drawingBmp.Height)
  20.     
  21.     for y = 0 to (drawingBmp.Height - 1) do
  22.         for x = 0 to (drawingBmp.Width - 1) do
  23.             let col = mandelbrotColor(from + Complex(double(x) * deltax, double(y) * deltay))
  24.             drawingBmp.SetPixel(x, y, System.Drawing.Color.FromArgb(col, col, col))
  25.     
  26.     do
  27.         use g = System.Drawing.Graphics.FromImage(drawingBmp)
  28.         use font = new System.Drawing.Font("Meiryo UI", 25.0f, System.Drawing.GraphicsUnit.Pixel)  
  29.         use brush = new System.Drawing.SolidBrush(System.Drawing.Color.Red)
  30.         g.DrawString("マンデルブロ集合", font, brush, 230.0f, 200.0f)
  31.     
  32.     drawingBmp
  33.  
  34. [<System.Runtime.InteropServices.DllImport("gdi32")>]
  35. extern bool DeleteObject(nativeint hObject)  
  36.  
  37. [<System.Windows.Data.ValueConversion(typeof<System.Drawing.Bitmap>, typeof<System.Windows.Media.ImageSource>)>]
  38. type MyBitmapConverter() =
  39.     interface System.Windows.Data.IValueConverter with
  40.         override x.Convert(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
  41.             let drawingBmp = value :?> System.Drawing.Bitmap
  42.             assert(not <| obj.ReferenceEquals(drawingBmp, null))
  43.             let hBitmap: nativeint = drawingBmp.GetHbitmap() // GDIビットマップオブジェクトへのハンドルを取得
  44.             let sourceRect = System.Windows.Int32Rect.Empty
  45.             let sizeOptions = System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions()
  46.             let bmpSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, System.IntPtr.Zero, sourceRect, sizeOptions)
  47.             bmpSource.Freeze()
  48.             DeleteObject(hBitmap) |> ignore // GDIビットマップオブジェクトの後始末
  49.             bmpSource :> obj
  50.  
  51.         override x.ConvertBack(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
  52.             failwith("not implemented")
  53.  
  54. [<EntryPoint; System.STAThread>]
  55. let main _ =
  56.     let app = new System.Windows.Application()
  57.     let win = new WpfTest.MainWindow()
  58.  
  59.     use bitmap: System.Drawing.Bitmap = createMandelbrotBitmap()
  60.     let binding = new System.Windows.Data.Binding(
  61.                                             Source = bitmap,
  62.                                             Converter = new MyBitmapConverter())
  63.     win.TestImage1.SetBinding(System.Windows.Controls.Image.SourceProperty, binding) |> ignore
  64.  
  65.     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 APIDeleteObject 関数を直接呼び出すための宣言です。MyBitmapConverter クラスのメソッド Convert 内で DeleteObject 関数を呼び出すため必要になります。

38行目WPFImage コントロールへのデータバインディングに今回必要な型変換クラス 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

実行結果

以下のようなウィンドウが表示されます。