画像認識のプレ加工に F# を使う

最終的には OpenCV と組み合わせるはずなのですが、いちいち C++ で組むのも面倒なので、事前加工の部分を F# で組みます。

image

こんな画像をグレースケールに変えたり、2値化するだけですね。

imageimage

imageimage

■画像加工にパイプを使う

C++ で OpenCV を使っていた頃から考えていたのですが、画像加工のフィルタ部分はパイプ(>>)を使うとスムースです。当然フィルタなんだから、画像関係の多種の filter と同じなので当然といえば当然。

        let path = @"D:\work\PiVistion\src\PiVision\FsVision.Test\data\001.bmp"
        let pi = FsVision.LoadForm( path )
        // グレースケール
        pi |> Pixel.toGray 
           |> FsVision.SavePixel( @"D:\work\PiVistion\src\PiVision\FsVision.Test\data\001-pi-gray.bmp" )

F# では、こんな風に |> を使ってつなげます。

■PowerPack を使うかどうか?

The Old F# “PowerPack” – Home
https://fsharppowerpack.codeplex.com/
fsprojects/powerpack
https://github.com/fsprojects/powerpack

画像加工の場合、2次元マトリクスが頻発するので、F# powerpack を使うのがベターなんでしょうが、要素として RGBA を使わないといけないので、Matix<RGBA> と定義したいんですよね。が、が、なんとなくうまく動かない。Matrix<‘T> になっているけど、実質 Matirx<float> しか動かないようです。RGBA を別々に扱っても良いのですが、試してみると F# の List#Item は結構遅くて、短時間に 640×480 の画素数を処理するのは難しそうです。シーケンシャルに for it in lst do な感じでイテレータアクセスすれば結構なスピードで動きます。まあ、有限要素法の件もあるし、マトリクス関係は自作しましょう、ということで powerpack 無しで作っています。

どちらかというと、OpenCV の各種関数を F# から使うところに力を入れた感じ。

■RGB/YUV/YIQ/HSV の相互変換

Zaku認識(2) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/601

かつて Zaku 認識(4年も前の記事になるのか)をやろうと思って、色相とか輝度とかを扱ったので、これの F# 版も作っておきます。3×3 か 4×4 のマトリクスなんだが、これをいちいち自前の matrix に通すと遅いので、直接計算します。さほど手間ではないし。

 
type RGB = { R:float; G:float; B:float; }
type YUV = { Y:float; U:float; V:float }
type YIQ = { Y:float; I:float; Q:float }
type HSV = { H:float; S:float; V:float }

module Pixel =

    type Conv =
        static member toYUV (rgb:RGB) =
            let y =  0.299 * rgb.R +  0.587 * rgb.G +  0.114 * rgb.B 
            let u = -0.169 * rgb.R -  0.331 * rgb.G +  0.500 * rgb.B 
            let v =  0.500 * rgb.R + -0.419 * rgb.G -  0.081 * rgb.B 
            { Y=y; U=u; V=v }
        static member toRGB (yuv:YUV) =
            let r =  1.000 * yuv.Y +  0.000 * yuv.U +  1.402 * yuv.V 
            let g =  1.000 * yuv.Y -  0.344 * yuv.U -  0.714 * yuv.V 
            let b =  1.000 * yuv.Y +  1.772 * yuv.U +  0.000 * yuv.V 
            { R=r; G=g; B=b }
        static member toYIQ ( rgb:RGB ) =
            let y = 0.2990 * rgb.R + 0.5870 * rgb.G + 0.1140 * rgb.B
            let i = 0.5959 * rgb.R - 0.2750 * rgb.G - 0.3210 * rgb.B
            let q = 0.2065 * rgb.R - 0.4969 * rgb.G + 0.2904 * rgb.B
            { Y=y; I=i; Q=q }
        static member toRGB ( yiq:YIQ ) =
            { R = 1.001 * yiq.Y + 0.955 * yiq.I + 0.622 * yiq.Q;
              G = 0.999 * yiq.Y - 0.272 * yiq.I - 0.648 * yiq.Q;
              B = 1.004 * yiq.Y - 1.106 * yiq.I + 1.704 * yiq.Q; }
        static member toHSV ( yiq:YIQ ) =
            { H = System.Math.Atan2(yiq.I, yiq.Q) ;
              S = Math.Sqrt( yiq.I * yiq.I + yiq.Q * yiq.Q) ;
              V = yiq.Y; }
        static member toYIQ ( hsv:HSV ) =
            { Y = hsv.V; 
              I = hsv.S * Math.Sin( hsv.H ) ;
              Q = hsv.S * Math.Cos( hsv.H ) ; }
        static member norm (rgb:RGB) =
            let r = if rgb.R < 0.0 then 0.0 else if rgb.R > 1.0 then 1.0 else rgb.R
            let g = if rgb.G < 0.0 then 0.0 else if rgb.G > 1.0 then 1.0 else rgb.G
            let b = if rgb.B < 0.0 then 0.0 else if rgb.B > 1.0 then 1.0 else rgb.B
            { R=r; G=g; B=b }

    let map (f:(RGB->RGB)) (pi:pixels) =
        pixels [ 
            for row in pi.Data do
                yield [ for it in row do
                            yield f( it )
                        ]] 
    /// <summary>
    /// gray scale filter
    /// </summary>
    /// <param name=&quot;pi&quot;></param>
    let toGray (pi:pixels) =
        pi |> map ( fun x ->
            // 加重平均 
            let c = (0.896 * x.R + 1.76 * x.G + 0.34 * x.B) / 3.0
            { R=c; G=c; B=c } )
    
    let to2Value (ce:float) (pi:pixels) =
        pi |> map ( fun x ->
            let c = (0.896 * x.R + 1.76 * x.G + 0.34 * x.B) / 3.0
            if c <= ce then { R=0.; G=0.; B=0. } else { R=1.; G=1.; B=1. }  
            )

    /// <summary>
    /// sepia filter
    /// </summary>
    /// <param name=&quot;pi&quot;></param>
    let toSepia (pi:pixels) =
        pi |> map ( fun x -> 
            let r = 0.393 * x.R + 0.769 * x.G + 0.189 * x.B
            let g = 0.349 * x.R + 0.686 * x.G + 0.168 * x.B
            let b = 0.272 * x.R + 0.534 * x.G + 0.131 * x.B 
            { R=r; G=g; B=b } |> Conv.norm)


こっちはいずれ整理して github に上げる予定です。

カテゴリー: F# | 画像認識のプレ加工に F# を使う はコメントを受け付けていません

麻雀牌認識のプロセスを考える

image

■目的

こんな画像から、14牌を認識する、とする。この画像自身の切り出しをどうするのか?って問題があるけど、麻雀牌自体は手元に置くことが多いし、ある程度画像認識の範囲を絞り込むことができるだろう…という仮定からスタートする。もちろん、これは「仮定」なので、前提条件が間違っている場合も頭の隅に入れておく。

■手順を考察

いきおい、牌の物体認識から入ってしまうと思われるが、それは駄目だ。というか無駄だ。牌の形は「四角」で固定されているし、牌の表面は主に白でおおわれている。さらに背景は、白以外(大抵は緑のマット?)なのだから、わざわざ物体認識という大層なことをやらなくてもいい。白っぽい部分を抽出すれば、牌と特定できるだろう。

