f8g

Silverligt 2メモ

久々にSilverlightでもやってみたくてVisual Studio 2008 用 Silverlight Toolsをインストールしようとしたら、Visual Web Developerが必要、みたいなことを言われた。

使ったもの

開発は楽っぽいけど準備が面倒。Chironを使った方が準備は楽かも。

コード覚書

あ、何かVBだ。IronRubyとかは使えないんだろうか。

XAML

全部手書きかよ。

windowとdocument

そういったものはWindows.Browser.HtmlPageに用意されていて、それを使ってスクリプトのやり取りができる。DOMはIEっぽい。

Dim window As Browser.HtmlWindow = Browser.HtmlPage.Window
Dim document As Browser.HtmlDocument = Browser.HtmlPage.Document
Dim input As Browser.HtmlElement = document.CreateElement("input")

document.Body.AppendChild(input)
input.AttachEvent("onkeyup", AddressOf keyevent)
プロパティとメソッド

プロパティの取得, 設定にはGetProperty, SetPropertyを使う。Silverlightのオブジェクトも設定できるので、ブラウザ側からもコントロールできる、と思う。

Dim textbox As New TextBox
window.SetProperty("aaa", textbox)

ブラウザ側のメソッドを呼び出すときはinvokeを使う。

' Firebugのconsole.logを呼び出す
Dim console As Browser.ScriptObject = window.GetProperty("console")
console.Invoke("log", 1)

ビルド

SilverlightApplication1で作ったら、他のVisual Studioと同じように
\My Documents\Visual Studio 2008\Projects\SilverlightApplication1\SilverlightApplication1\Bin\Release
に出力される。使わなそうな言語関係のファイルとかも勝手に。

感想

VWD2008EEでコントロール作るのはちょっと面倒くさそう。.NETの一部を借りたくなったときに使ったりするのには便利かも。

JSActionsでGPSから座標を取得してGoogleMapsに反映

Tombloo + JSActionsでWSHを実行するコードは下ページからのコピペです。凄い簡単にFirefoxからWSHが使える。
http://d.hatena.ne.jp/brazil/20090102/1230876349

GPSからデータを取得する

GPSからデータを取得するのに自作コンポーネントを使ってますが、MSCommを使ったりとか、そもそもWSHを使わないとか、色々方法はありますんでその辺は適当に。
自作コンポーネントはこんなの: http://d.hatena.ne.jp/arikui/20071212/1197453931

GoogleMapsを用意する

window.map = new GMap2(document.getElementById("map"));

みたいに、外からGoogleMapsのAPIが使えるやつ。

コード

JSActionsのコードからwindow.mapを参照できなかったので、javascriptスキームで座標を指定してます。

var T = Components.classes['@brasil.to/tombloo-service;1'].getService().wrappedJSObject;

(function(times){
	var data = "";
	var count = 1;

	var timer = setInterval(function(){
		data += getGpsData();

		var coord = getCoordinates();

		if(coord)
			window.location.href = "javascript:window.map.setCenter(new GLatLng("+coord+"));void(0);"

		if(++count > times)
			clearInterval(timer);
	}, 1000);

	function getCoordinates(){
		var _data = data.split("\n");
		var match = null;

		while(_data.length){
			if(match = /\$GPGGA.{12}(.{9}).{3}(.{10})/.exec(_data.pop())){
				data = _data.join("\n");

				var coord = [
					parseInt(match[1].substr(0, 2), 10) + match[1].substr(2) / 60,
					parseInt(match[2].substr(0, 3), 10) + match[2].substr(3) / 60,
				];

				if(coord[0] && coord[1])
					return coord;
				else
					return null;
			}
		}

		return null;
	}
})(5);

function getGpsData(){
	return executeWSH(function(){
		var gps = WSH.CreateObject("CommPort.Comm");

		gps.port = 23;
		gps.BaudRate = 4800;
		gps.Open();

		var s = gps.Input();

		WSH.Sleep(1000);

		s += gps.Input();

		gps.Close();

		return s;
	});
}

