Delphi Programming / Object Pascal
[掲載 2022年01月17日] [更新 2022年01月21日] Delphi サンプルプログラム集
988_Windows 10 のマウスカーソルのサイズと画像取得
動作確認等
Windows 10 ビルド 19042 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
988_cursorsize_win10.zip [11,5 MB] 2022年01月21日版 (EXE 同梱)
Windows 10 のマウスカーソルのサイズと画像取得
Windows 10 ではマウスカーソルのサイズ,つまり,マウスポインタのサイスが 256 x256 まで設定てぎます.この値はマウスカーソルの画像サイズです.実際のカーソルの画像は必ずしもこの大きさにはなりません.
本ページは,マウスカーソル画像のサイズに応じたカーソル画像の取得方法について検討していきます.
01_ GUI によるマウスカーソル ( マウスポインタ ) サイズの変更
以下のコマンドを実行して設定画面を起動します.その設定画面のスライダーでマウスポインタのサイズを変更でききす.スライダになっていますが,連続的ではなく,実際には飛び飛びの値になります.
[備考]
コマンドを実行するには [ファイル名を指定して実行] のダイアログを使用するか,上のリンクをクリックします.コマンドプロンプトからも実行できますが,先頭に start が必要です.
[ ファイル名を指定して実行] のダイアログは [Win] + [R] のキー操作で起動できます.
図1
マウスポインタの設定画面
マウスポインタの色も変更できます.上の画面では右下の部分をクリックすると色の選択状態となります.
マウスポインタのサイズや色を変更すると,マウスポインタのデザインがデフォルトの状態に戻されることがあります.
図2
マウスポインタの色を変更
マウスポインタのデザインは,下図のダイアログで設定できます.このダイアログは以下のコマンドを実行すると起動します.このコマンドは,[Win] + [R] のキー操作で [ファイル名を指定して実行] ダイアログを起動して実行できます.このコマンドは,コマンドプロンプトからも実行できます.
図3
[マウスのプロパティ] の設定ダイアログ
Windows 7 では,マウスカーソルの画像サイズは 32 x 32 まででした.Windows 10 では最大 256 x 256 まであります.それぞれの画像サイズに対して,下図のように 3 つの実カーソルサイズが設定できます.上の [マウスのプロパティ] のダイアログで,選択中のカーソルをダブルクリック,または [参照] ボタンから設定できます.
下図は,画像サイズが全て 144 x 144 のマウスカーソルです.
図4
同じ画像サイズ内でカーソルの実サイズを変更できる
画像の実サイズ (幅 x 高さ) は左から
59 x 84
66 x 110
82 x 131
02_ マウスポインタのサイズとマウスカーソルの画像
マウスポインタのサイズとマウスカールファイルの情報は,レジストリの以下のキーにあります.
CursorBaseSize がマウスカーソル画像のサイズです.Arrow 等の項目に実際のマウスカーソルのファイルのパス名があります.このファイルを読み込んで調べてみます.
HKEY_CURRENT_USER\Control Panel\Cursors
図5
レジストリのマウスカーソル関係
最小サイズのマウスカーソルの場合です.これが Windows 10 のデフォルトのサイズで,32 です.マウスカーソルの画像は正方サイズですから 32 x 32 です.ファイル名は以下です.
画像の市松模様は背景です.半透明のピクセルがあるので,アルファチャンネル付きの画像で,マルチカーソル画像のファイルになっています.
%SystemRoot%\cursors\aero_arrow.cur
図6
32 x 32
サイズが 64 x 64 のマウスカーソルです.カーソルファイル名が 32 x 32 の時とは違っていますが,やはりマルチカーソル画像のファイルとなっています.
%LOCALAPPDATA%\Microsoft\Windows\Cursors\arrow_eoa.cur
図7
64 x 64
サイズが 112 x 112 のマウスカーソルです.カーソルファイル名が 64 x 64 の時と同じになっています.これもマルチカーソル画像のファイルとなっています.
%LOCALAPPDATA%\Microsoft\Windows\Cursors\arrow_eoa.cur
図8
112 x 112
サイズが 192 x 192 のマウスカーソルです.カーソルファイル名が 64 x 64 の時と同じになっています.これはシングル画像です.
%LOCALAPPDATA%\Microsoft\Windows\Cursors\arrow_eoa.cur
図9
192 x 192
サイズが 256 x 256 のマウスカーソルです.これが最大サイズです.カーソルファイル名が 64 x 64 の時と同じです.これはシングル画像です.
カーソルファイル名が同じなのに何故,異なるサイズの画像が格納されているのか,その仕組みは分かりませんが,Windows 10 のマウスカーソルは基本的にマルチ画像のファイルであって,アルファチャンネル付きの画像であることが確認できました.
%LOCALAPPDATA%\Microsoft\Windows\Cursors\arrow_eoa.cur
図10
256 x 256
03_ スクリーンに表示中のカーソル画像を取得 - マウスキャプチャ
一般的に,スクリーンスクリーンキャプチャのコードだけではマウスカーソルを含めた画像を取キャプチャすることはできません.マウスカーソルを含めたキャプチャをしたい場合は,マウスカーソルの画像を別に取得してキャプチャした画像に重ね描画します.それがマウスカーソルのキャプチャ,マウスキャプチャです.
このサンプルは,ボタンをクリックすると 5 秒後にスクリーンに表示中のマウスカーソルをキャプチャして TImage にその画像を描画します.マウスポインタのサイズを 144 x 144 にしていますが,取得した画像のサイズは 32 x 32 です.
図11
マウスキャプチャの結果
マウスポインタのサイズは 144 x 144 であるが,取得画像のサイズは 32 x 32
右のマウスカーソルの画像が実際のサイズ
リスト1
マウスカーソルをキャプチャするコードの例
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Image1: TImage;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
Winapi.MMSystem;
{$R *.dfm}
// =============================================================================
// マウスカーソ画像をキャプチャ
//
// マウスカーソルのハンドルを取得
// そのハンドルを使用して DrawIconEx でマウスカーソルの画像を取得
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LtartTime : DWORD;
LCursorPos : TPoint;
LCursorInfo : TCursorInfo;
begin
// 指定時間が経過したらキャプチャ
LtartTime := timeGetTime;
while (timeGetTime - LtartTime) < 5000 do begin
Sleep(100 );
Application.ProcessMessages;
end ;
// カーソルの位置を取得
GetCursorPos(LCursorPos);
// 現在のスクリーンに表示中のマウスカーソルの情報を取得
LCursorInfo.cbSize := SizeOf(TCursorInfo);
LCursorInfo.flags := CURSOR_SHOWING;
GetCursorInfo(LCursorInfo);
// その情報を元に,Image1に描画する
Image1.Picture.Assign(nil );
DrawIconEx(Image1.Canvas.Handle, 0 , 0 , LCursorInfo.hCursor,
0 , 0 , 0 , 0 , DI_DEFAULTSIZE or DI_NORMAL);
// 解放・破棄の処理
DestroyCursor(LCursorInfo.hCursor);
end ;
end .
上のコードは従来から使用されているマウスカーソルのキャプチャ用コードです.当サイトにもネット上にも掲載されています.
Windows 10 で実際のサイズのマウスカーソル画像を取得するには,DrawIconEx 関数の引数に,取得するサイズを指定します.フラグには DI_IMAGE を指定します.DrawIconEx は,指定されたサイズの画像を取得します.指定したサイズの画像がない場合は,画像を拡大または縮小して取得します.
既に確認したように,Windows 10 のマウスカーソル画像はアルファチャンネル付きの画像です.Windows 10 のマウスカーソルのマスク画像は全てのピクセルが 0 の透明画像です.つまり,マスク画像は使用していません.
DI_NORMAL は DI_MASK or DI_IMAGE の画像です.新規に作成した画像,ビットマップの場合,これは背景が「白」の画像です「.そこに or 描画したのが結果の DI_NORMAL です.したがって DI_NORMAL の画像は背景が「白」の画像となり,画像の「白」と区別できまくなります.
下図が取得結果ですが,ホットスポットの値が正しくありません.
図12
指定サイズのカーソル画像を取得
ホットスポットの値 (16, 16) は正しくない
正確な値は (71, 71)
リスト2
指定サイズのマウスカーソルの画像をビットマップとして取得
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Image1: TImage;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
Winapi.MMSystem;
{$R *.dfm}
// =============================================================================
// マウスカーソ画像をキャプチャ
//
// マウスカーソルのハンドルを取得
// そのハンドルを使用して DrawIconEx でマウスカーソルの画像を取得
//
// (1) DrawIconEx で取得画像サイズを指定する
// (2) DI_DEFAULTSIZE フラグを使用しないで DI_IMAGE だけを指定する
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LtartTime : DWORD;
LBitmap : TBitmap;
LCursorPos : TPoint;
LCursorInfo : TCursorInfo;
LIconInfo : TIconInfo;
LImgSize : Integer;
LText : string ;
begin
// 指定時間が経過したらキャプチャ
LtartTime := timeGetTime;
while (timeGetTime - LtartTime) < 5000 do begin
Sleep(100 );
Application.ProcessMessages;
end ;
// カーソルの位置を取得
GetCursorPos(LCursorPos);
// 現在のスクリーンに表示中のマウスカーソルの情報を取得
LCursorInfo.cbSize := SizeOf(TCursorInfo);
LCursorInfo.flags := CURSOR_SHOWING;
GetCursorInfo(LCursorInfo);
// 画像のサイズを指定
LImgSize := 144 ;
LBitmap := TBitmap.Create;
try
LBitmap.SetSize(LImgSize, LImgSize);
DrawIconEx(LBitmap.Canvas.Handle, 0 , 0 , LCursorInfo.hCursor,
LImgSize, LImgSize, 0 , 0 , DI_IMAGE);
// Windows 10 のマウスカーソルはアルファチャンネルを使用した 32 bit の画像
// したがって,以下の設定が必要
LBitmap.PixelFormat := pf32bit;
LBitmap.AlphaFormat := afDefined;
Image1.Picture.Assign(nil );
// 透過状態を確認するために 背景の TImage を塗り潰す
Image1.Canvas.Brush.Color := clSilver;
Image1.Canvas.FillRect(LBitmap.Canvas.ClipRect);
// TBitmap を TImage に描画
Image1.Canvas.Draw(0 , 0 , LBitmap);
finally
FreeAndNil(LBitmap);
end ;
// アイコン画像としての情報を取得してホットスポットの値を表示
GetIconInfo(LCursorInfo.hCursor, LIconInfo);
LText := 'xHotspot : ' + IntToStr(LIconInfo.xHotspot) + sLineBreak
+ 'yHotspot : ' + IntToStr(LIconInfo.yHotspot);
MessageBox(Handle, PChar(LText), 'Hotspot' , MB_ICONINFORMATION);
// 解放・破棄の処理
DeleteObject(LIconInfo.hbmMask);
DeleteObject(LIconInfo.hbmMask);
DestroyCursor(LCursorInfo.hCursor);
end ;
end .
上のコードでカーソル画像が取得できるは,Windows 10 のマウスカーソル画像のように,アルファチャンネル付きのカーソル画像だけです.24 ビット以下で作成それたカーソル画像の場合は基本的に,マスク画像を使用した処理が必要です.下図の,Delphi 特有のマウスカーソルも当然取得できません.
図13
Delphi のマウスカーソル
上のコードではカーソル画像を取得できない
左上から
crDrag,
crNoDrop
crHSplit
crVSplit
crMultiDrag
crSQLWait
これらのマウスカーソルの画像は 1 ビットのモノクロ画像です.
Windows 10 では,Windows Vista で登場した Aero (アエロ) と呼ばれるカーソルファイルが主流です.Aero は,透過色に透明色を使用する,アルファチャンネルを使用する半透明のピクセルがある画像形式ですが,下図のよいに,1 ビット (2 色), 8 色,16 色,256 色等のカーソル画像もあります.マウスポインタのサイズをスライダで設定して,そのままの状態で使用しているだけであれば,カーソル画像を取得すのは比較的容易です.しかし,下図のような古い形式のカーソル画像も取得するとなると面倒にります.
図14
Delphi の crMultiDragカーソル
[備考]
アイコンとマウスカーソルはデータ構造が同じです.アイコンかマウスカーソルかは,アイコンヘッダ (マウスカーソルヘッダと同じ) の idType メンバの値で判定します.この値が 1 の時がアイコン,0 の時がマウスカーソルです.
04_ Windows 10 に
おけるマウスキャプチャの手順
Windows 10 のマウスカーソルの画像を取得するには,以下の情報が必要です.特に 3 の,レジストリの項目に相当するマウスカーソル名が分かれば,そのファイルを読み込んで画像が取得できることになります.実際の画像を読み込めば,ホットスポットの正確な値の取得も期待できます.
マウスカーソルの画像サイズ
マウスカーソルのハンドル
Windows 10 におけるマウスカーソル名 (レジストリの項目名)
ホットスポットの値
これらの処理を関数あるいは手続きとして作成していきます.
マウスカーソル画像のサイスは既に調べたように,レジストリから取得できます.その例です.
図15
マウスカーソルの画像サイズ
リスト3
マウスカーソルの画像サイズをレジストリから取得
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
function GetMouseCursorSize: Integer;
var
Form1: TForm1;
implementation
uses
System.Win.Registry;
{$R *.dfm}
// =============================================================================
// マウスカーソルのサイズを取得
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LSize : Integer;
LText : string ;
begin
LSize := GetMouseCursorSize;
LText := 'マウスカーソルのサイズ' + sLineBreak + IntToStr(LSize);
MessageBox(Handle, PChar(LText), '情報' , MB_ICONINFORMATION);
end ;
// -----------------------------------------------------------------------------
// 現在のマウスカーソルのサイズを取得する関数
// -----------------------------------------------------------------------------
function GetMouseCursorSize: Integer;
var
LRegistry : TRegistry;
LKeyName : string ;
begin
Result := 0 ;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyName :='Control Panel\Cursors' ;
if LRegistry.OpenKey(LKeyName, False) then begin
Result := LRegistry.ReadInteger('CursorBaseSize' );
LRegistry.CloseKey;
end ;
finally
FreeAndNil(LRegistry);
end ;
end ;
end .
06_ Delphi の TCursor 型の値と Windows 10 のマウスカーソル名
Delphi では,マウスカーソルの種類を TCursor 型で定義しています.それを列挙してみます.
図16
Delphi の TCursor 型のマウスカーソル名
リスト4
Delphi の TCursor 型のマウスカーソル名を列挙
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
{$R *.dfm}
// =============================================================================
// Delphi のマウスカーソル TCursor 型の値を列挙
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LName : string ;
LIndex : Integer;
begin
ListBox1.Items.Clear;
ListBox1.Items.BeginUpdate;
for LIndex := -1 downto -22 do begin
LName := CursorToString(TCursor(LIndex));
if StrToIntDef(LName, 0 ) = 0 then begin
ListBox1.Items.Add(LName);
end ;
end ;
ListBox1.Items.EndUpdate;
end ;
end .
Windows 10 のマウスカーソル名の一覧です.これは,レジストリの該当キーの項目のリストです.Delphi には専用のマウスカーソルがあるので,Windows 10 のマウスカーソルより多くあります.
図17
Windows 10 のマウスカーソル名一覧
リスト5
Windows 10 のマウスカーソル名一覧
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
System.Win.Registry;
{$R *.dfm}
// =============================================================================
// Windows 10 のマウスカーソル名を列挙
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LRegistry : TRegistry;
LKeyName : string ;
LItemList : TStringList;
LItemType : TRegDataType;
LIndex : Integer;
begin
LRegistry := TRegistry.Create;
LItemList := TStringList.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyName :='Control Panel\Cursors' ;
// キー内の項目のリストを取得
LRegistry.OpenKey(LKeyName, False);
LRegistry.GetValueNames(LItemList);
// REG_EXPAND_SZ 型 (可変長の文字列型) の値だけを列挙
ListBox1.Items.Clear;
ListBox1.Items.BeginUpdate;
for LIndex := 0 to LItemList.Count - 1 do begin
if LItemList[LIndex] = '' then Continue;
LItemType := LRegistry.GetDataType(LItemlist[LIndex]);
if LItemType = rdExpandString then begin
ListBox1.Items.Add(LItemList[LIndex]);
end ;
end ;
ListBox1.Items.EndUpdate;
LRegistry.CloseKey;
finally
FreeAndNil(LRegistry);
FreeAndNil(LItemList);
end ;
end ;
end .
GetCursorInfo 関数を使用して,スクリーンに表示中のマウスカーソルのハンドルを取得します.
Delphi てば TControl.Cursor あるいは Screen.Cursor プロパティでマウスカーソルを設定できます.その時のマウスカーソルのハンドルは,Screen.Cursor の場合は Screen.Cursors[ID] です.ID はマウスカーソルの種類,つまり,マウスカーソルの名前に応じた値です.
もし,両方で一致するハンドルの値があれば,それは Delphi の TCursor 型に存在するマウスカーソルということになります.更に,Windows 10 にもそれと同じマウスカーソルがあれば,カーソルファイルのパス名を知ることができます.
本サンプルはそのことを確認するテストです.そのために,いくつかの関数を作成しています.
ボタンをクリックすると,5 秒後にスクリーンに表示中のマウスカーソルが Windows 10 のマウスカーソルかを調べて,そうであれば,カーソルファイルのパス名を TEdit に表示します.
掲載している画像は,ボタンクリックの後に Screen.Cursor プロパティでマウスカーソルを設定して実行画面をキャプチャしています.
図18
Delphi の crHandPoint の場合
Windows 10 の Hand に相当するマウスカーソル
図19
Delphi の crMultiDrag
Windows 10 にないマウスカーソル
したがって,結果は空文字
リスト6
スクリーンに表示中のマウスカーソルが Windows 10 のマウスカーソルかを調べるテスト
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
function GetShowCursorHandle: HCURSOR;
function GetCursorFilePath: string ;
function GetMouseCursorFilePath(CursorID: TCursor): string ;
var
Form1: TForm1;
implementation
uses
System.Win.Registry,
Winapi.MMSystem;
{$R *.dfm}
// =============================================================================
// スクリーンに表示中のマウスカーソルのカーソルファイルのフルパスを取得
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LStartTime : DWORD;
LFilePath : string ;
begin
Edit1.Text := '' ;
// 指定時間が経過したらキャプチャ
LStartTime := timeGetTime;
while (timeGetTime - LStartTime) < 5000 do begin
Sleep(10 );
Application.ProcessMessages;
end ;
LFilePath := GetCursorFilePath;
Edit1.Text := LFilePath;
Edit1.SelStart := Length(Edit1.Text);
end ;
// -----------------------------------------------------------------------------
// 現在スクリーンに表示れているマウスカーソルのハンドルを取得する関数
// -----------------------------------------------------------------------------
function GetShowCursorHandle: HCURSOR;
var
LCursorInfo : TCursorInfo;
begin
LCursorInfo.cbSize := SizeOf(TCursorInfo);
LCursorInfo.flags := CURSOR_SHOWING;
GetCursorInfo(LCursorInfo);
Result := LCursorInfo.hCursor;
end ;
// -----------------------------------------------------------------------------
// マウスカーソルのファイルのフルパスを取得する関数
//
// (1) マウスカーソルのハンドルを取得
// (2) そのハンドルに相当する TCursor 型の値を調べる
// (3) TCuror 型に該当する Windows 10 のマウスカーソル名を調べる
// (4) そのマウスカーソル名のフルパスを取得
// -----------------------------------------------------------------------------
function GetCursorFilePath: string ;
var
LhCursor : HCURSOR;
LCursorID : TCursor;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := '' ;
LhCursor := GetShowCursorHandle;
// カーソルのハンドルから TCursor の値を取得
LCursorID := 0 ;
for LIndex := -1 downto -22 do begin
if LhCursor = Screen.Cursors[LIndex] then begin
LCursorID := TCursor(LIndex);
Break;
end ;
end ;
// TCursor の値に相当するカーソルファイルがあるか調べる
if LCursorID <> 0 then begin
Result := GetMouseCursorFilePath(LCursorID);
end ;
if LhCursor <> 0 then DestroyCursor(LhCursor);
end ;
// -----------------------------------------------------------------------------
// 環境変数を含む文字列から実際のフルパスを取得する関数
// -----------------------------------------------------------------------------
function ExpandEnvironmentString(S: string ): string ;
var
LDstChar : array [0 ..MAX_PATH-1 ] of Char;
begin
ExpandEnvironmentStrings(PChar(S), LDstChar, MAX_PATH);
Result := LDstChar;
end ;
// -----------------------------------------------------------------------------
// Delphi の TCursor 型の値からからカーソルファイルのフルパスを取得
// Windows にないカーソルの時は空文字を返す
// -----------------------------------------------------------------------------
function GetMouseCursorFilePath(CursorID: TCursor): string ;
var
LRegistry : TRegistry;
LItemName : string ;
LKeyName : string ;
begin
Result := '' ;
case CursorID of
crArrow : LItemName := 'Arrow' ;
crCross : LItemName := 'Crosshair' ;
crIBeam : LItemName := 'IBeam' ;
crSizeNESW : LItemName := 'SizeNESW' ;
crSizeNS : LItemName := 'SizeNS' ;
crSizeNWSE : LItemName := 'SizeNWSE' ;
crSizeWE : LItemName := 'SizeWE' ;
crUpArrow : LItemName := 'UpArrow' ;
crHourGlass : LItemName := 'Wait' ;
crDrag : LItemName := '' ;
crNoDrop : LItemName := '' ;
crHSplit : LItemName := '' ;
crVSplit : LItemName := '' ;
crMultiDrag : LItemName := '' ;
crSQLWait : LitemName := '' ;
crNo : LItemName := 'No' ;
crAppStart : LItemName := 'Appstarting' ;
crHelp : LItemName := 'Help' ;
crHandPoint : LItemName := 'Hand' ;
crSizeAll : LItemName := 'SizeAll' ;
else
LItemName := '' ;
end ;
if LItemName = '' then Exit;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyName :='Control Panel\Cursors' ;
if LRegistry.OpenKey(LKeyName, False) then begin
Result := LRegistry.ReadString(LItemName);
// 環境変数が含まれている場合があるので実パスに変換
Result := ExpandEnvironmentString(Result);
LRegistry.CloseKey;
end ;
finally
FreeAndNil(LRegistry);
end ;
end ;
end .
07_ マウスカーソルァイルを読み込んでビットマップ画像を取得
カーソルファイルのパス名が分かれば,Windows API の LoadImage 関数で読み込んで DrawIconEx 関数でビットマップとして取得できます.この時,TIcon.LoadFromFile は使用でできません.何故ならば,Windows 10 のマウスカーソルファイルは,既に確認したようにマルチカーソルファイル,つまり,マルチアイコンファイルです.Delphi の TIcon はマルチアイコンを扱えません.
図20
マウスのカーソルファイルを読み込んで表示
crHourGlass (Windows 10 では Wait)
リスト7
カーソルファイルを LoadImage 関数で読み込んで,DrawIconEx 関数でビットマップを取得
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Image1: TImage;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
function GetMouseCursorSize: Integer;
function ExpandEnvironmentString(S: string ): string ;
var
Form1: TForm1;
implementation
uses
System.Win.Registry;
{$R *.dfm}
// =============================================================================
// カーソルファイルを読み込んでマウスカーソ画像を表示
//
// TIcon.LoadFromFile は使用できない
// Windows のマウスカーソルファイルはマルチカーソル (マルチアイコン) ファイル
// TIcon はマルチアイコンに対応していない
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LImgSize : Integer;
LDeffSize : Integer;
LImagPath : string ;
LBitmap : TBitmap;
LhCursor : HCURSOR;
begin
LImgSize := GetMouseCursorSize;
LDeffSize := GetSystemMetrics(SM_CXCURSOR);
LImagPath := '%SYSTEMROOT%\Cursors\aero_busy.ani' ;
LImagPath := ExpandEnvironmentString(LImagPath);
LBitmap := TBitmap.Create;
try
LBitmap.SetSize(LImgSize, LImgSize);
// ファイルからカーソルファイルを読み込んでカーソルのハンドルを取得
if LImgSize = LDeffSize then begin
LhCursor := LoadImage(HInstance, PChar(LImagPath), IMAGE_CURSOR,
0 , 0 , LR_DEFAULTSIZE);
end else begin
LhCursor := LoadImage(HInstance, PChar(LImagPath), IMAGE_CURSOR,
0 , 0 , LR_LOADFROMFILE);
end ;
// マウスカーソル画像を TBitmap の Canvas に描画
DrawIconEx(LBitmap.Canvas.Handle, 0 , 0 , LhCursor,
LImgSize, LImgSize, 0 , 0 , DI_IMAGE);
// Windows 10 のマウスカーソルはアルファチャンネル付きの 32 bit のビットマップ
// DrawIconEx の後で設定する
LBitmap.PixelFormat := pf32bit;
LBitmap.AlphaFormat := afDefined;
// TImage に TBitmap を描画
Image1.Canvas.Draw(0 , 0 , LBitmap);
finally
FreeAndNil(LBitmap);
end ;
end ;
// -----------------------------------------------------------------------------
// 現在のマウスカーソルのサイズを取得する関数
// -----------------------------------------------------------------------------
function GetMouseCursorSize: Integer;
var
LRegistry : TRegistry;
LKeyName : string ;
begin
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyName :='Control Panel\Cursors' ;
LRegistry.OpenKey(LKeyName, False);
Result := LRegistry.ReadInteger('CursorBaseSize' );
LRegistry.CloseKey;
finally
FreeAndNil(LRegistry);
end ;
end ;
// -----------------------------------------------------------------------------
// 環境変数を含む文字列から実際のフルパスを取得する関数
// -----------------------------------------------------------------------------
function ExpandEnvironmentString(S: string ): string ;
var
LDstChar : array [0 ..MAX_PATH-1 ] of Char;
begin
ExpandEnvironmentStrings(PChar(S), LDstChar, MAX_PATH);
Result := LDstChar;
end ;
end .
08_ マウスカーソル画像取得用ユニットを作成してテスト
ここまでのテストの結果を元にして,マウスカーソル画像取得用のユニットを作成しました.そのテストプログラムです.ユニット名は plMouseCursorImage です.plMouseCursorImage,pas をライブラリのパスが通った場所 (フォルダ内) に置いて,uses に plMouseCursorImage を追加して使用します.以下の関数を使用してマウスカーソルの画像を取得します.
// マウスカーソル画像のサイズを取得
// 戻り値が画像のサイズ
function GetMouseCursorSize: Integer;
// マウスカールの画像を 32 bit のアルファチャンネル付きのビットマップとして取得
// 戻り値はホットスポットの値
function GetMouseCursorBitmap(ABitmap: TBitmap;
ADropShadow: Boolean = False): TPoint; overload ;
function GetMouseCursorBitmap(CursorID: TCursor; ABitmap: TBitmap;
ADropShadow: Boolean = False): TPoint; overload ;
ボタンをクリックすると,スクリーンに表示中のマウスカーソルの画像を取得して TImage に描画します.右上の TPanel に画像のサイズとホットスポットの値を表示します.TListBox でマウスカーソル名を選択すると,そのマウスカーソルの画像を TImage に描画します.
ホットスポットの値は全て比率計算で求めています.
下図は Windows 10 のマウスカーソルの例です.
図21
Windows 10 のマウスカーソル
Delphi 特有のマウスカーソルの例です.このマウスカーソルは Delphi の EXE のリソースにあり,1 ビットのモノクロ画像です.
今回作成したマウスカーソル画像取得用ユニットでは,24 ビット以下の色数で作成されたマウスカーソル画像も取得できるようにしています.
図22
Delphi 専用のマウスカーソル
図23
エクセルが使用しているマウスカーソル
図24
自作のマウスカーソル
リスト8
マウスカーソル画像取得用ユニットのテストプログラム
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
ListBox1: TListBox;
Panel1: TPanel;
Image1: TImage;
procedure Button1Click(Sender: TObject);
procedure ListBox1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private 宣言 }
procedure DrawCursorImage(ACursor: TCursor);
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
plMouseCursorImage,
Winapi.MMSystem;
{$R *.dfm}
// =============================================================================
// マウスカーソル画像取得用ユニット plMouseCursorImage のテストプログラム
//
// フォーム生成時の処理
// Delphi の TCursor 型の名前のリストを作成して TListBox に表示
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LName : string ;
LIndex : Integer;
begin
ListBox1.Items.Clear;
ListBox1.Items.BeginUpdate;
for LIndex := -1 downto -22 do begin
LName := CursorToString(TCursor(LIndex));
if StrToIntDef(LName, 0 ) = 0 then begin
ListBox1.Items.Add(LName);
end ;
end ;
ListBox1.Items.EndUpdate;
end ;
// =============================================================================
// 指定時間経過したら,スクリーン上のマウスカーソル画像をキャプチャ
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LStartTime : DWORD;
begin
// 指定時間が経過したらキャプチャ開始
LStartTime := timeGetTime;
while (timeGetTime - LStartTime) < 5000 do begin
Sleep(10 );
Application.ProcessMessages;
end ;
ListBox1.ItemIndex := -1 ;
DrawCursorImage(0 );
end ;
// =============================================================================
// TListBox でカーソル名を選択したら,そのマウスカーソル画像を TImage に描画
// =============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
Lindex : Integer;
LCursor : TCursor;
begin
Lindex := ListBox1.ItemIndex;
if Lindex < 0 then Exit;
LCursor := 0 ;
for LIndex := -1 downto -22 do begin
if LIndex = StringToCursor(ListBox1.Items[ListBox1.ItemIndex]) then begin
LCursor := Lindex;
Break;
end ;
end ;
DrawCursorImage(LCursor);
end ;
// -----------------------------------------------------------------------------
// 引数の TCursor 型に該当するマウスカーソル画像を TImage に描画するメソッド
// ACursor が 0 の時は現在スクリーンに表示中のマウスカーソル画像
// -----------------------------------------------------------------------------
procedure TForm1.DrawCursorImage(ACursor: TCursor);
var
LBitmap : TBitmap;
LImgSize : Integer;
LHotspot : TPoint;
LText : string ;
begin
Image1.Picture.Assign(nil );
LBitmap := TBitmap.Create;
try
// マウスカーソル画像のサイズを取得
LImgSize := GetMouseCursorSize;
if LImgSize = 0 then Exit;
// マウスカーソルの画像をビットマップ画像として取得
LHotspot := GetMouseCursorBitmap(ACursor, LBitmap, True);
// 透過状態確認のために背景を塗り潰してからビットマップを描画
Image1.Canvas.Brush.Color := clScrollBar;
Image1.Canvas.FillRect(LBitmap.Canvas.ClipRect);
Image1.Canvas.Draw(0 , 0 , LBitmap);
// 画像のサイズとホットスポットの値を TPanel に表示
LText := 'Size :' + IntToStr(LImgSize) + ' '
+ 'xHotspot:' + IntToStr(LHotspot.X) + ' '
+ 'yHotspot:' + IntToStr(LHotspot.Y);
Panel1.Caption := LText;
finally
FreeAndNil(LBitmap);
end ;
end ;
end .
上のサンプルで取得したビットマップは,32 ビットのアルファチャンネル付きのビットマップです.保存して,それを読みだして
TImage に表示する時は,TBitmap.AlphaFormat プロパティの値を afDefined にします.この値にしないと,下図の左の画像のように背景が黒になります.
// =============================================================================
// アルファチャンネルを使用している TBitmap を TImaage に描画
// TBitmap.AlphaFormat の値を afDefined にする
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LImgFilePath : string ;
LBitmap : TBitmap;
begin
LImgFilePath := ExtractFilePath(Application.ExeName) + 'crArrow.bmp' ;
LBitmap := TBitmap.Create;
try
LBitmap.LoadFromFile(LImgFilePath);
Image1.Canvas.Draw(0 , 0 , LBitmap);
LBitmap.AlphaFormat := afDefined;
Image2.Canvas.Draw(0 , 0 , LBitmap);
finally
FreeAndNil(LBitmap);
end ;
end ;
図25
保存したビットマップを TImage に描画
09_ アルファ値を持つ 32 bit のビットマップを PNG 画像に変換
マウスカーソル画像取得用ユニット plMouseCursorImage が出力する画像は全て 32 ビットのアルファチャンネル付きのビットマップです.32 ビットのアルファチャンネル付きのビットマップを扱えるアプリは少ないのが現状です.PNG 画像に変換しておくと便利かも知れません.そこで,PNG 画像に変換する関数を実装しておきました.その使用例です.
添付の 32 ビットのアルファチャンネル付きのビットマップ画像を読み込んで,PNG 画像に変換して保存するたけのサンプルです.
図26
PNG 画像への変換例
左から順番に,
元のビットマップ画像
変換した PNG 画像
透過状態の確認表示
リスト9
アルファ値を持つ 32 bit のビットマップを PNG 画像に変換
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
Image1: TImage;
Image2: TImage;
Image3: TImage;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
plMouseCursorImage,
Vcl.Imaging.pngimage;
{$R *.dfm}
// =============================================================================
// アルファチャンネル付きの TBitmap 画像を PNG 形式の画像に変換して保存
//
// uses に Vcl.Imaging.pngimag が必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LBitmap : TBitmap;
LPngImage : TPngImage;
LbmpFilePath : string ;
LSavePath : string ;
begin
LbmpFilePath := ExtractFilePath(Application.ExeName) + 'crHourGlass.bmp' ;
Image1.Picture.Assign(nil );
Image2.Picture.Assign(nil );
Image3.Picture.Assign(nil );
LBitmap := TBitmap.Create;
try
LBitmap.LoadFromFile(LbmpFilePath);
// ここで以下にしないこと
// afDefinedにしてしまうと PNG 画像の色がおかしくなる
// LBitmap.AlphaFormat := afDefined;
// TPngImage 生成
LPngImage := TPngImage.CreateBlank(COLOR_RGBALPHA, 8 ,
LBitmap.Width,LBitmap.Height);
try
// TBitmap を TPngImage に変換
ConvertBitmapToPngimage(LBitmap, LPngImage);
// 保存
LSavePath := ChangeFileExt(LbmpFilePath, '.png' );
LPngImage.SaveToFile(LSavePath);
// TBitmap をアルファチャンネル付きで表示する場合
// TPngImage に変換してから afDefined にして描画する
LBitmap.AlphaFormat := afDefined;
Image1.Canvas.Draw(0 , 0 , LBitmap);
// 保存した PNG 画像を読み込んで TImage に描画
LPngImage.LoadFromFile(LSavePath);
Image2.Canvas.Draw(0 , 0 , LPngImage);
// 透過状態を確認のために背景を塗り潰してから PNG 画像を描画
Image3.Canvas.Brush.Color := clSilver;
Image3.Canvas.FillRect(LPngImage.Canvas.ClipRect);
Image3.Canvas.Draw(0 , 0 , LPngImage);
finally
FreeAndNil(LPngImage);
end ;
finally
FreeAndNil(LBitmap);
end ;
end ;
end .
マウスカーソル画像取得用ユニット plMouseCursorImage に,実際にマウスカーソルの画像がある矩形領域を取得する関数を実装しています.そのテストプログラムです.
関数名は GetRealCursorImageSize で,戻り値は矩形領域のサイズです.
図27
実際に画像がある領域
リスト10
マウスカーソル画像の実画像領域の切り取り
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
ListBox1: TListBox;
Panel1: TPanel;
Image1: TImage;
procedure Button1Click(Sender: TObject);
procedure ListBox1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private 宣言 }
procedure DrawCursorImage(ACursor: TCursor);
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
plMouseCursorImage,
Winapi.MMSystem;
{$R *.dfm}
// =============================================================================
// マウスカーソル画像の実画像の矩形領域を取得
//
// フォーム生成時の処理
// TListBox に Delphi の TCursor の値の一覧を表示
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LName : string ;
LIndex : Integer;
begin
ListBox1.Items.Clear;
ListBox1.Items.BeginUpdate;
for LIndex := -1 downto -22 do begin
LName := CursorToString(TCursor(LIndex));
if StrToIntDef(LName, 0 ) = 0 then begin
ListBox1.Items.Add(LName);
end ;
end ;
ListBox1.Items.EndUpdate;
end ;
// =============================================================================
// 指定時間が経過したらスクリーンに表示中のカーソル画像をキャプチャ
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LStartTime : DWORD;
begin
// 指定時間が経過したらキャプチャ開始
LStartTime := timeGetTime;
while (timeGetTime - LStartTime) < 5000 do begin
Sleep(10 );
Application.ProcessMessages;
end ;
DrawCursorImage(0 )
end ;
// =============================================================================
// TListBox のカーソル名をクリックしたら,そのマウスカーソルの画像を取得
// =============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
LItemIndex : Integer;
LCursor : TCursor;
LIndex : Integer;
begin
Panel1.Caption := '' ;
LItemIndex := ListBox1.ItemIndex;
if LItemIndex < 0 then Exit;
Panel1.Caption := '' ;
// TListBox で選択した名前から TCursor の値を求める
LCursor := 0 ;
for LIndex := -1 downto -22 do begin
if LIndex = StringToCursor(ListBox1.Items[LItemIndex]) then begin
LCursor := LIndex;
Break;
end ;
end ;
DrawCursorImage(LCursor);
end ;
// -----------------------------------------------------------------------------
// 引数の TCursor 型に該当するマウスカーソル画像を TImage に描画するメソッド
// ACursor が 0 の時は現在スクリーンに表示中のマウスカーソル画像
// -----------------------------------------------------------------------------
procedure TForm1.DrawCursorImage(ACursor: TCursor);
var
LBitmap : TBitmap;
LSizeBitmap : TBitmap;
LImgSize : TSize;
LText : string ;
begin
Image1.Picture.Assign(nil );
LBitmap := TBitmap.Create;
LSizeBitmap := TBitmap.Create;
try
// マウスカーソルの画像をビットマップ画像として取得
GetMouseCursorBitmap(ACursor, LBitmap);
// 実際にカーソル画像が描画されている矩形領域だけをビットマップとして取得
LImgSize := GetRealCursorImageSize(LBitmap, LSizeBitmap);
// 透過状態確認のために背景を塗り潰してからビットマップを描画
Image1.Width := LImgSize.Width;
Image1.Height := LImgSize.Height;
Image1.Canvas.Brush.Color := clSilver;
Image1.Canvas.FillRect(Image1.Canvas.ClipRect);
Image1.Canvas.Draw(0 , 0 , LSizeBitmap);
// 取得したサイズを表示
LText := 'width:' + IntToStr(LImgSize.cx) + ' '
+ 'height:' + IntToStr(LImgSize.cy);
Panel1.Caption := LText;
finally
FreeAndNil(LBitmap);
FreeAndNil(LSizeBitmap);
end ;
end ;
end .
GetRealCursorImageSize 関数の使用例です.
TControl.Hint プロパティにテキストを代入して,TControl.ShowHint プロパティを True にすると,マウスカーソルがそのコントロールに移動すると,下図のようにヒントウィンドウが表示されます.マウスカーソルのサイズが大きくなると,ヒントのテキストの一部がマウスカーソルで隠れるようになります.
図28
ヒントウィンドウ
図29
マウスポインタのサイズを大きくした状態
以下のサンプルでは,ヒントウィンドウの表示位置をマウスカーソルのサイズに合わせて変更しています.その結果,ヒントのテキストがマウスカーソルの陰に隠れなくなります.
図30
ヒントウィンドウの表示位置を変更
リスト11
マウスカーソルのサイズに合わせてヒントの表示位置を変更
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TMyHint = class (THintWindow)
public
procedure ActivateHint(Rect: TRect; const AHint: string ); override ;
end ;
TForm1 = class (TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
FHint : TMyHint;
end ;
var
Form1: TForm1;
implementation
uses
plMouseCursorImage;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
// ヒントの表示に使用するクラスを指定して,そのインスタンスを生成
HintWindowClass := TMyHint;
FHint := TMyHint.Create(Self);
Button1.Hint := 'ヒント' + sLineBreak + 'ヒントの表示位置変更' ;
Button1.ShowHint := True;
end ;
// =============================================================================
// THintWindow の継承クラスの ActivateHint メソッド
// ヒントウィンドウの表示位置を変更
// =============================================================================
procedure TMyHint.ActivateHint(Rect: TRect; const AHint: string );
var
LPoint : TPoint;
LSize : TSize;
begin
inherited ;
GetCursorPos(LPoint);
LSize := GetRealCursorImageSize(crArrow);
Self.Top := LPoint.Y + LSize.Height - 3 ;
end ;
end .
11_ マウスカーソルを含めたアクティブウィンドウのキャプチャ
実際にマウスカーソルを含めてアクティブなウィンドウをキャプチャしてみます.そうすれば,比例計算で求めたホットスポットの精度が確認できます.
EXE を起動して Button1 をクリックすると,5 秒後にアクティブなウィンドウをマウスカーソルを含めてキャプチャします.下図は,右上のウィンドウをキャプチャした結果です.右上のウィンドウはテスト用で,このウィンドウ,つりフォームの TListBox にリストされているマウスカーソル名をクリックすると,Screen.Cursor をそのマウスカーソルにします.
この図ではホットスポットの精度は確認できません.この画像は,今回作成したユニットを使用してキャプチャしています.
図31
アクティブウィンドウをキャプチャ
リスト12
アクティブウィンドウをマウスカーソルを含めてキャプチャ
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
PaintBox1: TPaintBox;
procedure Button1Click(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
FWndBitmap : TBitmap;
end ;
var
Form1: TForm1;
implementation
uses
Vcl.Clipbrd,
Winapi.Dwmapi,
plMouseCursorImage,
Winapi.MMSystem;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
FWndBitmap := TBitmap.Create;
end ;
// =============================================================================
// フォーム破棄時の処理
// =============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FWndBitmap);
end ;
// -----------------------------------------------------------------------------
// ウィンドウのスクリーン上の位置情報を取得
// Windows Vista 以降の拡張フレーム対応コード
// uses に Winapi.Dwmapi が必要
// -----------------------------------------------------------------------------
function GetWindowRectForVista(hWnd: HWND): TRect;
var
LRect : TRect;
begin
DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, @LRect, SizeOf(TRect));
Result := LRect;
end ;
// -----------------------------------------------------------------------------
// アクティブなウィンドウをキャプチャして画像をクリップボードにコピー
// [Alt] + [Print Screen] のキー操作をエミュレート
// -----------------------------------------------------------------------------
procedure CopyImageActiveWindow;
var
LKeyFlag : Cardinal;
begin
// [Alt] + [Print Screen] のキー操作
LKeyFlag := KEYEVENTF_EXTENDEDKEY;
keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0 ), LKeyFlag, 0 );
keybd_event(VK_SNAPSHOT, MapVirtualKey(VK_SNAPSHOT, 0 ), LKeyFlag, 0 );
LKeyFlag := KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP;
keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0 ), LKeyFlag, 0 );
keybd_event(VK_SNAPSHOT, MapVirtualKey(VK_SNAPSHOT, 0 ), LKeyFlag, 0 );
Application.ProcessMessages;
Sleep(100 );
end ;
// =============================================================================
// マウスカーソを含めたアクティブウィンドウのキャプチャ
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LtartTime : Cardinal;
LTargetWnd : HWND;
LCursorBmp : TBitmap;
LWndRect : TRect;
LCursorPos : TPoint;
LHotspot : TPoint;
begin
// テスト用に用意したウィンドウを最前面に表示
LTargetWnd := FindWindow('TCaptureWindow' , nil );
SetForegroundWindow(LTargetWnd);
// 指定時間が経過したらキャプチャ
LtartTime := timeGetTime;
while (timeGetTime - LtartTime) < 5000 do begin
Sleep(10 );
Application.ProcessMessages;
end ;
// アクティブなウィンドウの位置とマウスカーソルの位置を取得
LTargetWnd := GetForegroundWindow;
LWndRect := GetWindowRectForVista(LTargetWnd);
GetCursorPos(LCursorPos);
// アクティブなウィンドウをキャプチャ
CopyImageActiveWindow;
LCursorBmp := TBitmap.Create;
try
// キャプチャしたウィンドウの画像を取得
FWndBitmap.Assign(Clipboard);
// マウスカーソルの画像をキャプチャ
LHotspot := GetMouseCursorBitmap(LCursorBmp, True);
// マウスカーソルの画像を描画する位置を計算
LCursorPos.X := LCursorPos.X - LWndRect.Left - LHotspot.X;
LCursorPos.Y := LCursorPos.Y - LWndRect.Top - LHotspot.Y;
// ウィンドウの画像にカーソル画像を重ね描画.それを TPaintBox に描画
FWndBitmap.Canvas.Draw(LCursorPos.X, LCursorPos.Y, LCursorBmp);
PaintBox1.Invalidate;
finally
FreeAndNil(LCursorBmp);
end ;
SetForegroundWindow(Handle);
end ;
// =============================================================================
// PaintBox1 の OnPaint イベント
// キャプチャした画像を描画
// =============================================================================
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
PaintBox1.Canvas.Brush.Color := clSilver;
PaintBox1.Canvas.FillRect(PaintBox1.Canvas.ClipRect);
if FWndBitmap <> nil then begin
PaintBox1.Canvas.Draw(0 , 0 , FWndBitmap);
end ;
end ;
end .
テスト用のウィンドウ (フォーム) のコードです.
// =============================================================================
// フォーム生成時の処理
// Delphi の TCursor 型の値を列挙
// =============================================================================
procedure TCaptureWindow.FormCreate(Sender: TObject);
var
LName : string ;
LIndex : Integer;
begin
ListBox1.Items.Clear;
ListBox1.Items.BeginUpdate;
for LIndex := -1 downto -22 do begin
LName := CursorToString(TCursor(LIndex));
if StrToIntDef(LName, 0 ) = 0 then begin
ListBox1.Items.Add(LName);
end ;
end ;
ListBox1.Items.EndUpdate;
end ;
// =============================================================================
// TListBox のカーソル名を選択したらそのカーソルを Sreen.Cursor にする
// =============================================================================
procedure TCaptureWindow.ListBox1Click(Sender: TObject);
var
LIndex : Integer;
LCursor : TCursor;
begin
Lindex := ListBox1.ItemIndex;
if Lindex < 0 then Exit;
for LIndex := -1 downto -22 do begin
if LIndex = StringToCursor(ListBox1.Items[ListBox1.ItemIndex]) then begin
LCursor := Lindex;
Break;
end ;
end ;
Screen.Cursor := LCursor;
end ;
// =============================================================================
// TListBox にカーソルが移動したら Screen.Cursor をデフォルトにする
// =============================================================================
procedure TCaptureWindow.ListBox1MouseEnter(Sender: TObject);
begin
Screen.Cursor := crDefault;
end ;
12_ マウスカーソル画像取得用ユニットのソースコード
マウスカーソル画像取得用ユニット plMouseCursorImage; のソースコードです.このコードのファイルをライブラリのパスが通った場所 (フォルダ内) に置いて,uses に plMouseCursorImage を追加して使用します.
このユニットのコードは,Windows 10 よりも前の Windows では正常に動作しません.
ホットスポットの値は全て卑劣計算の結果です.[マウスのプロパティ] でサイズやデザインを変更すると,たとえカーソルファイルから読み込んでも正しいホットスポットの値は計算できせん.
Aero と呼ばれるアルファチャンネルを使用しているマウスカーソルは,ファイルから読み込んで取得していますが,実際にはマウスカーソルのハンドルから DrawIconEx で取得できます.DrawIconEx でサイズを指定して画像を取得すると,そのサイズの画像を取得します.該当サイズがない場合は拡大または取得賞した画像を取得します.
このユニットを使用しても,全てのマウスカーソルのキャプチャが可能となるわけではありません.デザインを変更したりしている場合は,全く取得できない,あるいは正常に取得できない場合もあります.
リスト13
マウスカーソル画像取得 (マウスキャプチャ) 用ユニットのソースコード
copy code
unit plMouseCursorImage;
interface
uses
Winapi.Windows, System.SysUtils, Vcl.Graphics, Vcl.Controls, Vcl.Forms, System.Classes,
Vcl.Imaging.pngimage, Vcl.Dialogs;
function GetMouseCursorBitmap(ABitmap: TBitmap;
ADropShadow: Boolean = False): TPoint; overload ;
function GetMouseCursorBitmap(CursorID: TCursor; ABitmap: TBitmap;
ADropShadow: Boolean = False): TPoint; overload ;
function GetMouseCursorSize: Integer;
function GetMouseCursorFilePath(CursorID: TCursor): string ; overload ;
function GetRealCursorImageSize(ABitmap: TBitmap; ADstBitmap:
TBitmap = nil ): TSize; overload ;
function GetRealCursorImageSize(ACursor: TCursor;
ADstBitmap: TBitmap = nil ): TSize; overload ;
procedure CreateMaskBitmapOpacity(ABitmap: TBitmap; Opacity: Byte);
procedure GaussianBlur(ASrcBmp: TBitmap; ARadius, ARepeat: Integer);
function ConvertBitmapToPngimage(SrcBitmap: TBitmap;
DstPngImage: TPngImage): Boolean;
var
FMouseCursorFilePath : string ;
implementation
uses
System.Win.Registry,
System.StrUtils,
System.Types;
type
TRGBQuadArray = array [0 ..MaxInt div SizeOf(TRGBQuad) - 1 ] of TRGBQuad;
PRGBQuadArray = ^TRGBQuadArray;
TRGBTArray = array [0 ..MaxInt div SizeOf(TRGBTRIPLE) - 11 ] of RGBTRIPLE;
PRGBTripleArray = ^TRGBTArray;
// -----------------------------------------------------------------------------
// マスク画像から元の画像を生成する処理
// 24 bit 以下で作成されたマウスカーソル画像で必要な処理
//
// ASrcBitmap : DrawIconEx でフラグを DI_NORMAL にして取得したビットマップ
// AImgBitmap : DrawIconEx でフラグを DI_IMAGE にして取得したビットマップ
// LImgBitmap.AlphaFormat := afDefined にしておく
// これが処理後のビットマップとなる
// AMaskBitmap : DrawIconEx でフラグを DI_MASK にして取得したビットマップ
//
// 24 bit 以下の画像を PixelFormat で 32 bit にするとアルファ値は 0 になる
// 元々アルファを使用している場合はそのアルファ値が取得できる
// この手続きの処理はこのことを利用して 32 bit と 24 bit 以下の判定をしている
// -----------------------------------------------------------------------------
procedure MakeCursorBmpByMaskBmp(ASrcBitmap, AImgBitmap, AMaskBitmap: TBitmap);
var
LRedSrc : Byte;
LGreenSrc : Byte;
LBlueSrc : Byte;
LAlphaSrc : Byte;
LRedMask : Byte;
LGreenMask : Byte;
LBlueMask : Byte;
LYPos : Integer;
LXPos : Integer;
LScanArraySrc : PRGBQuadArray;
LScanArrayImg : PRGBQuadArray;
LScanArrayMask : PRGBQuadArray;
begin
ASrcBitmap.Canvas.Lock;
AImgBitmap.Canvas.Lock;
AMaskBitmap.Canvas.Lock;
try
for LYPos := 0 to AMaskBitmap.Height - 1 do begin
LScanArraySrc := ASrcBitmap.ScanLine[LYPos];
LScanArrayImg := AImgBitmap.ScanLine[LYPos];
LScanArrayMask := AMaskBitmap.ScanLine[LYPos];
for LXPos := 0 to AMaskBitmap.Width - 1 do begin
// 退避
LRedSrc := LScanArraySrc[LXPos].rgbRed;
LGreenSrc := LScanArraySrc[LXPos].rgbGreen;
LBlueSrc := LScanArraySrc[LXPos].rgbBlue;
LAlphaSrc := LScanArraySrc[LXPos].rgbReserved;
// マスク画像のピクセルの色.黒か白
LRedMask := LScanArrayMask[LXPos].rgbRed;
LGreenMask := LScanArrayMask[LXPos].rgbGreen;
LBlueMask := LScanArrayMask[LXPos].rgbBlue;
if (LRedMask = 0 ) and (LGreenMask = 0 ) and (LBlueMask = 0 ) then begin
if LAlphaSrc = 0 then begin
// アルファチャンネルを使用していないカーソル画像の場合
// マスク部分のピクセルに色データをセット
// アルファチャンネルを使用するビットマップにしてしまう
LScanArrayImg[LXPos].rgbRed := LRedSrc;
LScanArrayImg[LXPos].rgbGreen := LGreenSrc;
LScanArrayImg[LXPos].rgbBlue := LBlueSrc;
LScanArrayImg[LXPos].rgbReserved := $FF ;
end else begin
// アルファ値がある場合
// AImgBitmapGE は目的のカーソル画像なので処理は不要
Exit;
end ;
end ;
end ;
end ;
finally
ASrcBitmap.Canvas.Unlock;
AImgBitmap.Canvas.Unlock;
AMaskBitmap.Canvas.Unlock;
end ;
end ;
// //-----------------------------------------------------------------------------
// マスク画像が有効かをチェック
// Windows 10 の Aero でないカーソルでマスク画像を省略しているカーソルがある
// 現在確認しているのは Crosshair, IBeam
// これらはマスク画像がなくても背景を透過表示することが可能
// ゴミが入っている場合があるので背景色でないピクセル数を取得して確認
// (歴史的産物か ?)
// -----------------------------------------------------------------------------
function CheckMaskBmp(AMaskBitmap: TBitmap): Integer;
var
LColor : TColor;
LRTrans : Byte;
LGTrans : Byte;
LBTrans : Byte;
LRMask : Byte;
LGMask : Byte;
LBMask : Byte;
LYPos : Integer;
LXPos : Integer;
LScanArrayMask : PRGBQuadArray;
begin
Result := 0 ;
AMaskBitmap.Canvas.Lock;
LColor := AMaskBitmap.Canvas.Pixels[0 , AMaskBitmap.Height - 1 ];
LRTrans := GetRValue(LColor);
LGTrans := GetGValue(LColor);
LBTrans := GetBValue(LColor);
try
for LYPos := 0 to AMaskBitmap.Height - 1 do begin
LScanArrayMask := AMaskBitmap.ScanLine[LYPos];
for LXPos := 0 to AMaskBitmap.Width - 1 do begin
LRMask := LScanArrayMask[LXPos].rgbRed;
LGMask := LScanArrayMask[LXPos].rgbGreen;
LBMask := LScanArrayMask[LXPos].rgbBlue;
if (LRMask = LRTrans) and (LGMask = LGTrans) and (LBMask = LBTrans) then begin
end else begin
Result := Result + 1 ;
end ;
end ;
end ;
finally
AMaskBitmap.Canvas.Unlock;
end ;
end ;
// -----------------------------------------------------------------------------
// 白黒を反転してマスク画像作成
// Aero でないカーソル画像でマスク画像がない画像用
// 確認しているのは Crosshair, IBeam
// この時,DI_IMAGE で取得した画像がマスクの反転画像と同じなのでそれを利用
// -----------------------------------------------------------------------------
function MakeMasBmpForWin10(AMaskBitmap, AImgBitmap: TBitmap): Boolean;
var
LRImg : Byte;
LGImg : Byte;
LBImg : Byte;
LYPos : Integer;
LXPos : Integer;
LScanArrayMask : PRGBQuadArray;
LScanArrayImg : PRGBQuadArray;
begin
Result := True;
AMaskBitmap.Canvas.Lock;
AImgBitmap.Canvas.Lock;
try
for LYPos := 0 to AMaskBitmap.Height - 1 do begin
LScanArrayMask := AMaskBitmap.ScanLine[LYPos];
LScanArrayImg := AImgBitmap.ScanLine[LYPos];
for LXPos := 0 to AMaskBitmap.Width - 1 do begin
LRImg := LScanArrayImg[LXPos].rgbRed;
LGImg := LScanArrayImg[LXPos].rgbGreen;
LBImg := LScanArrayImg[LXPos].rgbBlue;
if (LRImg = 0 ) and (LGImg = 0 ) and (LBImg = 0 ) then begin
LScanArrayMask[LXPos].rgbRed := $FF ;
LScanArrayMask[LXPos].rgbGreen := $FF ;
LScanArrayMask[LXPos].rgbBlue := $FF ;
LScanArrayMask[LXPos].rgbReserved := $FF ;
end else begin
LScanArrayMask[LXPos].rgbRed := 0 ;
LScanArrayMask[LXPos].rgbGreen := 0 ;
LScanArrayMask[LXPos].rgbBlue := 0 ;
LScanArrayMask[LXPos].rgbReserved := $FF ;
end ;
end ;
end ;
finally
AMaskBitmap.Canvas.Unlock;
AImgBitmap.Canvas.Unlock;
end ;
end ;
// -----------------------------------------------------------------------------
// マウスカーソルのビットマップ画像を取得するメインルーチン
//
// AFilePath : マウスカーソルファイルのフルパス名
// 環境変数を含まないこと
// 空文字の時は AhCursor (カーソルハンドル) の値から取得
// AhCursor : マウスカーソルのハンドルの値
// ABitmap : 出力の 32 bit アルファチャンネル付きビットマップ
// ADropShadow : True ならマウスカーソルに影を描画
// Windows では全てのマウスカーソルに影を描画しているわけではない
//
// 戻り値は TPoint 型で,ホットスポット (Hotspot) の値
// ホットスポットの値は全て比率計算の結果
// -----------------------------------------------------------------------------
function GetMouseCursorBitmapImage(AFilePath: string ; AhCursor: HCURSOR;
ABitmap: TBitmap; ADropShadow: Boolean): TPoint;
var
LAeroFlag : Boolean;
LIconSize : integer;
LDeffSize : Integer;
LhCursor : HCURSOR;
LIconInfo : TIconInfo;
LImgBitmap : TBitmap;
LMaskBitmap : TBitmap;
LTransColor : TColor;
LHasShadow : Boolean;
begin
Result := Point(0 , 0 );
FMouseCursorFilePath := '' ;
if (AFilePath = '' ) and (AhCursor = 0 ) then Exit;
LIconSize := GetMouseCursorSize;
LDeffSize := GetSystemMetrics(SM_CXCURSOR);
if LIconSize = 0 then Exit;
LhCursor := 0 ;
LAeroFlag := (AFilePath <> '' ) and (LeftStr(ExtractFileName(AFilePath), 5 ) = 'aero_' );
// Windows にマウスカーソルファイルがあり Aero カーソルの場合
// デフォルトの最小サイズの時の Crosshair; IBeam はファイルなしとなっている
// 実際には次の Aero でないカーソル画像のコードでも取得できる
if LAeroFlag then begin
FMouseCursorFilePath := AFilePath;
ABitmap.SetSize(LIconSize, LIconSize);
if LIconSize = LDeffSize then begin
LhCursor := LoadImage(HInstance, PChar(AFilePath), IMAGE_CURSOR,
0 , 0 , LR_DEFAULTSIZE or LR_LOADFROMFILE);
end else begin
LhCursor := LoadImage(HInstance, PChar(AFilePath), IMAGE_CURSOR,
0 , 0 , LR_LOADFROMFILE);
end ;
ABitmap.SetSize(LIconSize, LIconSize);
DrawIconEx(ABitmap.Canvas.Handle, 0 , 0 , LhCursor,
LIconSize, LIconSize, 0 , 0 , DI_IMAGE);
ABitmap.PixelFormat := pf32bit;
ABitmap.AlphaFormat := afDefined;
end ;
// Windows Aero 以外のカーソル画像 (Delphi 専用とその他のマウスカーソル)
// 引数のカーソルハンドルを使用して画像を取得
// 実際には Aero のカーソル画像もこのコードで取得できる
if not LAeroFlag then begin
LImgBitmap := TBitmap.Create;
LMaskBitmap := TBitmap.Create;
try
ABitmap.SetSize(LIconSize, LIconSize);
LImgBitmap.SetSize(LIconSize, LIconSize);
LMaskBitmap.SetSize(LIconSize, LIconSize);
DrawIconEx(ABitmap.Canvas.Handle, 0 , 0 , AhCursor,
LIconSize, LIconSize, 0 , 0 , DI_NORMAL);
DrawIconEx(LImgBitmap.Canvas.Handle, 0 , 0 , AhCursor,
LIconSize, LIconSize, 0 , 0 , DI_IMAGE);
DrawIconEx(LMaskBitmap.Canvas.Handle, 0 , 0 , AhCursor,
LIconSize, LIconSize, 0 , 0 , DI_MASK);
ABitmap.PixelFormat := pf32bit;
LImgBitmap.PixelFormat := pf32bit;
LMaskBitmap.PixelFormat := pf32bit;
// アルファチャンネル使用のビットマップか調べる
// これで Aero かも判定可能
LTransColor := LMaskBitmap.Canvas.Pixels[0 , LMaskBitmap.Height - 1 ];
if LTransColor = 0 then begin
LImgBitmap.AlphaFormat := afDefined;
ABitmap.Assign(LImgBitmap);
end else begin
// マスク画像がある場合
// マスク画像を省略しているカーソルがあるのでチェック
if CheckMaskBmp(LMaskBitmap) <= 10 then begin
MakeMasBmpForWin10(LMaskBitmap, LImgBitmap);
end ;
// 以下で 32 bit か 24 bit 以下かを判定して処理する
LImgBitmap.AlphaFormat := afDefined;
MakeCursorBmpByMaskBmp(ABitmap, LImgBitmap, LMaskBitmap);
ABitmap.Assign(LImgBitmap);
end ;
finally
FreeAndNil(LImgBitmap);
FreeAndNil(LMaskBitmap);
end ;
end ;
// ホットスポット
// Aero であっても [マウスのプロパティ] でデザインを変更している場合
// ホットスポットの値が正確に取得できない場合があるので全て比率計算とする
GetIconInfo(AhCursor, LIconInfo);
Result.X := Trunc(LIconSize * Integer(LIconInfo.xHotspot) / LDeffSize);
Result.Y := Trunc(LIconSize * Integer(LIconInfo.yHotspot) / LDeffSize);
// マウスカーソルの影の描画
// [マウスのプロパティ] [ポインタ]で[ポインタの影を有効にする] をチェック
// チェックがなければ影の描画は行わない
SystemParametersInfo(SPI_GETCURSORSHADOW, 0 , @LHasShadow, 0 );
if ADropShadow and LHasShadow and (AFilePath <> '' )
and (ExtractFileExt(AFilePath) <> '.ani' ) then begin
LMaskBitmap := TBitmap.Create;
LImgBitmap := TBitmap.Create;
try
LMaskBitmap.PixelFormat := pf32bit;
LMaskBitmap.AlphaFormat := afDefined;
LImgBitmap.PixelFormat := pf32bit;
LImgBitmap.AlphaFormat := afDefined;
LMaskBitmap.Assign(ABitmap);
LImgBitmap.Assign(ABitmap);
CreateMaskBitmapOpacity(LImgBitmap, 0 );
CreateMaskBitmapOpacity(LMaskBitmap, 70 );
GaussianBlur(LMaskBitmap, 1 , 1 );
LImgBitmap.Canvas.Draw(2 , 1 , LMaskBitmap);
LImgBitmap.Canvas.Draw(0 , 0 , ABitmap);
ABitmap.Assign(LImgBitmap);
finally
FreeAndNil(LImgBitmap);
FreeAndNil(LMaskBitmap);
end ;
end ;
if LhCursor <> 0 then DestroyCursor(LhCursor);
if LIconInfo.hbmColor <> 0 then DeleteObject(LIconInfo.hbmColor);
if LIconInfo.hbmMask <> 0 then DeleteObject(LIconInfo.hbmMask);
end ;
// -----------------------------------------------------------------------------
// 現在スクリーンに表示れているマウスカーソルのハンドルを取得する関数
// -----------------------------------------------------------------------------
function GetShowCursorHandle: HCURSOR;
var
LCursorInfo : TCursorInfo;
begin
LCursorInfo.cbSize := SizeOf(TCursorInfo);
LCursorInfo.flags := CURSOR_SHOWING;
GetCursorInfo(LCursorInfo);
Result := LCursorInfo.hCursor;
end ;
// =============================================================================
// マウスカールをビットマップ画像として取得
//
// ABitmap : 出力の 32 bit アルファチャンネル付きビットマップ
// ADropShadow : True にするとカーソルに影を描画
//
// 戻り値は TPoint 型で,ホットスポット (Hotspot) の値
// =============================================================================
function GetMouseCursorBitmap(ABitmap: TBitmap;
ADropShadow: Boolean = False): TPoint;
var
LhCursor : HCURSOR;
LCursorID : TCursor;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := '' ;
LhCursor := GetShowCursorHandle;
// カーソルのハンドルから TCursor の値を取得
LCursorID := 0 ;
for LIndex := -1 downto -22 do begin
if LhCursor = Screen.Cursors[LIndex] then begin
LCursorID := TCursor(LIndex);
Break;
end ;
end ;
// TCursor の値に相当するカーソルファイルがあるか調べる
if LCursorID <> 0 then begin
LFilePath := GetMouseCursorFilePath(LCursorID);
end ;
Result := GetMouseCursorBitmapImage(LFilePath, LhCursor, ABitmap, ADropShadow);
if LhCursor <> 0 then DestroyCursor(LhCursor);
end ;
// =============================================================================
// マウスカールをビットマップ画像として取得
// Delphi の TCursor 型を指定する場合
// 主にテスト用 (Delphi の自アプリ内であれば TCursor は Screen.Cursor の値)
//
// CursorID : Delphi の TCursor 型の値 (crArrow 等)
// ABitmap : 出力の 32 bit アルファチャンネル付きビットマップ
// ADropShadow : True にするとカーソルに影を描画
//
// 戻り値は TPoint 型で,ホットスポット (Hotspot) の値
// =============================================================================
function GetMouseCursorBitmap(CursorID: TCursor; ABitmap: TBitmap;
ADropShadow: Boolean = False): TPoint;
var
LhCursor : HCURSOR;
LFilePath : string ;
LIndex : Integer;
begin
if CursorID = 0 then begin
Result := GetMouseCursorBitmap(ABitmap, ADropShadow);
Exit;
end ;
// 引数と同じ TCursor の値があるか調べてスクリーンカーソルのハンドルを取得
LhCursor := 0 ;
for LIndex := -1 downto -22 do begin
if CursorID = TCursor(LIndex) then begin
LhCursor := Screen.Cursors[LIndex];
Break;
end ;
end ;
if LhCursor = 0 then Exit;
// TCursor の値に相当するカーソルファイルがあるか調べる
if LhCursor <> 0 then begin
LFilePath := GetMouseCursorFilePath(CursorID);
end ;
Result := GetMouseCursorBitmapImage(LFilePath, LhCursor, ABitmap, ADropShadow);
if LhCursor <> 0 then DestroyCursor(LhCursor);
end ;
// =============================================================================
// 現在のマウスカーソルのサイズを取得
// =============================================================================
function GetMouseCursorSize: Integer;
var
LRegistry : TRegistry;
LKeyName : string ;
begin
Result := 0 ;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyName :='Control Panel\Cursors' ;
if LRegistry.OpenKey(LKeyName, False) then begin
Result := LRegistry.ReadInteger('CursorBaseSize' );
LRegistry.CloseKey;
end ;
finally
FreeAndNil(LRegistry);
end ;
end ;
// -----------------------------------------------------------------------------
// 環境変数を含む文字列から実際のフルパスを取得する関数
// -----------------------------------------------------------------------------
function ExpandEnvironmentString(S: string ): string ;
var
LDstChar : array [0 ..MAX_PATH-1 ] of Char;
begin
ExpandEnvironmentStrings(PChar(S), LDstChar, MAX_PATH);
Result := LDstChar;
end ;
// =============================================================================
// Delphi の TCursor 型の値からからカーソルファイルのフルパスを取得
// Windows にないカーソルの時は空文字を返す
// =============================================================================
function GetMouseCursorFilePath(CursorID: TCursor): string ;
var
LRegistry : TRegistry;
LItemName : string ;
LKeyName : string ;
begin
Result := '' ;
case CursorID of
crArrow : LItemName := 'Arrow' ;
crCross : LItemName := 'Crosshair' ;
crIBeam : LItemName := 'IBeam' ;
crSizeNESW : LItemName := 'SizeNESW' ;
crSizeNS : LItemName := 'SizeNS' ;
crSizeNWSE : LItemName := 'SizeNWSE' ;
crSizeWE : LItemName := 'SizeWE' ;
crUpArrow : LItemName := 'UpArrow' ;
crHourGlass : LItemName := 'Wait' ;
crDrag : LItemName := '' ;
crNoDrop : LItemName := '' ;
crHSplit : LItemName := '' ;
crVSplit : LItemName := '' ;
crMultiDrag : LItemName := '' ;
crSQLWait : LitemName := '' ;
crNo : LItemName := 'No' ;
crAppStart : LItemName := 'Appstarting' ;
crHelp : LItemName := 'Help' ;
crHandPoint : LItemName := 'Hand' ;
crSizeAll : LItemName := 'SizeAll' ;
else
LItemName := '' ;
end ;
if LItemName = '' then Exit;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyName :='Control Panel\Cursors' ;
if LRegistry.OpenKey(LKeyName, False) then begin
Result := LRegistry.ReadString(LItemName);
// 環境変数が含まれている場合があるので実パスに変換
Result := ExpandEnvironmentString(Result);
LRegistry.CloseKey;
end ;
finally
FreeAndNil(LRegistry);
end ;
end ;
// -----------------------------------------------------------------------------
// 透明色 (または透過色) を除いた矩形領域を切り取った画像とそのサイズを取得
// 24 bit, 32 bit 兼用.出力は元のビットマップと同じ bit 数
//
// ABitmap : 元のビットマップ
// ADstBitmaap : 処理後のビットマップ
// 色のビット数は元のビットマップの色ビット数と同じ
// 結果としてのビットマップ゜が必要なければ nil を渡す
//
// 戻り値は TSize 型で処理後のビットマップの幅と高さ
// -----------------------------------------------------------------------------
function GetRealBitmapImageSize(ABitmap: TBitmap; ADstBitmap: TBitmap): TSize;
var
LBitmap : TBitmap;
LWidth : Integer;
LHeight : Integer;
LXPos : integer;
LYPos : Integer;
LScanArraySrc : PRGBQuadArray;
LScanArrayDst : PRGBQuadArray;
LColorDst : RGBQUAD;
LTransColor : TColor;
LTransR : Byte;
LTransG : Byte;
LTransB : Byte;
LRValue : Byte;
LGValue : Byte;
LBValue : Byte;
LAValue : Byte;
LIsTrans : Boolean;
LImgLeft : Integer;
LImgRight : Integer;
LImgTop : Integer;
LImgbottom : Integer;
LInImageY : Boolean;
LInImageX : Boolean;
begin
Result.Width := 0 ;
Result.Height := 0 ;
ABitmap.Canvas.Lock;
if ADstBitmap <> nil then ADstBitmap.Canvas.Lock;
LBitmap:= TBitmap.Create;
try
LBitmap.Assign(ABitmap);
LBitmap.PixelFormat := pf32bit;
if ADstBitmap <> nil then begin
ADstBitmap.PixelFormat := pf32bit;
end ;
LWidth := LBitmap.Width;
LHeight := LBitmap.Height;
LTransColor := LBitmap.Canvas.Pixels[0 , LBitmap.Height - 1 ];
LTransR := GetRValue(LTransColor);
LTransG := GetGValue(LTransColor);
LTransB := GetBValue(LTransColor);
// 切り取り矩形領域の上下左右の値
LImgLeft := LWidth - 1 ;
LImgRight := 0 ;
LImgTop := 0 ;
LImgbottom := 0 ;
LInImageY := False;
// 全ピクセルの色を調査.透過色のピクセルを調べていく
for LYPos := 0 to LHeight - 1 do begin
LScanArraySrc := LBitmap.ScanLine[LYPos];
LInImageX := False;
for LXPos := 0 to LWidth - 1 do begin
LRValue := LScanArraySrc[LXPos].rgbRed;
LGValue := LScanArraySrc[LXPos].rgbGreen;
LBValue := LScanArraySrc[LXPos].rgbBlue;
LAValue := LScanArraySrc[LXPos].rgbReserved;
if LTransColor = 0 then begin
LIsTrans := (LAValue = 0 );
end else begin
LIsTrans := (LRValue = LTransR) and (LGValue = LTransG) and (LBValue = LTransB);
end ;
// 切り取り領域の値を計算
if LIsTrans then begin
if not LInImageY then begin
if LYPos <= LImgTop then begin
LImgTop := LYPos + 1 ;
end ;
end ;
end else begin
LInImageY := True;
end ;
if not LIsTrans then begin
if not LInImageX then begin
if LXPos <= LImgLeft then begin
LImgLeft:= LXPos;
end ;
LInImageX := True;
end ;
if LXPos >= LImgRight then begin
LImgRight := LXPos;
end ;
if LYPos >= LImgBottom then begin
LImgBottom := LYPos;
end ;
end ;
end ;
end ;
LWidth := LImgRight - LImgLeft;
LHeight := LImgbottom - LImgTop;
// 元のビットマップの各画像の値を出力先のビットマップの画素に代入していく
if ADstBitmap <> nil then begin
ADstBitmap.SetSize(LWidth, LHeight);
for LYPos := 0 to LHeight - 1 do begin
LScanArraySrc := LBitmap.ScanLine[LYPos + LImgTop];
LScanArrayDst := ADstBitmap.ScanLine[LYPos];
for LXPos := 0 to LWidth - 1 do begin
LColorDst.rgbRed := LScanArraySrc[LXPos + LImgLeft].rgbRed;
LColorDst.rgbGreen := LScanArraySrc[LXPos + LImgLeft].rgbGreen;
LColorDst.rgbBlue := LScanArraySrc[LXPos + LImgLeft].rgbBlue;
LColorDst.rgbReserved := LScanArraySrc[LXPos + LImgLeft].rgbReserved;
LScanArrayDst[LXPos] := LColorDst
end ;
end ;
end ;
finally
FreeAndNil(LBitmap);
end ;
ABitmap.Canvas.Unlock;
if ADstBitmap <> nil then begin
if LTransColor = 0 then ADstBitmap.AlphaFormat := afDefined;
if ABitmap.PixelFormat = pf24bit then begin
ADstBitmap.PixelFormat := pf24bit;
ADstBitmap.Transparent := ABitmap.Transparent;
end ;
ADstBitmap.Canvas.Unlock;
end ;
Result.Width := LWidth;
Result.Height := LHeight;
end ;
// ============================================================================
// マウスカーソル画像の透明部分を除いた画像の矩形領域を取得
// スクリーン上のマウスカーソルが対象
//
// ABitmap : 元のマウスカーソルのビットマップ画像
// ADstBitmap : 出力.切り取った 32 bit のビットマップ
// 画像が不要であれば nil を渡す
//
// 戻り値値は TSize 型で,切り取った結果としての画像のサイズ
// ============================================================================
function GetRealCursorImageSize(ABitmap: TBitmap;
ADstBitmap: TBitmap = nil ): TSize;
begin
Result := GetRealBitmapImageSize(ABitmap, ADstBitmap);
end ;
// ============================================================================
// マウスカーソル画像の透明部分を除いた画像の矩形領域を取得
// Delphi の TCursor 型のマウスカーソルが対象 (crArrow 等)
//
// CursorID : Delphi の TCursor 型の値 (crArrow 等)
// 0 にするとスクリーン上のマウスカーソルが対象
// ADstBitmap : 出力.切り取ったビットマップ
// 画像が不要であれば nil を渡す
//
// 戻り値値は TSize 型で,切り取った結果としての画像のサイズ
// ============================================================================
function GetRealCursorImageSize(ACursor: TCursor;
ADstBitmap: TBitmap = nil ): TSize;
var
LBitmap : TBitmap;
LPoint : TPoint;
begin
Result.Width := 0 ;
Result.Height := 0 ;
LBitmap := TBitmap.Create;
try
LPoint := GetMouseCursorBitmap(ACursor, LBitmap);
Result := GetRealBitmapImageSize(LBitmap, ADstBitmap);
finally
FreeAndNil(LBitmap);
end ;
end ;
// =============================================================================
// マウスカーソルの半透明マスク画像の作成
// マウスカーソルの影部分の作成用
// 32 bit 用
//
// 与えられたビットマップの左下のピクセルと同じ色のピクセルを透明化する
// それ以外のピクセルは,透過度をOpadityで指定する値にする
//
// ABitmap : 処理対象のビットマップ.処理したビットマップを返す
// Opacity : 透明度.0 なら完全に透明
//
// 32 bit でアルファチャンネルが有効なビットマップ専用
// =============================================================================
procedure CreateMaskBitmapOpacity(ABitmap: TBitmap; Opacity: Byte);
var
LTransColor : TColor;
LFlag : Boolean;
LTransR : Byte;
LTransG : Byte;
LTransB : Byte;
LRed : Byte;
LGreen : Byte;
LBlue : Byte;
LAlpha : Byte;
LYPos : Integer;
LXPos : Integer;
LScanArray : PRGBQuadArray;
begin
ABitmap.Canvas.Lock;
// 指定色の R,G,B の各値を取得しておく
LTransColor := ABitmap.Canvas.Pixels[0 , ABitmap.Height - 1 ];
LTransR := GetRValue(LTransColor);
LTransG := GetGValue(LTransColor);
LTransB := GetBValue(LTransColor);
for LYPos := 0 to ABitmap.Height - 1 do begin
LScanArray := ABitmap.ScanLine[LYPos];
for LXPos := 0 to ABitmap.Width - 1 do begin
LRed := LScanArray[LXPos].rgbRed;
LGreen := LScanArray[LXPos].rgbGreen;
LBlue := LScanArray[LXPos].rgbBlue;
LAlpha := LScanArray[LXPos].rgbReserved;
LScanArray[LXPos].rgbRed := 0 ;
LScanArray[LXPos].rgbGreen := 0 ;
LScanArray[LXPos].rgbBlue := 0 ;
if LTransColor = 0 then begin
LFlag := ( LAlpha = 0 );
end else begin
LFlag := (LTransR = LRed) and (LTransG = LGreen) and (LTransB = LBlue);
end ;
if LFlag then begin
LScanArray[LXPos].rgbReserved := 0 ;
end else begin
LScanArray[LXPos].rgbReserved := Opacity;
end ;
end ;
end ;
ABitmap.Canvas.Unlock;
end ;
// =============================================================================
// TBitmap をウスぼかし画像にする
// 32 bit 用
//
// ARadius : ほかしの半径
// ARepeat : 繰り返し回数
//
// 32 bit のビットマップ専用
// =============================================================================
procedure GaussianBlur(ASrcBmp: TBitmap; ARadius, ARepeat: Integer);
var
LDstBmp : TBitmap;
LYRepeatCnt : Integer;
LXRepeatCnt : Integer;
LDiameter : Integer;
LImgWidth : Integer;
LImgHeight : Integer;
LPosX : Integer;
LPosY : Integer;
LPixel1 : Integer;
LPixel2 : Integer;
LCalcX : Integer;
LCalcY : Integer;
LScanLineDst : PRGBQuadArray;
LpARGB : PRGBQuad;
LAlpha : array of Integer;
LRed : array of Integer;
LGreen : array of Integer;
LBlue : array of Integer;
LScanStart : Integer;
LDistance : Integer;
LBytesShift : Integer;
begin
ASrcBmp.Canvas.Lock;
LDstBmp := TBitmap.Create;
try
LDstBmp.Assign(ASrcBmp);
if ARadius = 0 then exit;
LDiameter := (ARadius * 2 ) + 1 ;
if ASrcBmp.PixelFormat = pf32bit then begin
LBytesShift := SizeOf(TRGBQuad);
end else begin
LBytesShift := SizeOf(TRGBTriple);
end ;
LImgWidth := LDstBmp.Width - 1 ;
LImgHeight := LDstBmp.Height - 1 ;
// 計算範囲を (幅 x 半径 x2 + 1) に拡張
SetLength(LAlpha, LImgWidth + 1 + (ARadius * 2 ));
SetLength(LRed, LImgWidth + 1 + (ARadius * 2 ));
SetLength(LGreen, LImgWidth + 1 + (ARadius * 2 ));
SetLength(LBlue, LImgWidth + 1 + (ARadius * 2 ));
LScanLineDst := LDstBmp.ScanLine[0 ];
LDistance := integer(LDstBmp.ScanLine[1 ]) - integer(LScanLineDst);
LScanStart := Integer(LScanLineDst);
for LPosY := 0 to LImgHeight do begin
for LYRepeatCnt := 1 to ARepeat do begin
LXRepeatCnt := -ARadius;
while LXRepeatCnt <= LImgWidth + ARadius do begin
LCalcX := LXRepeatCnt;
if LCalcX < 0 then begin
LCalcX := 0 ;
end else
if LCalcX >= LImgWidth then begin
LCalcX := LImgWidth;
end ;
LpARGB := PRGBQuad(LScanStart + LCalcX * LBytesShift);
LPixel1 := LXRepeatCnt + ARadius;
LPixel2 := LXRepeatCnt + ARadius - 1 ;
LAlpha[LPixel1] := LpARGB^.rgbReserved + LAlpha[LPixel2];
LRed [LPixel1] := LpARGB^.rgbRed + LRed [LPixel2];
LGreen[LPixel1] := LpARGB^.rgbGreen + LGreen[LPixel2];
LBlue [LPixel1] := LpARGB^.rgbBlue + LBlue [LPixel2];
inc(LXRepeatCnt);
end ;
for LPosX := 0 to LImgWidth do begin
LpARGB := PRGBQuad(LScanStart + LPosX * LBytesShift);
LCalcX := LPosX + ARadius;
LPixel1 := LCalcX + ARadius;
LPixel2 := LCalcX - 1 - ARadius;
LpARGB^.rgbReserved := ((LAlpha[LPixel1] - LAlpha[LPixel2]) div LDiameter);
LpARGB^.rgbRed := ((LRed [LPixel1] - LRed [LPixel2]) div LDiameter);
LpARGB^.rgbGreen := ((LGreen[LPixel1] - LGreen[LPixel2]) div LDiameter);
LpARGB^.rgbBlue := ((LBlue [LPixel1] - LBlue [LPixel2]) div LDiameter);
end ;
end ;
inc(LScanStart, LDistance);
end ;
// 計算範囲を(高さ x 半径 x 2 + 1)に拡張
SetLength(LAlpha, LImgHeight + 1 + (ARadius * 2 ));
SetLength(LRed, LImgHeight + 1 + (ARadius * 2 ));
SetLength(LGreen, LImgHeight + 1 + (ARadius * 2 ));
SetLength(LBlue, LImgHeight + 1 + (ARadius * 2 ));
for LPosX := 0 to LImgWidth do begin
for LYRepeatCnt := 1 to ARepeat do begin
LScanStart := Integer(LScanLineDst);
LXRepeatCnt := -ARadius;
while LXRepeatCnt <= LImgHeight + ARadius do begin
if (LXRepeatCnt > 0 ) and (LXRepeatCnt < LImgHeight) then begin
inc(LScanStart, LDistance);
end ;
LpARGB := PRGBQuad(LScanStart + LPosX * LBytesShift);
LPixel1 := LXRepeatCnt + ARadius;
LPixel2 := LXRepeatCnt + ARadius - 1 ;
LAlpha[LPixel1] := LpARGB.rgbReserved + LAlpha[LPixel2];
LRed [LPixel1] := LpARGB.rgbRed + LRed [LPixel2];
LGreen[LPixel1] := LpARGB.rgbGreen + LGreen[LPixel2];
LBlue [LPixel1] := LpARGB.rgbBlue + LBlue [LPixel2];
inc(LXRepeatCnt);
end ;
LScanStart := Integer(LScanLineDst);
for LPosY := 0 to LImgHeight do begin
LpARGB := PRGBQuad(LScanStart + LPosX * LBytesShift);
LCalcY := LPosY + ARadius;
LPixel1 := LCalcY + ARadius;
LPixel2 := LCalcY - 1 - ARadius;
LpARGB.rgbReserved := ((LAlpha[LPixel1] - LAlpha[LPixel2]) div LDiameter);
LpARGB.rgbRed := ((LRed [LPixel1] - LRed [LPixel2]) div LDiameter);
LpARGB.rgbGreen := ((LGreen[LPixel1] - LGreen[LPixel2]) div LDiameter);
LpARGB.rgbBlue := ((LBlue [LPixel1] - LBlue [LPixel2]) div LDiameter);
inc(LScanStart, LDistance);
end ;
end ;
end ;
SetLength(LAlpha, 0 );
SetLength(LRed, 0 );
SetLength(LGreen, 0 );
SetLength(LBlue, 0 );
ASrcBmp.Assign(LDstBmp);
finally
FreeAndNil(LDstBmp);
end ;
ASrcBmp.Canvas.Unlock;
end ;
// =============================================================================
// TBitmap をアルファチャンネルの情報を保持してTPngImageに変換
// 戻り値は常に True
//
// 呼び出し側で TPngImage のインスタンスを以下のように生成しておく
// DstPngImage := TPngImage.CreateBlank(COLOR_RGBALPHA, 8,
// SrcBitmap.Width,SrcBitmap.Height);
// =============================================================================
function ConvertBitmapToPngimage(SrcBitmap: TBitmap;
DstPngImage: TPngImage): Boolean;
var
LBmpScanLine : PRGBQuadArray;
LPngScanLine : PRGBTripleArray;
LpPngARGBQuad : RGBQUAD;
LpBmpRGBTriple : RGBTRIPLE;
LYPos : Integer;
LXPos : Integer;
begin
Result := True;
SrcBitmap.Canvas.Lock;
DstPngImage.Canvas.Lock;
for LYPos := 0 to SrcBitmap.Height - 1 do begin
LBmpScanLine := SrcBitmap.ScanLine[LYPos];
LPngScanLine := DstPngImage.ScanLine[LYPos];
for LXPos := 0 to SrcBitmap.Width - 1 do begin
LpPngARGBQuad := LBmpScanLine[LXPos];
LpBmpRGBTriple.rgbtBlue := LpPngARGBQuad.rgbBlue;
LpBmpRGBTriple.rgbtGreen := LpPngARGBQuad.rgbGreen;
LpBmpRGBTriple.rgbtRed := LpPngARGBQuad.rgbRed;
LPngScanLine[LXPos] := LpBmpRGBTriple;
DstPngImage.AlphaScanline[LYPos]^[LXPos] := LpPngARGBQuad.rgbReserved;
end ;
end ;
SrcBitmap.Canvas.Unlock;
DstPngImage.Canvas.Unlock;
end ;
end .