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 によるマウスカーソル ( マウスポインタ ) サイズの変更


以下のコマンドを実行して設定画面を起動します.その設定画面のスライダーでマウスポインタのサイズを変更でききす.スライダになっていますが,連続的ではなく,実際には飛び飛びの値になります.


ms-settings:easeofaccess-mousepointer


[備考]
コマンドを実行するには [ファイル名を指定して実行] のダイアログを使用するか,上のリンクをクリックします.コマンドプロンプトからも実行できますが,先頭に start が必要です.
[ ファイル名を指定して実行] のダイアログは [Win] + [R] のキー操作で起動できます.


図1
マウスポインタの設定画面
  • サイズと色の設定部分

マウスポインタの色も変更できます.上の画面では右下の部分をクリックすると色の選択状態となります.
マウスポインタのサイズや色を変更すると,マウスポインタのデザインがデフォルトの状態に戻されることがあります.



図2
マウスポインタの色を変更

マウスポインタのデザインは,下図のダイアログで設定できます.このダイアログは以下のコマンドを実行すると起動します.このコマンドは,[Win] + [R] のキー操作で [ファイル名を指定して実行] ダイアログを起動して実行できます.このコマンドは,コマンドプロンプトからも実行できます.

control.exe main.cpl ,1


図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
マウスカーソルをキャプチャするコードの例
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
指定サイズのマウスカーソルの画像をビットマップとして取得
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 の,レジストリの項目に相当するマウスカーソル名が分かれば,そのファイルを読み込んで画像が取得できることになります.実際の画像を読み込めば,ホットスポットの正確な値の取得も期待できます.

  1. マウスカーソルの画像サイズ
  2. マウスカーソルのハンドル
  3. Windows 10 におけるマウスカーソル名 (レジストリの項目名)
  4. ホットスポットの値

これらの処理を関数あるいは手続きとして作成していきます.




05_マウスカーソルの画像サイズの取得


マウスカーソル画像のサイスは既に調べたように,レジストリから取得できます.その例です.


図15
マウスカーソルの画像サイズ

リスト3
マウスカーソルの画像サイズをレジストリから取得
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 型のマウスカーソル名を列挙
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 のマウスカーソル名一覧
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 のマウスカーソルかを調べるテスト
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 関数でビットマップを取得
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
マウスカーソル画像取得用ユニットのテストプログラム
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 画像に変換
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.




10_マウスカーソル画像の実画像領域の切り取り


マウスカーソル画像取得用ユニット plMouseCursorImage に,実際にマウスカーソルの画像がある矩形領域を取得する関数を実装しています.そのテストプログラムです.
関数名は GetRealCursorImageSize で,戻り値は矩形領域のサイズです.


図27
実際に画像がある領域

リスト10
マウスカーソル画像の実画像領域の切り取り
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
マウスカーソルのサイズに合わせてヒントの表示位置を変更
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
アクティブウィンドウをマウスカーソルを含めてキャプチャ
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
マウスカーソル画像取得 (マウスキャプチャ) 用ユニットのソースコード
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.