// http://d.hatena.ne.jp/brazil/20090102/1230876349
function executeWSH(func, args){
  args = args || [];
  
  with(T){
    var bat = getTempFile('bat');
    var script = getTempFile();
    var out = new LocalFile(script.path + '.out');
    
    putContents(bat, [
      'cscript //E:JScript //Nologo', 
      script.path.quote(), 
      '>', 
      out.path.quote()].join(' '));
    putContents(script, 
      args.map(function(a, i){return 'var ARG_' + i + ' = ' + uneval(a) + ';'}).join('\n') + 
      'WScript.echo(' + func.toSource() + '(' + 
      args.map(function(a, i){return 'ARG_' + i}).join(',') + 
      '));');
    
    new Process(bat).run(true, [], 0);
    
    var res = getContents(out).replace(/\s+$/, '');
    
    bat.remove(false);
    script.remove(false);
    out.remove(false);
    
    return res;
  }
}

ウィンドウをキャプチャしてAVI動画を作る (.NET)

機能のつづき: ウィンドウをキャプチャしてアニメーションGIFを作る (.NET) - f8g

AVIファイルの生成

WindowsではDirectShowを使ってAVIファイルを作ることができます。だた、DirectShowを.NETで使おうとすると結構面倒なので、これもまたその辺のライブラリを使って簡単にやります。
http://www.codeproject.com/KB/audio-video/avifilewrapper.aspx
昨日みたいに適当にコンパイルして参照しちゃいましょう。

' 下記以外は前と一緒
Dim AviManager As AviFile.AviManager
Dim AviStream As AviFile.VideoStream

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
    Dim bmp As Bitmap = Me.GetBitmapByHwnd(Me.GetHwndByTitle("メモ帳"))

    ' 最初のフレームか
    If Me.AviStream Is Nothing Then
        Timer1.Enabled = False ' 一回止めないとヤバイ
        Me.AviStream = Me.AviManager.AddVideoStream(True, 1.0! / (Timer1.Interval / 1000.0!), bmp)
        Timer1.Enabled = True
    Else
        Me.AviStream.AddFrame(bmp)
    End If
    
    bmp.Dispose()
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    If SaveFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
        Me.AviManager = New AviFile.AviManager(SaveFileDialog1.FileName, False)
        Timer1.Enabled = True
    End If
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    If Timer1.Enabled Then
        Timer1.Enabled = False
        Me.AviManager.Close()
    End If
End Sub

最初のフレームのとき止めないとヤバイのは、
http://gyazo.com/361b4552301d150916e57b0ffb582e40.png
みたいなダイアログが出てきてから動画を作るのに、バックグラウンドでTimerが動いてて、変なことになるからです。多分。
今回利用したライブラリは動画を作るだけじゃなく音声も付けられるようなので、自作ラップと一緒に動画にして楽しむ、といった使い方もできます。

ウィンドウをキャプチャしてアニメーションGIFを作る (.NET)

http://data.tumblr.com/CAiSbEIuHhrg1dcctc8PiWiUo1_500.gif
.NET Framework v3.5で動作確認。(v2.0.50727でも動くと思う)

使ったもの

ウィンドウのキャプチャ

' Imports System.Runtime.InteropServices

''' <summary>
''' GetWindowRectで使う構造体
''' </summary>
<StructLayout(LayoutKind.Sequential)> _
Structure RECT
    Public left As Integer
    Public top As Integer
    Public right As Integer
    Public bottom As Integer
End Structure

''' <summary>
''' 画像を別のコンテキストに送る
''' 参考: http://yokohama.cool.ne.jp/chokuto/urawaza/api/BitBlt.html
''' </summary>
<DllImport("gdi32.dll")> _
Private Shared Function BitBlt(ByVal hDestDC As IntPtr, _
                               ByVal x As Integer, _
                               ByVal y As Integer, _
                               ByVal nWidth As Integer, _
                               ByVal nHeight As Integer, _
                               ByVal hSrcDC As IntPtr, _
                               ByVal xSrc As Integer, _
                               ByVal ySrc As Integer, _
                               ByVal dwRop As Integer) As Integer
End Function

''' <summary>
''' ウィンドウを最前面に出す
''' </summary>
<DllImport("user32.dll")> _
Private Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As Boolean
End Function

