月刊ソフト作り!
Make The Software For VisualBasic


『画面キャプチャソフを作る!』

2002.Jun.30
 Presented by kouta_y
感想等は掲示板、苦情はメールへ。


会社
ビットマップをただ表示するんじゃ面白くない。
そこで今回はGDI編総仕上げとして、画面のキャプチャを行い、保存までするものを作ります。


キャプチャとは?

画面のキャプチャとは、その名の通り、今表示している画面を取り込んでしまいます。
筆者のように、何かの操作を1つ1つ説明する場合など、こういうソフトは必須です。
ウィンドウズでは、「Print Screen」キーというキーがあり、このキーを押すことにより全画面をクリップボードにコピー出来ますが、それだけでは不便ですよね。
そこで、ユーザーが任意で範囲指定して、その部分のみを切り取ってファイルに保存するものを今回は作ります。

ちなみに「キャプチャソフト」というのは、筆者が「一番最初に作りたかったソフト」の1つです。
今は違うソフトを使っていますがね。

キャプチャの方法

キャプチャの仕方は大まかに以下の方法で行います。


デスクトップの画像をフォームに貼り付ける
    ↓
最初にマウスがクリック(ダウン)された位置を記憶する
    ↓
マウスボタンがアップした場所を記憶する
    ↓
その範囲を切り取って、別フォームにコピーする
    ↓
保存する


それでは作ってみましょう。
API宣言用として、標準モジュールが1つ。
スタートアップ、キャプチャ用、切り取った画像用として、フォームは3つ挿入します。
各プロパティは以下の通りです。
プロジェクト名は「MyCapture」としています。


Form1
(オブジェクト名) Form1
Caption MyCapture
Form2
(オブジェクト名) Form2
Appearance 0 - フラット
AutoRedraw False
BackColor
BorderStyle なし
Caption なし
ControlBox False
DrawMode 10 - Not Xor Pen
ForeColor
ScaleMode 3 - ピクセル
ShoeInTaskbar False
WindowState 2 - 最大化
Form3
(オブジェクト名) Form3
AutoRedraw True
Caption キャプチャ画面
ScaleMode 3 - ピクセル
StartUpPosition 1 - オーナーフォームの中央


さらにこの他にForm1にコマンドボタンを、Form3にはメニューバーを取り付けます。

Command1
(オブジェクト名) Command1
Caption キャプチャ開始!
メニューバー
m_File(トップ) ファイル(&F)
m_Save 保存(&S)
m_Line -
m_Close 閉じる(&X)


デザイン時のそれぞれの外観はこんな感じです。




ちょっとForm2の設定箇所が多いですね。
このプロパティはAPIでも同じ効果を得られるけど、プロパティの方が楽だ。という感じです。

では各モジュールのソースコードです。
今回出てくるAPIは、既に説明済みのものばかりなので、細かい説明はしません。

Form1

Private Sub Command1_Click()

Me.WindowState = 1
DoEvents
Form2.Show
Call SetWindowPos(Form2.hwnd, HWND_TOPMOST, 0, 0, 0, 0, _
                  SWP_SHOWWINDOW Or SWP_NOSIZE Or SWP_NOMOVE)

End Sub

ここではこんだけ。
メインはForm2で処理します。
Form2

Private hMemdc  As Long
Private hBmp    As Long
Private hOldBmp As Long

Private StartPo As POINTAPI
Private EndPo   As POINTAPI



Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
' ESCで終了
If KeyCode = vbKeyEscape Then Unload Me
End Sub

Private Sub Form_Load()
Dim hDeskdc As Long
Dim w As Long, h As Long

' 画面サイズをピクセルに変換
w = Screen.Width / Screen.TwipsPerPixelX
h = Screen.Height / Screen.TwipsPerPixelY

' デスクットップをコピー
hDeskdc = GetDC(0)
hMemdc = CreateCompatibleDC(0)

hBmp = CreateCompatibleBitmap(hDeskdc, w, h)
hOldBmp = SelectObject(hMemdc, hBmp)

Call BitBlt(hMemdc, 0, 0, w, h, hDeskdc, 0, 0, vbSrcCopy)

Call ReleaseDC(0, hDeskdc)
End Sub

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)

' 範囲選択
If Button = vbLeftButton Then
'   前のを消す
    Call Rectangle(Me.hdc, StartPo.x, StartPo.y, EndPo.x, EndPo.y)
'   新しいのを書く
    Call Rectangle(Me.hdc, StartPo.x, StartPo.y, x, y)
'   新しい座標を取得
    EndPo.x = x
    EndPo.y = y
End If

End Sub

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
If Button = vbLeftButton Then
'   カーソルをクロスにする
    MousePointer = vbCrosshair
'   スタート位置を記憶
    StartPo.x = x
    StartPo.y = y
    EndPo = StartPo
End If
End Sub

Private Sub Form_MouseUp(Button As Integer, Shift As Integer, x As Single, y As Single)
Dim Frm As New Form3    ' キャプチャのたびに作る

If Button = vbLeftButton Then
    MousePointer = vbDefault
    EndPo.x = x
    EndPo.y = y

