画面キャプチャーをしてみよう
(目次)
(1)はじめに ('01/11/20)
(2)ソースコードの紹介 ('01/11/25)
(3)JPEG書き出し ('01/11/30)
(4)保存領域の指定 ('01/12/05)
(5)矩形選択の指定位置を拡大表示する ('01/12/10)
日経ソフトウェアのバックナンバー(1999年2月号)でソースコードがダウンロードできるページを見つけました。
http://software.nikkeibp.co.jp/software/download/down99.html
Visual C++6.0をお持ちの方はコンパイルすればキャプチャソフトが作成できます。
(6)AVIファイルの取り扱い ('01/12/16)
(7)AVIファイルからBMP取り出す ('01/12/22)
(8)AVI作成時にエラーダイアログが大量に表示されて操作不能になる不具合を修正しました。 ('04/01/25)
(9)静止画保存をGIF形式に対応しました。動画保存時に連続して保存すると、最初の方に静止画が残ってしまう不具合を修正しました。('07/03/23)
正式公開バージョンをVectorからダウンロードできるようにしました。
最近ちょっと調べものをしていて画面キャプチャーをする必要があったので、Vectorを見てみると数十ものフリーソフトがあった。2,3個ダウンロードして使ってみると、なかなか面白い。 「いったいどうやって作ってるんだろう・・・?」興味本位でネットを検索してみると、Delphiで簡単に作れるというページを見つけたので、早速作ってみました。
使用ソフトはC++ Builder 5.0J
<仕様>
Windowsのデスクトップ画面の任意の場所を画像ファイル(BMP,JPEG,GIF)、動画ファイル(AVI)で保存する。
<実行画面>
こんな画面 ↓ になります。

Download(Vectorへ)(ZIP圧縮) Windows98/Me/NT4.0SP6/2000SP1/XPで動作確認
ネットで検索をしていくと、C++ BuilderとDelphiそれぞれの例が載っていました。これは画面全体をキャプチャーするものです。
(1)C++ Builderの例
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HDC dc;
TImage *bmp = new TImage(this);
bmp->Width = Screen->Width;
bmp->Height = Screen->Height;
dc = GetDC(0);
BitBlt(image1->Canvas->Handle,0,0,bmp->Width,bmp->Height,dc,0,0,SRCCOPY);
if(SaveDialog1->Execute())
image2s->Picture->SaveToFile(SaveDialog1->FileName);
ReleaseDC(0, dc);
delete image1;
}
(2)Delphiの例
procedure TForm1.Button1Click(Sender: TObject); var
bmp:Tbitmap;
dc:hdc;
begin
bmp:=Tbitmap.Create;
bmp.width:=screen.Width;
bmp.height:=screen.Height;
dc:=getdc(0);
bitblt(bmp.canvas.handle,0,0,bmp.width,bmp.height,dc,0,0,srccopy);
if savedialog1.Execute then bmp.SaveToFile(savedialog1.filename);
releasedc(0,dc);
bmp.free;
end;
手順としては、
・画面へのHDC(Handle Device Context)を取得 ... getDC(0);
引数の0は画面全体を示す。
・画面のビットマップを自分で用意したビットマップへ転送 ... BitBlt();
・ファイルに保存 ... SaveToFile();
WindowsプログラムはMicrosoftが用意したルールに沿った書き方をすればいいので、こういった操作は定石として誰が書いても同じようになります。
ラジオボタンを利用して、保存ファイルの形式をビットマップ、JPEGのどちらかに選択できるようにしました。本当はGIFにも対応できるんですが、特許の行方がどうなったのか知りませんので、非対応にしています。ライセンス取得しないとダメなんですよね・・・GIF圧縮ルーチンの使用。
→追記(2001/12/08)
やはりライセンス契約が必要でした。フリーソフト、シェアウェアに関わらず。
http://www.unisys.co.jp/LZW/
→追記(2007/03/23)
日本国内で2004年6月20日にLZW特許が失効したので、GIF対応しました(Ver0.65)。
TJPEGImage *image2;
image2 = new TJPEGImage;
// 品質重視
image2->Performance = jpBestQuality;
// 圧縮率%指定
image2->CompressionQuality = (short int)100;
// 表示方法
image2->ProgressiveEncoding = false;
// BMPをアサイン
image2->Assign(image1->Picture->Bitmap);
// JPEGで保存
if (SaveDialog1->Execute())
image2->SaveToFile(SaveDialog1->FileName);
delete image2;
C++ Builderの機能そのまま利用しているだけです。
ちょっと物足りませんか? あせらず、ゆっくり、ということで・・・。
そろそろキャプチャソフトらしくしていきます。
キャプチャしたいものはウィンドウだけとは限りませんので、デスクトップ画面の任意の領域を指定できるようにします。任意のデスクトップ領域を指定するために、マウスでクリック&ドラッグで長方形領域を選択します。このとき、Windows画面をそのままクリックすると他のアプリケーションに制御が移ってしまうので、デスクトップ画面全体をコピーして、新たなフォームを作成し、そこにコピーしたイメージを貼り付けることにします。
つまり、ユーザーはデスクトップ上で領域選択をしているように見せかけているだけで、実際は新規フォーム上で作業していることになります。
最初に示したコードで画面全体のイメージを取得できますので、これを利用します。画面上で長方形領域を選択する操作で、現在選択している範囲を長方形で表示するようにするには以下のようにします。これでとりあえずキャプチャソフトの基本機能は作成できました。
TPoint FClickPoint,FPrevPoint;
// マウスのボタンが押されたときの処理
void __fastcall TForm2::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
if(Button==mbLeft){ //左ボタンのみ処理
FClickPoint.x = X; //クリックされた位置を記録
FClickPoint.y = Y;
FPrevPoint = FClickPoint; //前回のカーソル位置=クリックされた位置
}
}
void __fastcall TForm2::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
if (Shift.Contains(ssLeft)) { // ボタンが押された場合
Canvas->Pen->Mode = pmNotXor; //描画時に色を反転するように指定
Canvas->Pen->Color = clBlue; //青色を指定
Canvas->Brush->Style = bsClear; //塗り潰しはしない
//前回描いた四角を消す
Canvas->Rectangle(FClickPoint.x,FClickPoint.y,FPrevPoint.x,FPrevPoint.y);
//新しく四角を描く
Canvas->Rectangle(FClickPoint.x,FClickPoint.y,X,Y);
//現在のカーソルの位置を記録しておく
FPrevPoint.x = X;
FPrevPoint.y = Y;
}
}
void __fastcall TForm2::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
if(Button == mbLeft){ //左ボタンが離された場合のみ処理する
//現在のカーソルの位置を記録しておく
FPrevPoint.x = X;
FPrevPoint.y = Y;
Close();
}
}
いろんなホームページで説明がありますが、ビットマップの拡大はStretchBlt関数を利用すると簡単に実現できます。使い方は、BitBltとほとんど同じです。
StretchBlt(コピー先DC, コピー先のx 座標, コピー先のy 座標, コピー先の幅, コピー先の高さ, コピー元DC, コピー元のx 座標, コピー元のy 座標, コピー元の幅, コピー元の高さ, SRCCOPY);
ただ、ネットを検索すると不具合情報がたくさん!
最もpopularなものは、このAPIに最適化されていないグラフィックボードでは、動作が極端に遅くなるそうです。
自力で拡大縮小ルーチンを作成し、BitBltするのがいいみたいですね。面倒くさいけど。
いよいよ今回から動画を扱っていきます。動画というと私はビデオキャプチャボードからMPEG圧縮してファイルを作成する方法を連想するんですが、ここではAVIファイルを扱います。
WindowsにはVideo for Windows(VFW)という比較的利用しやすい手段が提供されています。
VFWの説明については非常にわかりやすい解説をしたページがありました。
http://www.maid.org/
たぶん有名な方だと思うのですが、私は検索するまで知りませんでした。
このHPの技術室にある「プログラミング資料(自作)」
VFWAPI20.LZH Video for Windows AVI API 解説 2.0
というファイルです。
本当にすばらしく、分かりやすく書かれています。
さらにVideo maidというフリーのAVIファイルエディタもあるという。
しかもソースコードを公開しているという話。
ところが・・・
http://www.maid.org/vmaid/
を見ると、なにやら特許絡みで2000年に配布中止されていました。
またGIF同様ここでも特許紛争が勃発しているようです。
特許庁 特許電子図書館で検索してみました。
http://www.ipdl.jpo.go.jp/homepg.ipdl
【特開平07-046462】
【目的】 ビデオ画像を簡単に編集することができる方法及び装置を提供する。
【構成】 映像クリップ(及びオプションとして静止画像と音声クリップ)をコンピュータメモリにディジタルデータとして記憶し、選択したクリップをディスプレィ画面上に引き伸ばしたトラックに表示し、表示されたカーソルとアイコンの操作に対応してクリップに対して編集作業を行い、編集映像プログラムを組み合わせて試写する映像編集方法と装置である。
なるほど、こんなのが特許として認定されるんですね。いったいどこから特許で縛られるのか、わからなくなってきます。ソフト開発はアイディア勝負なので、そのアイディア自体が特許として成立してしまうと回避のしようがありません。
例えば、製造物や製造方法の場合は、結果的に同じ効果が得られるものでも、工法や利用する理論を特許範囲からはずすことで対処できるでしょう。同じ機能を実現していても、その実装方法(具体的にはコード)が異なっていれば、問題にならないと認識していました。
このへんはどうなんでしょうか?
前述のホームページの資料をみればわかるのですが、AVIファイルからビットマップ画像を取り出す方法です。なんかこの方法って定石らしいです。詳しくは解読してないのでわかりません。Cマガジンあたりに載ってたらしい。
BOOL SaveBitmap(LPCSTR lpszFile,LPBITMAPINFOHEADER pbmih)
{
BITMAPFILEHEADER bmfh;
FILE *fp;
bmfh.bfType=0x4d42;
bmfh.bfReserved1=bmfh.bfReserved2=0;
if (pbmih->biClrUsed==0)
switch (pbmih->biBitCount) {
case 1:bmfh.bfOffBits=sizeof(RGBQUAD)*2; break;
case 4:bmfh.bfOffBits=sizeof(RGBQUAD)*16; break;
case 8:bmfh.bfOffBits=sizeof(RGBQUAD)*256; break;
case 24:bmfh.bfOffBits=0; break;
case 16:
case 32:
bmfh.bfOffBits=pbmih->biCompression==BI_RGB?0:sizeof(DWORD)*3;
}
else
bmfh.bfOffBits=pbmih->biClrUsed*sizeof(RGBQUAD);
bmfh.bfOffBits+=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
bmfh.bfSize=bmfh.bfOffBits+(pbmih->biSizeImage=0)
?(((pbmih->biWidth)*(pbmih->biBitCount)+31)&~31)/8*abs(pbmih->biHeight):pbmih->biSizeImage;
if ((fp=fopen(lpszFile,"wb"))==NULL)
return FALSE;
if (fwrite(&bmfh,sizeof(BITMAPFILEHEADER),1,fp)!=1
|| fwrite(pbmih,bmfh.bfSize-sizeof(BITMAPFILEHEADER),1,fp)!=1)
return FALSE;
return fclose(fp)==0;
}
void TForm1::avitobmp()
{
AVIFILEINFO fi;
AVISTREAMINFO si;
char szFile[MAX_PATH], dmy[256];
DWORD dwStream;
LONG i,lStart,lEnd;
LPBITMAPINFOHEADER pbmih;
PAVIFILE pavi;
PAVISTREAM pstm=NULL,ptmp;
PGETFRAME pfrm;
WORD wVideo=~0;
AVIFileInit();
if (AVIFileOpen(&pavi,_T(OpenDialog1->FileName.c_str()), OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return;
if (AVIFileInfo(pavi,&fi,sizeof(AVIFILEINFO))!=0)
return;
for (dwStream=0;dwStream<fi.dwStreams;dwStream++) {
if (AVIFileGetStream(pavi,&ptmp,0,dwStream)!=0)
return;
if (AVIStreamInfo(ptmp,&si,sizeof(AVISTREAMINFO))!=0)
return;
switch(si.fccType) {
case streamtypeVIDEO:
if (pstm==NULL || si.wPriority<wVideo) {
if (pstm!=NULL)
AVIStreamRelease(pstm);
pstm=ptmp;
wVideo=si.wPriority;
}
continue;
}
AVIStreamRelease(ptmp);
}
if (pstm==NULL)
return;
if ((pfrm=AVIStreamGetFrameOpen(pstm,NULL))==NULL)
return;
lStart=AVIStreamStart(pstm);
lEnd=AVIStreamLength(pstm)+lStart;
for (i=lStart;i<lEnd;i++) {
if ((pbmih=(LPBITMAPINFOHEADER)AVIStreamGetFrame(pfrm,i))==NULL)
return;
strcpy(szFile, Edit1->Text.c_str());
sprintf(dmy, "%02d.BMP", i);
strcat(szFile, dmy);
if (!SaveBitmap(szFile,pbmih))
return;
}
if (AVIStreamGetFrameClose(pfrm)!=0)
return;
AVIStreamRelease(pstm);
AVIFileRelease(pavi);
AVIFileExit();
return;
}