さらに、この場合は牌が14個連なっている。多少上下にずれているが、だいたいは真ん中で一直線になっている。牌の数は決まっていて、14個であるのだから、先に大まかな位置が分かったら、単純に14等分すればそれぞれの牌の位置がわかるだろう。もちろん、上下のずれ、左右のずれ(カメラ位置のずれ)などを考慮するために若干ずらした牌の位置でも確認が必要になる。

牌の大まかな位置がわかったら、個別の牌を認識させる。これは特徴量抽出かテンプレートマッチングでよい。たぶん、テンプレートマッチングでも十分なスピードが出るのではないだろうか?牌の種類自体は、たかだか9×3+7=28種 花牌などを入れれば36種しかない。アルファベットよりも少ない。大文字小文字を区別する必要もないし、似ている文字(ゼロとオーとか)もないので、ほとんど誤認識はせずに済むだろう。教師画像は多少の上下の揺れと、上下さかさまの画像を用意しておけばよい。グレースケールのテンプレートマッチング法で十分だと思う。

赤牌をどう認識するかだが、これはテンプレートマッチングした後にあらためて色で識別すればよい。伍萬など決まっているから、それで特定ができる。

牌が特定できたら、後は自由に待ちとかを計算すればOK。

■認識の流れ

  1. 背景色とは異なる、白っぽい部分を特定する。
  2. 白っぽい部分は、横長と仮定する(先行きは、縦長やななめでも良い)
  3. 白っぽい横長の牌並びの、左端と右端の位置を特定する。
  4. 左端と右端で14分割する。
  5. 14分割した牌の位置に対して、テンプレートマッチングする。テンプレートマッチは上下逆さまの場合と上下の位置の揺れも教師画像として用意する。
  6. 牌の特定ができる。

これでやってみよう。

カテゴリー: OpenCV | 麻雀牌認識のプロセスを考える はコメントを受け付けていません

麻雀牌言語を Forth で作ろう

無事、Linq2Twitter で絵文字が出たところで、麻雀牌言語に取り組んでいます。ユニコードには麻雀牌の領域があって、麻雀牌 – Wikipedia http://ja.wikipedia.org/wiki/%E9%BA%BB%E9%9B%80%E7%89%8C を見ると、U+1F000 – U+1F02B が割り当てられています。

な感じで表示ができます(残念ながら、Mac/iPhone の場合はフォントがないらしく豆腐になりますが)。

■言語仕様は Forth

Tiny Forthインタープリタ
http://middleriver.chagasi.com/electronics/tforth.html

Forth 言語をご存じでしょうか?実際使ったことはないのですが、Cマガジンにスタックマシン言語として、スタックを最大に利用した(レジスタを使わない)方式のプログラミング言語です。確か、逆ポーランド法の解説だったかコンパイラの解説だったかで出た記事だったと思います。CPU の高速なレジスタに直接アクセスする代わりに、スタックに積んで計算するというちょっと回りくどいのですが、アセンブリ的に覚えやすくて実装しやすいという利点がある、ってことだけ覚えています。

実際、先のリンクをたどると tiny forth が C言語で600行弱しかありません。今の F# などで組めばもっと短くなるはずです。

そこで、リファレンスガイド を麻雀牌に直してプログラム言語化してみようという試みです。

■言語ルールを決める

文法よりも先に「ルール」を決めます。ええ、麻雀牌なんだから、13牌か14牌がいいですよね。

  1. 順子または暗刻が4つ、対子が1つで、1行14牌でプログラムする。
  2. キーワードは暗刻に割り当てる。
  3. ワードは順子に割り当てる。
  4. 数字、文字列はどうするか検討中。
  5. 14牌で、上がっていなければエラーとする。

5番目が重要ですね。きちんと、上がりを作らないとコンパイルエラーになります。

…ってことにしておきます。

■文法に割り与える

Tiny Forth のリファレンスガイドに準じて、適当に暗刻を割り当てます(覚えづらいので、後で割り当て直しますが)。

DRP スタックに積む [hai]111m[/hai]
DUP スタックに複製を積む [hai]222m[/hai]
SWP スワップする [hai]333m[/hai]
>R  パラメータスタックからリターンスタックへ積む [hai]444m[/hai]
<R  リターンスタックからパラメータスタックへ積む [hai]555m[/hai]

+ 加算 [hai]666m[/hai]
– 減算 [hai]777m[/hai]
* 乗算 [hai]888m[/hai]
/ 除算 [hai]999m[/hai]
% あまり [hai]111s[/hai]

AND 論理積 [hai]222s[/hai]
OR  論理和 [hai]333s[/hai]
XOR 排他的論理和 [hai]444s[/hai]

= [hai]555s[/hai]
< [hai]666s[/hai]
> [hai]777s[/hai]
<= 不要かも [hai]888s[/hai]
>= 不要かも [hai]999s[/hai]
<> [hai]111p[/hai]
NOT [hai]222p[/hai]

メモリ操作用
@ 2バイトスタックに積む [hai]333p[/hai]
! 2バイトスタックに積む [hai]444p[/hai]
@@ 1バイトスタックに積む [hai]555p[/hai]
!! 1バイトスタックに積む [hai]666p[/hai]
#面倒なので、char, int に揃えてしまうか。

出力ワード
. 出力 [hai]777p[/hai]

辞書操作
: … ; … ワードを辞書に登録 [hai]HHH FF[/hai]
VAR 変数を定義 [hai]FFF[/hai]
FGR ワードを削除(不要?)[hai]CCC[/hai]

制御文
IF … THEN … [hai]TTT SSS[/hai]
IF … ELSE … THEN … [hai]TTT NNN SSS[/hai]
BEGIN … END … スタック上の1番目のデータが偽ならば繰り返し [hai]PPP NNN[/hai]
BEGIN … WHILE … WEND … … (不要?)[hai]PPP SSS NNN[/hai]
DO … LOOP …   (不要?)[hai]PPP TTT[/hai]
I カウンタ値のアドレスをスタックに積む [hai]NNN [/hai]

ループ関係は冗長なので、ひとつだけ定義すれば OK かも。

■サンプルコード

先の Tiny Forth のページから “3+4*5” の計算が

3 4 5 * + 

となるので、

[hai]345m 456s 567p 888m 77m[/hai]

ではどうだろうか?順子の頭を数字とみなして計算する感じ。答えは、23 なんだが…分かりづらいことおびただしい w 慣れると打ちやすいかも?

■Pi㊥Pad

麻雀牌専用エディタ。先日、キーボード判別できないことが分かったので、キーボード自体はユーザーが設定ってことになりそうです。クリップボード機能とツイート機能だけあれば十分かと。Windows ストアに近日、公開します。

image

■Wordpress の麻雀牌プラグイン

麻雀牌プラグイン | 1000sei WordPress
http://wordpress.1000sei.com/ja/mahjong/