''' <summary>
''' ウィンドウの座標を取得
''' </summary>
<DllImport("user32.dll")> _
Private Shared Function GetWindowRect(ByVal hwnd As IntPtr, ByRef rect As RECT) As Boolean
End Function

''' <summary>
''' ウィンドウの座標を取得
''' </summary>
Private Shared Function GetWindowRect(ByVal hwnd As IntPtr) As RECT
    Dim rec As RECT
    GetWindowRect(hwnd, rec)
    Return rec
End Function

''' <summary>
''' ウィンドウのハンドルをタイトル(正規表現)から取得
''' </summary>
Private Function GetHwndByTitle(ByVal title As String) As IntPtr
    Dim hwnd As IntPtr = Nothing
    Dim processes() As Process = Diagnostics.Process.GetProcesses()

    For Each p In processes
        If System.Text.RegularExpressions.Regex.IsMatch(p.MainWindowTitle, title) Then
            hwnd = p.MainWindowHandle
            Exit For
        End If
    Next

    Return hwnd
End Function

''' <summary>
''' ウィンドウの画像を取得
''' </summary>
Private Function GetBitmapByHwnd(ByVal hwnd As IntPtr) As Bitmap
    ' ウィンドウが後ろに隠れてるとちゃんとキャプチャできませんので
    SetForegroundWindow(hwnd)
    System.Threading.Thread.Sleep(100)

    Dim rect As RECT = GetWindowRect(hwnd)
    ' GetWindowRectのサイズとGraphics.FromHwndのサイズが違うので適当に調節
    Dim bmp As New Bitmap(rect.right - rect.left - 8, rect.bottom - rect.top - 50)
    Dim image As Graphics = Graphics.FromImage(bmp)
    Dim window As Graphics = Graphics.FromHwnd(hwnd)

    Dim imageHdc As IntPtr = image.GetHdc
    Dim windowHdc As IntPtr = window.GetHdc

    BitBlt(imageHdc, 0, 0, bmp.Width, bmp.Height, windowHdc, 0, 0, 13369376) ' SRCCOPY: 13369376
    image.ReleaseHdc(imageHdc)
    window.ReleaseHdc(windowHdc)

    Return bmp
End Function

アニメーションGIFの生成

System.Drawing.BitmapにはSaveAddというメソッドがありますが騙されてはいけません。これで作れるのはマルチフレームなTIFFだけです。GDI+ではアニメーションGIFが作れないらしいので、バイナリに直接書き込んでいくしかなさそうです。今更GIFの仕様とか理解するのは面倒なので、その辺に転がってるライブラリを使います。
http://www.codeproject.com/KB/GDI-plus/NGif.aspx
ログインしてダウンロードしたら適当にコンパイル

> c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc /target:library /out:NGif.dll /warn:0 /nologo /debug *.cs

http://gyazo.com/d561219a660171e601d4f5309fd5f675.png

Dim GifEncoder As New Gif.Components.AnimatedGifEncoder

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Timer1.Interval = 500
End Sub

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
    Dim bmp As Bitmap = Me.GetBitmapByHwnd(Me.GetHwndByTitle("メモ帳"))
    Me.GifEncoder.AddFrame(bmp)
    bmp.Dispose()
End Sub

''' <summary>
''' キャプチャ開始
''' </summary>
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    If SaveFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
        Me.GifEncoder.Start(SaveFileDialog1.FileName)
        Me.GifEncoder.SetFrameRate(1.0! / (Timer1.Interval / 1000.0!))
        Me.GifEncoder.SetRepeat(0)
        Timer1.Enabled = True
    End If
End Sub

''' <summary>
''' キャプチャ終了
''' </summary>
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    If Timer1.Enabled Then
        Timer1.Enabled = False
        Me.GifEncoder.Finish()
    End If
End Sub

次回はAVIバージョン。

netkeibaの出馬表に過去の競争成績を表示するGreasemonkey(修正)

http://d.hatena.ne.jp/arikui/20060513/1147528160
久々に動かしたら動かなくなってたので修正。多少軽くもなったか。
http://data.tumblr.com/CAiSbEIuHfr1qq58xaMUPkcno1_500.png
pastRaceNumを変えると、過去何走表示するかを変更できます。