'   フォームの設定
    Load Frm
    With Frm
'       横の淵も含めた幅を計算
        .Width = (Abs(EndPo.x - StartPo.x) * Screen.TwipsPerPixelX) + _
                 (.Width - (.ScaleWidth * Screen.TwipsPerPixelX))
'       タイトルバーも含めた高さを計算
        .Height = (Abs(EndPo.y - StartPo.y) * Screen.TwipsPerPixelY) + _
                  (.Height - (.ScaleHeight * Screen.TwipsPerPixelY))
        
'       マイナス方向へ範囲選択された時の処理
        If StartPo.x > EndPo.x Then
            StartPo.x = EndPo.x
        End If
        If StartPo.y > EndPo.y Then
            StartPo.y = EndPo.y
        End If
'       フォームへコピー
        Call BitBlt(.hdc, 0, 0, .ScaleWidth, .ScaleHeight, hMemdc, StartPo.x, StartPo.y, vbSrcCopy)
        .Show
    End With
    Unload Me
End If

End Sub

Private Sub Form_Paint()
' AutoRedrawは遅いのでしない
Call BitBlt(Me.hdc, 0, 0, Me.ScaleWidth, Me.ScaleHeight, hMemdc, 0, 0, vbSrcCopy)
End Sub

Private Sub Form_Unload(Cancel As Integer)
' 最前面を解除
Call SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, _
                  SWP_SHOWWINDOW Or SWP_NOSIZE Or SWP_NOMOVE)
' 後処理
Call SelectObject(hMemdc, hOldBmp)
Call DeleteObject(hBmp)
Call DeleteDC(hMemdc)

' 呼び出しフォームを戻す
Form1.WindowState = 0
End Sub
		


毎度ながらばっちぃソースコードです。
順序よく説明しましょう。

まずフォームがロードされたら、デスクトップの画像デバイスコンテキスト(以下DC)を取得します。
再描画用のメモリデバイスコンテキスト(以下メモリDC)も作成します。
作成したメモリDCの色情報(ビットマップ)は、1x1のモノクロですので、そのままBitBltをしても正しく表示されません。
そこで画面と互換のあるビットマップを作成し、それをメモリDCに選択します。
その後BitBltすれば、瓜2つのデスクトップが出来上がりました。
ユーザーには「これがデスクトップだ!」と思わせといて、実はウィンドウ。と騙します。

再描画命令であるPaintイベントでは、そのメモリDCをそのままフォームにコピーしています。


範囲の選択は、ユーザーにドラッグ&ドロップしてもらうようにします。
ドラッグ中、どこを選択しているのかわかるように「四角形」を描画します。
四角形の描画には「Rectangle」APIを使っています。
ここは「現在のペン」を使って描画するものならなんでも構いません。

まずMouseDownイベントで、クリクされた現在の座標を、用意していた「POINT構造体」へ格納します。
POINT構造体のメンバは、

Type POINTAPI
  X As Long
  Y As Long
End Type

です。
あとマウスポインタの形状も変えています。

MouseMoveイベントでは、先程言った四角形の描画を行います。
Rectangle関数を2回呼んでいますが、これは「前のを消して次のを描く」という動作をしているためです。

でもそうすると、四角形が連続しちゃうんじゃないの?

そうです。
四角形を描画する「ペン」が普通のペンではそうなります。
そこで、上で表にしたForm2のプロパティをもう1度みてください。
DrawModeをNot Xor Penにしていますよね?
これが重要です。
これは前景色(つまりペン)の描画方法を「Not Xor」演算を行うことを示しています。
この演算は2回同じ色を描けば元の色に戻ります
この方法で前回描画した四角形を消しています。
あとFillStyleプロパティも「透明」にしておかなくては、四角形の内容が塗りつぶされてしまいます。(ここはデフォルトで設定されています)


んで最後にMouseUpイベントです。
これまで選択してもらった範囲で、切り取った画像用のフォームの大きさを決定します。
長い計算式となっていますが、これはフォームの横の淵と、タイトルバーの高さも計算しているためです。
スタート時の座標とエンド座標の計算はAbs(絶対値)で計算しています。
次にマイナス方向へドラッグされていた場合の処理をします。
ここは値を見て、代入するだけでOKです。
最後にフォームにBitBltでコピーします。
コピーしている範囲に気をつけてください。


あとはUnloadイベント内で、メモリDCなどの後処理を行っています。
ついでにEscキーを押したら、フォームが閉じるようにもしています。

メインの画面キャプチャはこれで以上です。



Form3のコードは、前回までの「ビットマップビューアを作る」で使った保存ルーチンをm_Saveクリックイベントで行います。
ここはサンプルプロジェクトを見てもらうか、ご自分で作ってみてください。




実行結果
画面をキャプチャしたところです。




終わり

これで、グラッフィク関係のソフト作りは終わりにしたいと思います。
ここまでやってビットマップ、如何でしたでしょう?
少しはお判りいただけたと思います。
グラッフィクスは、ハマっていくとものすごい大変です。
そのうち画像の圧縮関係についてもやりたいですね。


今回のサンプルプロジェクト
vb12.lzh(11.9KB)