Unicode の麻雀牌は Live Writer 等で投稿できないので、表示にはプラグインを利用しています(爆

カテゴリー: 雑談 | 麻雀牌言語を Forth で作ろう はコメントを受け付けていません

絵文字を LinqToTwitter で送るための方法

ChuPad を製作している途中で、ツイッターに経過を表示させようと思いつきました。ひとりで作成しているとつまらないで、コピペするよりも直接ツイートできると面白いかと。ChuPad 自体は、Windows ストアアプリで作っているので(内部的にはユニバーサルアプリなので、お次は Windows Phone アプリになります)、People あたりに共有コントラクト…と考えたのですが、People に対しては URL しか共有できないのですね orz なんだかなー。どうやら、公式 Twitter アプリへの共有もできないらしいので、自前で実装することにしました。

で、いくつか探したなかで、LINQ To Twitter を使おうかなと、

LINQ to Twitter – Home
http://linqtotwitter.codeplex.com/

自前のツールは、デスクトップ環境でしか動かないのと、PCL で試してみたかったので、これを使うことに決まました。実際はツイートしかしないので、機能的にはオーバーなんですけどね。

■LinqToTwitterのPCL版を使う

PCL版は NuGet でダウンロードができます。「LinqToTwitter」で検索すれば ok です。

20140903_02

動作確認用のサンプルコードとしては、先の「LINQ to Twitter – Home」にはデモ用のコードがあるので、それを使うといいでしょう。Twitter API ひとつひとつにデモをつけているので、目的のものを探すのに苦労はするのですが。

ストア版の場合は、以下のように「LinqToTwitter.WindowsStore」を使います。なんか、ストア版、WPF版などクラスの名前が変わっているので、ネット上ではサンプルを探すのが苦労するのですが、本家のデモコードを見るとあっさり書いてあります。

using LinqToTwitter.WindowsStore;
using LinqToTwitter;

LinqToTwitter が便利なのは、最初の認証部分も受け持ってくれるところです。認証部分をブラウザに渡したり、WebView で作ったりするのが面倒なので、あらかじめ用意してあるものを使ったのが次のコードです。

private string API_KEY = &quot;&quot;;
private string API_SECRET = &quot;&quot;;
private string API_CALLBACK = &quot;&quot;;
// @chu_lang へ投稿
private string OAUTH_KEY = &quot;&quot;;
private string OAUTH_SECRET = &quot;&quot;;

/// <summary>
/// ツイッター投稿
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
async void kTwi_Tapped(object sender, TappedRoutedEventArgs e)
{
    WindowsStoreAuthorizer auth;
    if (OAUTH_KEY == &quot;&quot;)
    {
        auth = new WindowsStoreAuthorizer()
        {
            CredentialStore = new InMemoryCredentialStore
            {
                ConsumerKey = API_KEY,
                ConsumerSecret = API_SECRET
            },
            Callback = API_CALLBACK

        };
    }
    else
    {
        auth = new WindowsStoreAuthorizer()
        {
            CredentialStore = new InMemoryCredentialStore
            {
                ConsumerKey = API_KEY,
                ConsumerSecret = API_SECRET,
                OAuthToken = OAUTH_KEY,
                OAuthTokenSecret = OAUTH_SECRET
            },
            Callback = API_CALLBACK

        };

    }
    await auth.AuthorizeAsync();

    // int count = auth.Parameters.Count;
    // string oauth_token = auth.CredentialStore.OAuthToken;
    // string oauth_sercret = auth.CredentialStore.OAuthTokenSecret;

    var context = new TwitterContext(auth);
    var msg = text1.Text + &quot;n&quot; + DateTime.Now.ToString();
    await context.TweetAsync(msg);
}

Twitte のデベロッパで取得した API のコードを API_KEY と API_SECRET にいれておきます。まだ認証が終わっていないときには、OAUTH_KEY と OAUTH_SECRET が空なので、Twitter 認証ページを開いてユーザーに認証してもらえます。2回目以降は、OAUTH_KEY と OAUTH_SECRET を保存しておけば ok です。ここでは、ChuPad 専用のために直書きになっていますが、アプリフォルダに適当なフォーマットで保存しておけば ok でしょう。

末尾に DateTime をつけているのは実験用ので外しても ok です。同じツイートをすると、Twitter のほうで弾かれるのでそれを防ぐためです。実験していてあれー、となったので念のために。

■そのままでは絵文字コードが送れない

さて、この組み込みをして、さっくりと動かてみようと思ったのですが、絵文字を送ろうとすると実行エラーになります。Twitter のほうでエラーを返している訳ではなくて、LinqToTwitter の内部でエラーになるのです。自前で作っている投稿専用ツールのほうは絵文字が送れるので、LinqToTwitter の内部で何か変なことをやっているようです。

結論から言うと、Url#PercentEncode メソッドのロジックが未対応でした。このメソッドは、URLエンコードをするために%xxに変換するのですが、絵文字のサロゲートペアは内部的に何か変なことになっているみたいですね。これは、通常版の LinqToTwitter でも同じで、BuildUrlHelper#UrlEncode メソッドの中身が未対応です。
LinqToTwitter は通常版とPCL版とでコードが共有していないので、2箇所直す(しかもメソッド名もクラス名も異なる)のですが、これは .NET Framework 3.5 の頃に対応しようとした名残りなんでしょうね。

■エンコード部分を直す

仕方がないので、エンコード部分を直します。LinqToTwitter 自体は .NET 3.5 から対応しているのですが、これを奥深く入って直すのはしんどいので、.NET 4.5 の関数を使ってしまいます。

Uri.EscapeDataString メソッド (System)
http://msdn.microsoft.com/ja-jp/library/ms131567(v=vs.110).aspx

これ自体は 3.5 にもあるようなのですが、面倒なのでプロジェクト自体も 4.5 にしてしまいます。

本家の LinqToTiwtter からソースコードをダウンロードして、通常版とPCLのソリューションを開きます。

□通常版

namespace LinqToTwitter
{
    public class BuildUrlHelper
    {
...
        public static string UrlEncode(string value)
        {
            return Uri.EscapeDataString(value);
        }
	}
}

□PCL 版

namespace LinqToTwitter.Net
{
    public class Url
    {
...
        public static string PercentEncode(string value)
        {
            return Uri.EscapeDataString(value);
		}
	}
}

それぞれをビルドして NuGet から取ってきたものと摩り替えておけば ok です。こうすると無事、絵文字のツイートも可能になります。

後で、codeplex への pull request を送っておきます。

カテゴリー: C#, WinRT | 絵文字を LinqToTwitter で送るための方法 はコメントを受け付けていません

はじめての chu カッコカリの LT を発表しました

8月末の .NETラボ勉強会で「はじめての chu」をライトニングトークスで発表してきました。

ええ、発端はネタ言語ですが、内実はネタ言語の域を超えている…ってのが目標ですね。普通のエディタでは絵文字が打ちづらいので、専用の IDE で作成できる予定です。手元の環境では、絵文字を打つところまでできたので適当なタイミングでストアなどにアップしましょう。

image

Chu Pad からはツイートができたほうがいいので、左下ののアイコンがツイートアイコンです。Twitter の鳥ではないのは「絵文字」だからですね。それぞれのアイコンも画像ではなくて絵文字だけになっています。

最初は自分のところにばしばしと絵文字を流していたのですが、テストなので専用のアカウントを用意しました。公開するのも、この専用のアカウントに投稿してしまうのがいいかなと思っています。アカウントの乗っ取りのリスクがあるのですが、まあ秘密トークンは多少暗号化しておく形にしましょう。厳密にやれば Web API 作って、それから拾ってくるというのもありだけど。

image

奇特な方で自分のアカウントにも送りたい、って話もあるかなーと思って現在、秘密キー等を保存するところを製作中です(ってことにしておきます)。Twitter への認証自体は LINQ to Twitter にお世話になっています。が…この LINQ to Twitter はそのままでは「絵文字」を通さなかったんですよね。多少、苦労しましたが少しコードを変えることで通るようになったので、その件は別の記事にして残しておきます。

■文字解析等は FsLex/FsYacc を使っているよ

試しに、FsLex/Yacc を使っています。本当は BNFC のツールを使いたかったのですが、なんか吐き出すコードが -fsharp でやっても ocaml のコードがでるので、よくわかりません。ってのと、字句解析部分にひと工夫必要なので lex のところからきちんと書いています。

というのも、懇親会でも話したのですが、この chu-lang には「分かち書き」という概念がありません。絵文字をずらずらと並べて書くということを目標としたかったので、単語の区切りは文脈に沿うスタイルになります。当然、既存言語であってもターミネータ(括弧や四則演算などの特殊記号)と通常のアルファベットの組み合わせで単語の区切りはわかるのですが、そこは日本語のようにずらずらと書きたかったのです。例の富士通の「プログラマ不要」プログラム言語とか、既存の日本語プログラム言語との違いは、分かち書き(空白)を必要としない言語設計にあります(ってことにしておいてください)。まあ、Sketch とか UML プログラミングに近い感じです。手元の meArm とか Raspberry Pi とかにも使う予定です。

で、言語としては手続き言語に近い方にしたかったので関数言語スタイルをとります。そのまま F# にコンバートして .NET ライブラリを扱いやすいというのも、その手段のひとつではありますが、大抵のビジュアルプログラム言語が Alice をはじめとしてオブジェクト指向言語を学ぶスタイルを取っているので、どうせならば関数型言語ではどうでしょう?ってな感じですね。

文法的には F# と LISP を混ぜた感じになるはずです。括弧のような記号が絵文字では作りづらいので、タプルや配列は諦めてリストのみで表現します。まあ、それでもなんとかなるかなと。

  • Implementation Programming Languages
  • 計算機プログラムの構造と解釈

あたりを参考にすればよいでしょう。

■ChuLang は OSS です

コード自体は https://github.com/moonmile/ChuLang にちょっとずつ上げていきます。

懇親会で出た「特許」なり「商標」なりは、一般的に広めるための OSS ってことで、ここに公開しておきます。特許関連の抜け道?としては、最初に公開しておいて「周知の事実」にしていまえばいいんですよね。Java やら VB やらの前例があるので、Chu-Lang は数式ものが特許にならない程度に OSS ってことなのです。まあ、その他諸々は「商品」なのでしょうが、それは捕らぬ狸なので、ここでは触れませむー。

■Pi㊥Pad も作ってみた

その後、がりっちさんと池田さんと池袋に変える途中で、麻雀牌言語で盛り上がっていた(のは俺だけか?)のでパイ㊥パッドも作ってみました。

image

麻雀牌 http://ja.wikipedia.org/wiki/%E9%BA%BB%E9%9B%80%E7%89%8C#cite_ref-16 も Unicode で規定されていて、U+1F000 から U+1F02F まで割り当てられているんですよ(実際には、U+1F02B まで使われています)。ここからコピペコピペすると牌譜が作れるのですが、面倒なので Surface を使います…つーか、作ってみました。

どうせならば、これをプログラム言語に仕立て上げたいので思案中です。

  • 対子、順子/暗刻 の組みあわせで、アセンブラのニーモニック風に仕立て上げる。
  • スタックを扱って Forth 風にすればいいかな。

までは決めてあるのですが、その先は七誌さんあたりに相談しようかなと。

カテゴリー: 勉強会 | はじめての chu カッコカリの LT を発表しました はコメントを受け付けていません

[WinRT] ストアアプリで独自にキーコードを取得する方法

Surface の場合、ソフトウェアキーボードが出るのでぽちぽちと打てばいいのですが、PC 環境でストアプリを使う時にはキーボードを使ったほうが便利です。

が、XAML ではキー自体のイベントを拾うのはフォーカスのあるコントロールしか拾えません。KeyDown, KeyUp のイベントが発生しないんですよね。いきおい、テキストボックスがない画面にキーボードを使って入力するにはどうすればいいのか?と調べてみました。デスクトップアプリのように、フォーム自身(ストアアプリだから Page 自身)からキーイベントをフックしたいわけです。

■結論から言えばできない

Windowsストアアプリ: 極意7: アプリは、キーボード、マウス、タッチで機能する必要がある – Build Insider
http://www.buildinsider.net/mobile/winstoretips/07

色々探したのですが、結論から言えば「できません」。なんらかのフォーカスを持つコントロールを張り付けて、それに対してキーボード操作をすることになります。なので、画面にフォーカスを持つコントロールがあれば、それに Focus を当ててやって、そこからキーボードイベントを拾うことになります。

さて、自作の ChuPad は、TextBlock だけで構成されているためにフォーカスを付けることができません。表示部分なり入力部分なりを TextBox にしてもよいのですが、look & feel が悪いし、絵文字なり画像なりを貼り付けようと思うと結構面倒そうです(画像に関しては、RichTextBoxを使う方法もありますが)。
このパッド自体は、タブレットや Windows Phone を想定するので、本来ならばキーボードが無い環境でつかいます。通常のソフトウェアキーボードを出してもよいのですが、大きさがひどいのと、Chu-lang 特有の絵文字の順番にしたいという事情もあるので、キーボード部分を自作したいのです。

さて、どうすればよいのでしょうか?

■フォーカスを持つ自作 Control を作る

Focus メソッドを持てば良いということが分かったので、となれば、Focus メソッドを持つコントロールを自作してしまえばよいのです。どうせ画面にひとつしたフォーカスがないのですから、そのコントロールにあわせればよいでしょう、という発想をしました。
見ると、Focus メソッドを持つのは、Control クラスなので、自作コントロールを作ります。その中で、KeyDown/KeyUp の処理をすればよいわけです。

マウスや指で別の場所をタップする(この場合は、絵文字キーボード部分をタップする)と、フォーカスが外れれてイベントが取れなくなるので、無理やり戻してやります。タブ移動で止まるように IsTabStop = true にするのを忘れないでください。

//強制的にフォーカスを設定する
this.keycode.Focus(FocusState.Keyboard);
this.keycode.LostFocus += (s, e) => this.keycode.Focus(FocusState.Keyboard);
this.keycode.IsTabStop = true;
this.keycode.OnKeyPush += keycode_OnKeyPush;

ちなみに、自作絵文字キーボードからへの打ち込みは、それぞれの TextBlock に名前を付けておいて、コード上で一気に Tapped イベントを設定します。

var lst = new TextBlock[] {
    kLet, kLeft, kRight, kCat, kDog,
    kIf, kThen, kElse, kEqual, kNEqual,
    kFor, kTo, kFun, kTrue, kFalse,
    kList, kHead, kTail, kPrint, kCase,
};
foreach (var it in lst)
    it.Tapped += it_Tapped;

こんな風に配列を使って設定すればコードが短くなりますね。いちいち new しなくよい F# の影響です。

■KeyDown/KeyUp イベントを処理する

キーボードを押し下げ/離したときのイベントから仮想キーコードが取得できます。

VirtualKey Enumeration
http://msdn.microsoft.com/ja-jp/library/windows/apps/windows.system.virtualkey(v=win.10).aspx

この enum をざっと見てわかるんですが、アルファベットしかなくて記号がありません。「@」とか「”」とかは、どうやって判別すればよいのでしょうか?

WPF の場合には、

KeyInterop.KeyFromVirtualKey Method (System.Windows.Input)
http://msdn.microsoft.com/en-us/library/system.windows.input.keyinterop.keyfromvirtualkey(v=vs.110).aspx

を使ってコンバートするのですが、WinRT では使えない。

仕方がないので、シフトキーの押し下げなどを判別して、JISキーボードをエミュレートします(英語向けなので、ASCIIキーボードも用意しないと駄目ですが)。

void makeKeycode()
{
    KCs = new KC[] {
        new KC( VirtualKey.Number1, "1", "!"),
        new KC( VirtualKey.Number2, "2", """),
        new KC( VirtualKey.Number3, "3", "#"),
        new KC( VirtualKey.Number4, "4", "$"),
        new KC( VirtualKey.Number5, "5", "%"),
        new KC( VirtualKey.Number6, "6", "&"),
        new KC( VirtualKey.Number7, "7", "'"),
        new KC( VirtualKey.Number8, "8", "("),
        new KC( VirtualKey.Number9, "9", ")"),
        new KC( VirtualKey.Number0, "0", ""),
        new KC((VirtualKey)189 , "-", "="),
        new KC((VirtualKey)222, "^", "~"),
        new KC((VirtualKey)220, "", "|"),

...略

        // 特殊キー
        new KC( VirtualKey.Space, " ", " "),
        new KC( VirtualKey.Enter, "ENTER", "ENTER"),
        new KC( VirtualKey.Back, "BS", "BS"),
        new KC( VirtualKey.Insert, "INS", "INS"),
        new KC( VirtualKey.Delete, "DEL", "DEL"),
        new KC( VirtualKey.Home, "HOME", "HOME"),
        new KC( VirtualKey.End, "END", "END"),
        new KC( VirtualKey.PageUp, "PAGEUP", "PAGEUP"),
        new KC( VirtualKey.PageDown, "PAGEDOWN", "PAGEDOWN"),
        new KC( VirtualKey.Up, "UP", "UP"),
        new KC( VirtualKey.Down, "DOWN", "DOWN"),
        new KC( VirtualKey.Left, "LEFT", "LEFT"),
        new KC( VirtualKey.Right, "RIGHT", "RIGHT"),

        // 数字キーパッド
        new KC( VirtualKey.NumberKeyLock, "NUMLOCK", "NUMLOCK"),
        new KC( VirtualKey.Divide, "/", "/"),
        new KC( VirtualKey.Multiply, "*", "*"),
        new KC( VirtualKey.Subtract, "-", "-"),
        new KC( VirtualKey.Add, "+", "+"),
        new KC( VirtualKey.NumberPad0, "0", "0"),
        new KC( VirtualKey.NumberPad1, "1", "1"),
        new KC( VirtualKey.NumberPad2, "2", "2"),
        new KC( VirtualKey.NumberPad3, "3", "3"),
        new KC( VirtualKey.NumberPad4, "4", "4"),
        new KC( VirtualKey.NumberPad5, "5", "5"),
        new KC( VirtualKey.NumberPad6, "6", "6"),
        new KC( VirtualKey.NumberPad7, "7", "7"),
        new KC( VirtualKey.NumberPad8, "8", "8"),
        new KC( VirtualKey.NumberPad8, "9", "9"),
    };
}

こんな風にちまちまとテーブルを使って変換表を作っておいて、LINQ 使って検索します。
KeyDown/KeyUp 時にあれこれとやっているのは、要らないキー処理を捨てているところです。実際試してみると、キーリピートが必要そうなので、後で修正しておきます。

protected override void OnKeyDown(KeyRoutedEventArgs e)
{
    // base.OnKeyDown(e);
    OnKey(e);
}
protected override void OnKeyUp(KeyRoutedEventArgs e)
{
    // base.OnKeyUp(e);
    OnKey(e);
}
private void OnKey(KeyRoutedEventArgs e)
{
    // if (e.KeyStatus.WasKeyDown == true) return;

    if (e.Key == Windows.System.VirtualKey.Shift)
    {
        isShift = !e.KeyStatus.IsKeyReleased;
        return;
    }
    if (e.Key == Windows.System.VirtualKey.Menu)
    {
        isAlt = !e.KeyStatus.IsKeyReleased;
        return;
    }
    if (e.Key == Windows.System.VirtualKey.Control)
    {
        isCtrl = !e.KeyStatus.IsKeyReleased;
        return;
    }
    if (e.KeyStatus.WasKeyDown == true) return;
    if (e.KeyStatus.ScanCode == 0) return;  // Enter外し

    Debug.WriteLine(&quot;key {0} {1} {2} {3} {4}&quot;,
        e.Key,
        isShift, isCtrl, isAlt,
        e.KeyStatus.ScanCode
        );

    try
    {
        var key = KCs.First(kc => kc.Key == e.Key);
        if (OnKeyPush != null)
        {
            string ch = &quot;&quot;;
            if ( key.Normal.Length == 1 ) {
                ch = isShift == false ? key.Normal : key.Shift;
            } else {
                ch = key.Normal;
            }
            OnKeyPush(ch, isShift, isCtrl, isAlt);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(&quot;Error: keycode error {0} {1}&quot;, e.Key, ex.Message );
    }
}

■サンプルコード

http://github.com/moonmile/ChuLang/tree/master/ChuPad

カテゴリー: C#, WinRT | [WinRT] ストアアプリで独自にキーコードを取得する方法 はコメントを受け付けていません

TypeProviderで生成したクラスをC#で使うための方法

型プロバイダー(Type Provider)のちょっとしたアレコレ – Bug Catharsis
http://zecl.hatenablog.com/entry/TypeProvderArekore

に勝手につけたし。ちょうど「消去型と生成型」のところを考えていたところで、消去型(IsErased=true)と、生成型(IsErased=true)では、何が「プログラミング的」に違うのか?という話です。

第一には型が消去してしまうので、別のアセンブリから参照するときにインテリセンスが効かない、プロパティなどの名前解決ができない、ってところで、

  • うちうちで使うならば「消去型」のままで ok
  • 別途公開する必要があれば「生成型」を使う

って感じですね。アセンブリをファイルとして残しておくことで、Visual Studio が参照できるようになる、っていう仕組みだと思います。なので、アセンブリ自体は、こんな形でテンポラリファイルとして残しておきます。

let outerType =
    ProvidedTypeDefinition (thisAssembly, namespaceName,
        typeName, Some(typeof), IsErased = false )
// 名前を付けてアセンブリを残す
let tempAssembly = ProvidedAssembly(System.IO.Path.ChangeExtension(System.IO.Path.GetTempFileName(), &quot;.dll&quot;))
tempAssembly.AddTypes <| [ outerType ]

このファイルは、テンポラリファイルなので、生成された後に適当な時間で消えてしまいます。あと、TypeProvider の DLL はコードを変えるたびに生成されるので、テンポラリ名にして名前が変化するようにします。

■C# からタイププロバイダを使うときには、F# を媒介させる

型生成はテンポラリファイルとして残っているものの、これを参照できるのは F# からだけです。何故、F# からのみしか参照できないのかはわかりませんが、C# から参照しようとしても見当たりません。
といいますか、型生成の方法が、こんな風な構文になっているので、構文的に C# では書けないというのもありますね。

type AA = CSharpTypeProvider.STR<&quot;masuda&quot;>
let a = new AA()

printfn &quot;CSharpTypeProvider test in F#&quot;
printfn &quot;%A&quot; a.Name
printfn &quot;%A&quot; a.Version

ただし、タイプビルダー自体は、

TypeBuilder クラス (System.Reflection.Emit)
http://msdn.microsoft.com/ja-jp/library/system.reflection.emit.typebuilder(v=vs.110).aspx

によって実現されているので F# しかできないものではありません。そのうちに C# や VB でも使えるようになる可能性はあります(構文さえ作ればよいので)。

さて、ここで作成した AA クラス あるいは CSharpTypeProvider.STR<“masuda”> というクラスを C# でどうやって扱えばいいのか?という話があまり書いていないのですが、結構簡単です。型生成だけした F# のライブラリを用意して、次のようにつくっておきます。

namespace FSharpLib
type AA = CSharpTypeProvider.STR<&quot;tomoaki&quot;>

そして、C# のプロジェクトから FSharpLib プロジェクトを参照設定して、次のように書けばいいのです。

class Program
{
    static void Main(string[] args)
    {
        // refer SampleType.tomoaki.dll
        var a = new FSharpLib.AA();
        Console.WriteLine("Typeprovider test in C#");
        Console.WriteLine("{0}", a.Name);
        Console.WriteLine("{0}", a.Version);

        System.Console.ReadKey();
    }
}

簡単ですよね。クラスの定義部分が F# になってしまうために、C# 内で閉じないのが難点ではありますが、T4 などを使わずに型生成が簡単にできるのは非常に便利です。この型生成に渡すパラメータを内部的に解釈を変えれば、外部ファイルを読み込んだり、インターネットからダウンロードして生成したりすることもできます。外部ファイルへの対応は、

ちょっと草植えときますね型言語Grass型プロバイダー
http://github.com/zecl/GrassTypeProvider

にあります。

注意したいところは、コード変えるたびに型生成の実行が発生するので、あまり重たい処理は入れられないということです。重たい処理の場合は、別プロセスにするとか遅延を許す感じでユーザーに通知するなどの工夫が必要でしょう。

■型生成済みのアセンブリを C# で直接参照する

動的に型生成をする場合には、上記のように F# プロジェクトが必要ですが、最初に生成しただけであとは全く同じ状態であるならば、C# プロジェクトから直接生成済みのアセンブリを参照することもできます。

let outerType =
    ProvidedTypeDefinition (thisAssembly, namespaceName,
        typeName, Some(typeof), IsErased = false )
// フルパスで指定する
// 型を残して C# から直接参照できるようにする
let tempAssembly = ProvidedAssembly(&quot;d:\temp\SampleType.&quot;+ str+&quot;.dll&quot;)
tempAssembly.AddTypes <| [ outerType ]

先に書いたテンポラリファイルへの書き込みを逆手にとって、同じファイルに出力するようにします。ここでは、渡された文字列と組み合わせてユニーク名にしたアセンブリを temp フォルダに保存しています(フルパスでないと駄目です)。タイププロバイダに渡した初期値ごとに違うのですが、同じ型であれば同じアセンブリ名になります。
型生成する F# のプロジェクトは別に作っておいて、一度だけ動かします。
そして、生成した型生成の DLL を C# プロジェクトから参照させます。

static void Main(string[] args)
{
    var a = new CSharpTypeProvider.AA();
    Console.WriteLine("Typeprovider test in C#");
    Console.WriteLine("{0}", a.Name);
    Console.WriteLine("{0}", a.Version);

    System.Console.ReadKey();
}

すると、普通の DLL のようにクラスを生成することができます。型生成されているアセンブリだから当然ですね。
この方式のメリットは、C# のプロジェクトから F# のプロジェクトを参照しなくてよいことです。生成済みの DLL だけを使えばいいので配布も簡単です。まあ、大量に型生成する必要があるかどうかは別なのですが、状況次第ではいいのではないでしょうか?

■サンプルコード

動作確認のためのサンプルコードはこちら

moonmile/SampleTypeProviderToUseCSharp
http://github.com/moonmile/SampleTypeProviderToUseCSharp

カテゴリー: C#, F# | TypeProviderで生成したクラスをC#で使うための方法 はコメントを受け付けていません

最新の FsYacc/lex 環境で chulang を作成するときのメモ

勢いで作り始めた chu-lang.com ですが、ちまちまと lex/yacc のパターンで書き直し。どうせならば、F# の FsLex/FsYacc で作ろうかと思ってやっているのですが、結構はまります。

F#のfslexとfsyaccを用いたコンパイラ作成 (2/4):CodeZine
http://codezine.jp/article/detail/5355?p=2
fslexとfsyaccを使って字句・構文解析して電卓っぽい計算をしてくれる奴を作成したい – My Life as a Mock Quant
http://d.hatena.ne.jp/teramonagi/20120506/1336290544
VS2013でFsYacc、FsLexを使う – komorebikoboshiのブログ
http://komorebikoboshi.hatenablog.com/entry/2014/05/23/225132

もともと私の手元には Powerpack-4.0.0.0 が入っていて、少し事情が違うのですが、だいたい環境は同じです。なんか、lex/yacc の実行ファイルから Visual Studio から自動でビルドできるところまで、追っていました。が、*.targets の設定がうまく動かないのか?(F# 3.0 の環境だと動くのか?)、妙に環境が整えづらいんですよね。FsLex のビルドも遅いし…と、再び探すと。

FsLex, FsYacc
http://fsprojects.github.io/FsLexYacc/

最新版は、Nuget から取って来る、という状態になっているようです。github から取って来れば、サンプルコードとサンプルプロジェクトがついてきて、最新の fslex.exe と fsyacc.exe が packages の中にダウンロードされます。便利ですね。

■Nuget から FsLexYacc を取ってくる

lex/yacc 用の DLL は、FsLexYacc.Runtime という名前に代わっています。

Lexer.fsl と Parser.fsy を使ってビルドするプロジェクトは、上のほうを選択します。実際に使うときは、Runtime だけのプロジェクトで Ok ですね。

■ビルドされるように *.fsproj を書き換える

http://github.com/fsprojects/FsLexYacc/blob/master/tests/TestProjectUsingNugetPackage/TestProjectUsingNugetPackage.fsproj#L86

を参考にして、Lexer.fsl と Parser.fsy を追加します。

  <ItemGroup>
    <None Include=&quot;Lexer.fsl&quot; />
    <None Include=&quot;Parser.fsy&quot; />
    <FsYacc Include=&quot;Parser.fsy&quot;>
      <OtherFlags>--module Parser</OtherFlags>
    </FsYacc>
    <FsLex Include=&quot;Lexer.fsl&quot;>
      <OtherFlags>--unicode</OtherFlags>
    </FsLex>
  </ItemGroup>

あと FsYacc と FsLex が有効になるように *.targets をインポートします。

  <Import Project=&quot;..\packages\FsLexYacc.6.0.3\bin\FsLexYacc.targets&quot; />

FsLexYacc.targets の呼び出しにバージョンが付いてしまっているので、Nuget が最新になると呼び出せない…という不具合はあるのですが、まあ、こうしておきます。*.fsproj に FsLexYacc.targets をコピーして動かすと、Build.dll がない状態になるので、仕方がないですね。

以前は、ビルド時に

"C:\Program Files (x86)\FSharpPowerPack-4.0.0.0\\bin\fslex.exe"  "$(ProjectDir)\Lexer.fsl" --unicode
"C:\Program Files (x86)\FSharpPowerPack-4.0.0.0\\bin\fsyacc.exe" "$(ProjectDir)\Parser.fsy" --module Parser

なことをやって、毎回ビルドしていたのですが、これでファイルが更新されたときにビルドされるようになります。

■PCLで動くのか?

結論から言うと、FsLexYacc.Rutime は PCL では動きません。ちなみに Powerpack 版も動かなかったので同じ状態です。

ただし、これはソースが github で公開されているので、Profile78 あたりでビルドし直してやれば、Xamarin.iOS/Android で動くようになるのでは?と思っています。あと Windows Store アプリのほうも。

■chu-lang とは?

絵文字で書く関数型プログラム言語です。何処を目指しているかは謎です(いや、実ははっきりしてるんだけど、いまのところは「謎」)。たぶん、タブレットとかスマートフォンで打ちやすい言語になるはずです。

http://chu-lang.com/

こんな感じで動く予定。

カテゴリー: F#, chu | 1件のコメント

WindowsストアアプリでXAMLを動的にロードする方法

XamlReader.Load method (Windows)
http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.markup.xamlreader.load.aspx

を使うと、XAML ファイルを動的にローディングできるんやで、ということは以前 XAML を調べていて分かったのだけどサンプルがいまいちだったので作ってみました。

ちなみに、Dev Center のサンプルは、

&quot;<Ellipse Name=&quot;EllipseAdded&quot; Width=&quot;300.5&quot; Height=&quot;200&quot; 
Fill=&quot;Red&quot; &quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;/>&quot;;

のところを、

&quot;<Ellipse Name=&quot;EllipseAdded&quot; Width=&quot;300.5&quot; Height=&quot;200&quot; 
Fill=&quot;Red&quot; xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;/>&quot;;

にして「xmlns=」を追加しないと動きません orz.

■XAML を文字列で指定する。

サンプルコードのように xaml 文字列を作成して、XamlReader.Load に渡すとルートのオブジェクトが帰ってきます。先の例では、Ellipse オブジェクトが取れるわけです。
これをどのようにすり替えるかというと、普通に Children.Add します。なので、ページを切り替えようと思って Frame.Navigate しようと思ってもうまくいきません。Navigate に渡すのはクラス自身なので、オブジェクトじゃないんですよね。仕方がないので、Page.Content の方をすり替えます。

private void OnClickString(object sender, RoutedEventArgs e)
{
    // var xaml = &quot;<TextBlock FontSize=&quot;50&quot; Text=&quot;From XAML&quot; &quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot; />&quot;;
    string xaml = &quot;<Grid xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;><Ellipse Name=&quot;backbtn&quot; Width=&quot;300.5&quot; Height=&quot;200&quot; Fill=&quot;Red&quot; /></Grid>&quot;;
    var doc = XamlReader.Load(xaml) as Grid;
    this.Content = doc ;
    // 短いXAML文字列だと FindName できる?
    var el = doc.FindName(&quot;backbtn&quot;) as UIElement;
    el.Tapped += (_,__) =>
    {
        this.Content = originalGrid;
    };
}

こんな風に、文字列を Page(this)のContent プロパティに設定すると画面が切り替わります。
戻すためにボタンをつけておいて、x:Name でつけること…は実はできないはずなのですができてしまいます。たぶん、XAML の文字列が短い場合は大丈夫なのかもしれません。次の HTTP プロトコルでデータを取ってくるときはうまくいかないのですから(MSDN にも x:Name は「使わないで」と書いてあるので)。

味気ない楕円の○が出ます。これをクリックすると元の画面に戻ることができます。

■HTTP プロトコルで XAML データをダウンロードして表示する

XAML 文字列で表示できたということは、インターネット上にあるデータを取ってくることもできます。HttpClient#GetStringAsync でがっつりともってきて表示すれば Ok です。

private async void OnClickHttp(object sender, RoutedEventArgs e)
{
    var url = &quot;http://moonmile.net/up/samplepage.xaml&quot;;
    var cl = new HttpClient();
    var xaml = await cl.GetStringAsync(new Uri(url));
    var doc = XamlReader.Load(xaml) as Grid;
    this.Content = doc;
    // 戻るボタン
    // HTTP からロードしたときは x:Name がスルーされない?
    // var el = doc.FindName(&quot;backbtn&quot;) as UIElement;
    var el = doc.Children.First<UIElement>( t => { 
        var btn = t as Button;
        return (btn != null && ((string)btn.Content)==&quot;back&quot; )? true : false;
    });
    el.Tapped += (_, __) =>
    {
        this.Content = originalGrid;
    };
}

文字列が長いためか、x:Name が有効に働きません。仕方がないので。Button#Content で戻るボタンを取ってきているのですが、これは別な手段を考えたほうがいいでしょう。

こんな風に、Grid と Button, 楕円を並べた XAML を表示させることができます。ただし、すべての XAML がロードできるわけではなく、かなり制限があるようです。

  • x:Name が設定できないので、目的のコントロールを探すのがしんどい。
  • Stroyboard がうまく設定できない(実行エラーになる)のでアニメーションができない。
  • トリガーの設定はできるのか不明

な感じです。アニメーション&トリガーの組み合わせができないのは痛いですよね。おそらく、Storyboard 自身への x:Name と targetProperty のあたりでエラーになっているようです。まあ、コードビハイド(というのか?)で、独自に storyboard を追加してやって適度にイベントを設定すればそれらしいことはできるのですが、HTMLとSVGの組み合わせでアニメーションという具合にはいかなそうです。
あと、XAML への C# コードの埋め込みはできるのか?という問題がありますね。埋め込みができれば、View のままで色々できるので、Model と View の完全な分離(ダイナミックにリンクさせるという意味で)はかなり実用的になりそうなのですが。

■サンプルコード

WinStoreDynamicView
http://github.com/moonmile/WinStoreDynamicView

カテゴリー: C#, XAML | WindowsストアアプリでXAMLを動的にロードする方法 はコメントを受け付けていません

WPF は死んだのか?

というか、Microsoft としては WPF をどうする気なのか?が気になるところです。by #comuplus ですね。表向きとしては、Windows DNA の頃(最初に XAML が発表された頃)と同じで「XAMLを学んでおくと、Windows Phoneでの開発がやりやすくなる」だと思うのですが、日本ではいまだにWindows Phoneが発売されない変な状況と、欧米諸外国の反応をかんがみれば、日本と日本以外のアプローチはかなり違っています…が、求めるところは同じところかな、とは思われるんですがね。

デスクトップでの WPF アプリケーションとしては、提督業も忙しい! を代表として、結構「綺麗な」アプリケーションができることがわかっています。もともと、ぐらばくさんがビデオ操作アプリ(でしたっけ?)の UI に特化していたのもあって「WFP で何が作れるのか?どこまでいけるのか?」の実験的な雰囲気もありました。あの、Visual Studio の光る影を作るところからスタートなハズです。で、Windows フォームでは作りづらかった(Visual Studio の 光る影は、がりがりに Widnows API なんですけど)綺麗な画面が、WPF で手軽に作れるところが最初の売りだったのです。従来の Windows Forms で作ると、テキストボックスの背景に色をつけるとか、コンボボックスのサイズを自由に変更して画像を入れ込むとか、が大変だったのです、DrawItem を使わないといけませんでしたから。コンポーネントを組んで発売したのが、例の旧文化オリエントで、COM の前身の VBX の頃からリッチなコンポーネントを販売していました(実は旧文化オリエントが作っていたのではなくて、イスラエル?だっけ?から買っているコンポーネントです。現在は、インドで作っていますね)。このあたりの、VBX → COM の流れを探ってみると、先の comuplus の話が分かりやすいのですが。そうそう、VBX 自体は、C++ でしか書けなくて、VB のコンポーネントを C++ で書くという言語差がありました。それを OCX とすることで、VB 自体でもコンポーネントを作れるようにしたんですよね。そのあたりが、従来の IUnknown とアパートメントの歴史になります。VB のほうは名前付けですからね。

さて「綺麗な画面」を形作るのは、実は WEB ブラウザで HTML 形式によるアプリケーションが出てきたのと無関係ではありません。当時、HTML で作ったブラウザアプリが画像を使って華やかになってくる、さらに Shockwave, Flash などを使ってアニメーション効果などが入った頃に、かたやデスクトップ環境の Windows フォームアプリは相変わらずの「灰色な画面」が標準的だったのです。一方で、DHTML を使ったアプリを IE 上で動かそうとしたり、あれこれと Web 業界に踏み込んだ Microsoft なのですが、やっぱり Apache に勝てなかった…んですよね。今はどうかわかりませんが、いや、Azure に LAMP 環境を乗せるという形で共存の道を選んでいます。

そのなかで、「統合」という形で、デスクトップの画面を HTML 形式風に扱えるようにしたのが、WPF です。かつ、Sliverlight ですね。この両方とも XAML という書式を扱っているのがミソで、デスクトップ環境でも動く、ブラウザ環境でも動く、ってのが最大のメリットでした。それ以前の UI の構築方式は、

  • VB6.0 のように独自の書式をつかう。
  • VC++ のようにリソースとして埋め込む。
  • tcl/tk のようにコードで構築する。

というスタイルが標準だったのです。そこで、HTMLかつXMLという標準的なフォーマットが生まれ、XLST などの派生ツールができたあたりで(これは死んでるのかな?)、標準フォーマットに XML を使おうといいうスタイルが生まれてきたわけですね。最初の、WPF1.0 や Silverlight が貧弱すぎてイベントまわりがひどかったので、SVG との差別化が難しかったのですが、バックグラウンドに .NET フレームワークと直結させたところに、WPF(XAML)のメリットがあります。

さて、当時「なぜ XAML を学ぶのか?」の質問に対して Microsoft は常に「統合されたときにあらゆるところで XAML が使えるようになるから、学んで損はない」という回答でした。これが、広告的なものだったのか、本音だったのかは定かではありませんが、今となって(非常に遅い登場ではありましたが)、Windows Store アプリと Windows Phone アプリでほぼ共通の XAML が使えるようになったことが、それを示しています。

その中で、MVVM パターンという View と Model の作成パターンの含めて(これは戦略的なものだったのか?少なくと WPF1.0 の頃にはなかったものですし、MVC を広めた後に、MVVM ですから、「幸運」かもしれません)、XAML の価値は MVVM とワンセットになって広がります。

独自形式であった Xcode の xib が storyboard になり XML 形式になって扱いやすくなったり、Android の UI が AXML 形式であったりする(これは単純に XML 形式です)したところから、「UI を XML 形式で分離させる良い」という風潮が広まり、同時に MvvmCross のような MVVM パターンを組み入れたライブラリが、Microsoft 以外にも広まるようになりました。

そうなると、XAML という Microsoft 独自形式ではなくても、XML で扱えば何かと UI が便利である、ことがわかってきます。静的に UI のプロパティは struts や ASP.NET で行われているので、それと同じように、静的にマッピングする方法を XAML が行い、それに storyboard や axml も追随しています。動的ローディングのほうはどうなのか?という話ですが、実は、XAML が発表される以前に、MyXaml で実現されています。と言いますか、もともと XAML は動的ロードになる予定だったのが、いつの頃なのか、静的ロードになったんですよね。ちなみに、Xamarin.Forms は静的ロードですが、(訂正)XAMLは動的ロードされています。Xamarin.Forms もリソースから動的ロードですよね。ただし、イベントのバインドとかも自動で行われる XamlReader を使って読み込まれます。ただ、これに癖があってイベントのバインドとか自動で行ってしまうのでバインド先がないとエラーになっちゃうのです。うまくやれば、HTTP プロトコルでネットからダウンロードして表示ってことも可能です。なので、 自前の Xamarin.Forms のプレビューアは別途動的ロードになっています。考えてみれば、ブラウザで表示される HTML のロードは動的ロードなので、静的に(ビルド時に)マッピングしなければいけない理由はないのです。当時、CPU パワーが遅かったので、初期表示時のロードを避けたのですが、いまとなっては結構なスピードで動きます。

そんな訳で、初期の頃には、WPF は XAML として孤立してた(XAML自体が孤立していた)のですが、今に至ると WPF は XAML の仲間となっています。Windows Store App や Windows Phone 8.1 アプリに XAML が使われると同様に、WPF でも XAML を使えます。それぞれのプラットフォームの違いにより使えるライブラリやコンポーネントの違いはありますが、基本的なところはほとんどかわりませんし、インターフェース自体(状況依存プロパティやイベントの作り)は変わらないので、UI ライブラリを移植するのはそれほど難しい作業ではありません(DirectX 絡みのところは大変でしょうが)。

というわけで、UI を XML 形式で作る、そして MVVM パターンを使ってプロパティとイベントを連結させる(ここの分離は Rx を使っても同じ、あるいは直接コードビハイドでも同じ)パターンとしては、

  • Windows Store App を XAML で書く。
  • Windows Phone 8.1 アプリを XAML で書く。
  • Xcode で iPhone/iPad アプリを Storyboard で書く。
  • Android で axml で書く。
  • Xamarin.iOS/Android で、storyboard, axml で書く。
  • Xamarin.Forms で、Xamarin製XAMLで書く。
  • WPF で XAML で書く。
  • Siverlight を XAML で書く。

という広がりまでできてます。そういう中で、Windows デスクトップアプリとして WPF を使った書くこと(書いておくこと)は、他のプラットフォームの移植しやすいという利点があります。惜しむらくは、このなかに Linux が含まれていないことと、ブラウザ環境が含まれていないことですよね。本来は Silverlight がそれを担っていたのですが、およそ死んでいます。XAML → HTML コンバータ、あるいは XAML を解釈するスタイルで、ブラウザのほうに XAML 形式を持っていければ(MVVM パターンも込みで)結構いいところまでいけるんじゃないんですかね?と期待はしているのですが → http://xaml.jp/

カテゴリー: 雑談 | WPF は死んだのか? はコメントを受け付けていません