画像を分割してランダムに配置するモザイク

http://gyazo.com/455ae05c5f70b64ae4a088e48790fccf.png
カラー画像だと少々面倒臭そうなので、グレースケールに変換してからやります。手順はこんな感じです。

  1. グレースケール化
  2. 10 * 10 に分割
  3. 分割画像のRGBを平均して分類
  4. 分類ごとにランダムソート
  5. 再配置

コード

使用

ImageProcessing.prototype.f= function(fnc){
	return fnc(this);
};

var clips = {};

ip = ImageProcessing
	.load("image.png")
	.lock()
	// グレースケール
	.each(function(px, x, y, self){
		self.setPixel(x, y, px.grayScale());
	})
	.update()
	// 分割して色ごとにまとめる
	// 分割した画像の平均でまとめるてるので結構適当
	.f(function(self){
		var clip, color;

		for(var x = 0; x > self.canvas.width - 10; x += 10){
			for(var y = 0; y < self.canvas.height - 10; y += 10){
				clip = self.clip(x, y, 10, 10);
				color = Math.round(clip.average().r);

				if(clips[color])
					clips[color].push(clip);
				else
					clips[color] = [clip];
			}
		}

		return self;
	})
	// 分割した画像をごちゃ混ぜにして再配置
	.f(function(self){
		var clone = ip.clone().clear(); // 別のCanvasに書き込む

		for(var n in clips) (function(parts, n){
			var positions = parts.map(function(clip){
				clip.toString = function(){
					return Math.random();
				};

				return [clip.origin.x, clip.origin.y];
			});

			parts.sort();

			parts.forEach(function(clip, i){
				clone.merge(clip, positions[i][0], positions[i][1]);
			});
		})(clips[n], n);

		return clone;
	})
	.update();

問題点

この方法だと、分割画像の平均値がユニークなとき、その画像は結局同じ場所に配置されてしまいます。RGBで分類する場合であれば、なおさらその傾向が出てしまいます。
より細かく分割するとか、分類するときに閾値を持たせてやれば、ある程度回避できそうです。

感想

最近こんなことばっかりやってて飽きてきました。

Tombloo Mozaic でソート by 色

http://media.tumblr.com/CAiSbEIuHf56x5ckvY5gAzbMo1_500.png
Greasemonkeyでやろうとしたけど動かなかったんで、そのままHTMLにコード書き込みました。HTMLは通常、
Firefoxのプロファイルディレクトリ\extensions\tombloo@brasil.to\chrome\content\library\Mosaic.html
にあります(多分)。Mosaic.htmlの一番下の方にでもコードを書いてやります。

<script src="http://github.com/arikui/image_processing.js/tree/master%2Fimage_processing.js?raw=true"></script>
<script type="text/javascript">
(function(){
	// sort button
	var button = $("control").appendChild(document.createElement("button"));
	button.innerHTML = "sort";
	var photos = [];

	button.onclick = function(){
		this.disabled = true;
		var i = 0;

		// 画像から色を取って配列に入れてる
		deferredForEach($("mosaic").getElementsByTagName("img"), function(photo){
			document.title = i++ / 10 + "%"; // 進捗状況が一目で分かる!

			// 画像がない時は何もしません
			if(!photo.naturalWidth)
				return wait(0);

			photos.push({
				image   : photo,
				color   : ImageProcessing.load(photo.src).average(),
				toString: function(){
					return ("00" + Math.round(this.color.average()).toString()).slice(-3);
				}
			});

			return wait(0);
		})
		// 配列に入れたやつをソートして戻してる
		.addCallback(function(){
			document.title = "complete";

			photos.sort();

			$("mosaic").innerHTML = "";

			photos.forEach(function(photo){
				$("mosaic").appendChild(photo.image);
			});
		})
		.callback();
	};
})()
</script>

sortボタンが現れます。
http://media.tumblr.com/CAiSbEIuHf57m7wlFUs7dBKzo1_500.png
押して長時間するとソートされます。

応用

画像からモザイク画が作れそうですね。