f8g

ウィンドウをキャプチャしてアニメーション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バージョン。