Delphi Programming / Object Pascal
[掲載 2016年08月24日] [更新 2017年07月23日] Delphi サンプルプログラム集
908_EXE や DLL ファイル内のアイコンの取得
動作確認等
Windows 7 U64(SP1) + Delphi XE(UP1) Pro
908_ExtractIcon.zip [17.9 MB] 2018年11月26日版 (EXE 同梱)
2016年09月03日
[アイコンの変更] ダイアログで他のファイルを選択した時に正常に取得できない現象を修正
2016年09月09日
EnumResourceNames 関数による EXE, DLL 内のアイコンの取得保存のサンプルを追加
マルチアイコン関係のサンプル等を追加
掲載済みの記事とサンプルの修正等
2016年09月09日
EnumResourceNames 関数による EXE, DLL 内のアイコンの取得保存のサンプルとマルチアイコンのサンプルで,PNG 形式のアイコンをビットマップ形式のアイコンとして取得できるようにした
アイコンは,操作の対象 (オブジェクト) とを関連付けした象徴的な画像の意味ですが,Windows ではコンピュータのディスプレイに表示される「小さな画像」の意味でも使われてきました.
画像を扱う立場で言う「アイコン」は,画像の形式のことを意味します.「アイコンファイル」は,その画像形式で保存した,拡張子が .ico のファイルのことを意味します.本ページで扱っているのはこのアイコンです.つまり,狭い意味でのアイコンとも言えます.
アイコン画像の作成,編集のソフトでは,多くの場合広い意味のアイコンとして使われています.したがって,複数の画像形式を扱えるのが一般的です.
コンポーネントパレットに表示される画像もアイコンと呼ばれていますが,画像の形式はビットマップです.アイコン本来の意味で使われていることになります.
Delphi でアイコン形式の画像を扱うには主に TIcon を使用します.
アイコンの特性については,中村の里の記事で確認してください.
[備考 1]
EXE や DLL に格納されているアイコンは 1 つと限りません.複数のアイコンが含まれていることもあります.また,各々のアイコンはサイズや色数が異なる複数の画像が含まれている,いわゆるマルチアイコンとなっていることもあります.
[備考 2]
ビットマップ形式の画像とは,広い意味では,ベクター方式ではない,つまり,ラスター方式の画像のことを意味します.JPEG, GIF, ICON, PNG 画像は全てビットマップ方式の画像です.これらのビットマップデータは,格納や圧縮方式に違いがあります.
当サイトの記事では,TBitmap で扱える画像のことをビットマップと言っています.
[備考 3]
携帯電話等の画面に表示されているアイコンは,ピクトグラム (小さな絵) の意味から「ピクト」と呼ばれることがあります.
[備考 4]
本ページは DLL, EXE のリソースからアイコン画像を取得に関する記事とサンプルですが,以下のサンプルで使用している unitResFile ユニットを使用すると DLL, EXE 内のビットマップや文字列等,他のリソースも取得することができます.unitResFile は,リソースエディタ XN Resource Editor で使用されているユニットです.
unitResFile
では,各種のリソースのデータを TMemoryStream に取得して操作します.
01_ [ アイコンの変更 ] ダイアログを使用したアイコンの取得
画像のデザインを確認してアイコンを取得するのであれば,下図のようなダイアログを表示して選択することができます.このダイアログは,ファイルのショートカットのプロパティで [アイコンの変更] を選択した時に表示されるダイアログと同じです.
本サンプルでは,選択したアイコンをフォームに配置した TImage に描画します.
このダイアログは,Shell32.dll の序数 62 の非公開関数を使用しています.
システム関係のアイコンは imageres.dll, shell32.dll に多く格納されています.アプリの .EXE ファイルにも,そのアプリのリソースとしてのアイコンが含まれています.
図1
実行して Button1 クリック
図2
アインコを選択した結果
TImage にアイコンを描画
Button2 はアイコン画像の保存用
本サンプルでは,アイコンを取得するのに ExtractIcon 関数を使用しています.本サンプルの場合,この関数はシステムの既定サイズである 32 x 32 のサイズでアイコンを取得します.
この値は,GetSystemMetrics 関数の引数を SM_CXICON または SM_CYICON にした時の値です.
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LSizeX : Integer;
LSizeY : Integer;
LText : string ;
begin
LSizeX := GetSystemMetrics(SM_CXICON);
LSizeY := GetSystemMetrics(SM_CYICON);
LText := IntToStr(LSizeX) + sLineBreak + IntToStr(LSizeY);
MessageBox(Handle, PChar(LText), '情報' , MB_ICONINFORMATION);
end ;
下図は Shell32.dll 内の 270 番目のアイコンですが,このアイコンは DLL 内に 16 x 16 のサイズで格納されています.ExtractIcon 関数で取得する際,32 x 32 のサイズのアイコンを作成して,それを取得するようになっています.つまり,拡大した画像となっています.
逆に,32 x 32 以下の小さいサイズのアイコンがない場合は,32 x 32 に縮小したサイズのアイコンが作られます.
図3
Shell32.dll 内の 270 番のアイコン
リスト1
[ アイコンの変更 ] ダイアログを表示してアイコンを取得
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ShellAPI, ExtCtrls, ExtDlgs;
type
TForm1 = class (TForm)
Button1: TButton;
Image1: TImage;
Button2: TButton;
SavePictureDialog1: TSavePictureDialog;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private 宣言 }
FFileName : string ;
FIconIndex : Integer;
public
{ Public 宣言 }
end ;
// ---------------------------------------------------------------------------
// shell32.dllの序数62の名前なし関数を使用するための関数の定義
// この関数は非公開API関数
//
// usesにShellAPIが必要.
// ---------------------------------------------------------------------------
function SH_PickIconDlg(
Handle : THandle;
lpFileName : PChar;
var FileSize : DWORD;
var Index : Integer):Integer; stdcall ; external 'shell32.dll' index 62 ;
var
Form1: TForm1;
implementation
{$R *.DFM}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
FFileName := 'imageres.dll' ;
end ;
// =============================================================================
// [アイコンの変更]ダイアログを表示
// 選択したら,そのアイコンをImage1に表示
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LpFileName : PChar;
LFileSize : DWORD;
LIcon : TIcon;
begin
LFileSize := MAX_PATH;
LpFileName := StrAlloc(LFileSize + 1 );
StrPCopy(LpFileName, FFileName);
LIcon := TIcon.Create;
try
// 関数を実行
if SH_PickIconDlg(Handle, LpFileName, LFileSize, FIconIndex) = 1 then begin
// アイコンを取出す
LIcon.Handle := ExtractIcon(hInstance, LpFileName, FIconIndex);
Image1.Picture.Assign(LIcon);
Label1.Caption := 'IconIndex : ' + IntToStr(FIconIndex);
FFileName := LpFileName;
end ;
finally
FreeAndNil(LIcon);
StrDispose(LpFileName);
end ;
end ;
// =============================================================================
// 取り出したアイコンを保存する
// =============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LIcon : TIcon;
LFileName : string ;
begin
if Image1.Picture = nil then exit;
SavePictureDialog1.InitialDir := ExtractFileDir(Application.ExeName);
if SavePictureDialog1.Execute then begin
LIcon := TIcon.Create;
try
LIcon.Assign(Image1.Picture.Icon);
LFileName := SavePictureDialog1.FileName;
if ExtractFileExt(LFileName) = '' then begin
LFileName := LFileName + '.ico' ;
end ;
LIcon.SaveToFile(LFileName);
finally
FreeAndNil(LIcon);
end ;
end ;
end ;
end .
02_ ExtractIcon 関数を使用したアイコンの取得
上のサンプルで使用していますが,
ファイル内のアイコンは,ExtractIcon 関数で,何番目のアイコンかを指定して取り出すことができます.そのサンプルです.
このアイコンの番号は,0 ベースの値です.0 が一番最初のアイコンです.本サンプルでは 32 x32 のサイズのアイコンを取得します.
[備考]
上の最後のリンクの記事では,ExtractIcon 関数で取得したアイコンのハンドルを使用してアイコンの画像を描画しています.TIcon は使用していません.このような場合は,DestroyIcon 関数でアイコンのハンドルを破棄する必要があります.
TIcon にアイコンバンドルを代入した場合は DestroyIcon による破棄は必要ありません.TIcon が処理します.
図4
設計時の画面
図5
実行結果
リスト2
アイコンの番号を指定して ExtractIcon 関数でアイコンを取り出す
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
Image1: TImage;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
ShellAPI;
{$R *.dfm}
// =============================================================================
// アイコンの番号を指定してシステムのアイコン画像を取得する
// usesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LDllName : string ;
LIconIndex : UINT;
LIcon : TIcon;
LExeDir : string ;
begin
LDllName := 'imageres.dll' ;
LExeDir := ExtractFilePath(Application.ExeName);
LIcon := TIcon.Create;
try
LIconIndex := 94 ;
LIcon.Handle := ExtractIcon(hInstance, PChar(LDllName), LIconIndex);
if LIcon.Handle > 0 then begin
Image1.Canvas.Draw(0 , 0 , LIcon);
LIcon.SaveToFile(LExeDir + LDllName + '_' + IntToStr(LIconIndex) + '.ico' );
end ;
LIconIndex := 73 ;
LIcon.Handle := ExtractIcon(hInstance, PChar(LDllName), LIconIndex);
if LIcon.Handle > 0 then begin
Image1.Canvas.Draw(50 , 0 , LIcon);
LIcon.SaveToFile(LExeDir + LDllName + '_' + IntToStr(LIconIndex) + '.ico' );
end ;
LIconIndex := 79 ;
LIcon.Handle := ExtractIcon(hInstance, PChar(LDllName), LIconIndex);
if LIcon.Handle > 0 then begin
Image1.Canvas.Draw(100 , 0 , LIcon);
LIcon.SaveToFile(LExeDir + LDllName + '_' + IntToStr(LIconIndex) + '.ico' );
end ;
LIconIndex := 76 ;
LIcon.Handle := ExtractIcon(hInstance, PChar(LDllName), LIconIndex);
if LIcon.Handle > 0 then begin
Image1.Canvas.Draw(150 , 0 , LIcon);
LIcon.SaveToFile(LExeDir + LDllName + '_' + IntToStr(LIconIndex) + '.ico' );
end ;
LIconIndex := 93 ;
LIcon.Handle := ExtractIcon(hInstance, PChar(LDllName), LIconIndex);
if LIcon.Handle > 0 then begin
Image1.Canvas.Draw(200 , 0 , LIcon);
LIcon.SaveToFile(LExeDir + LDllName + '_' + IntToStr(LIconIndex) + '.ico' );
end ;
finally
FreeAndNil(LIcon);
end ;
end ;
ExtractIcon 関数の最後の引数の値を -1 にして実行すると,ExtractIcon 関数は,ファイルに格納されているアイコンの数を返します.
その数だけ,つまり,ファイル内の全てのアイコンを取り出して保存する例です.EXE があるフォルダ内に [ICONS] と言う名前フォルダを自動で作成して,そこに保存します.
このサンプルで保存したアイコンのサイズは全て 32 x 32 となります.ExtractIcon 関数ではなく,後述の PrivateExtractIcons 関数を使用すれば,任意のサイズでアイコンを取得できます.
また,[EXE または DLL 内のアイコンを列挙する] サンプルを使用すると,全てのサイズのアイコンを取得してマルチアイコンのファイルとして保存できます.
図6
imageres.dll 内のすべてのアイコン画像を保存した結果
リスト3
指定ファイル内のアイコンを全て取り出して保存
アイコンのサイズは全て 32 x 32
32 x 32 のサイズのアイコンがない場合は,拡大または縮小されたアイコン画像となる
copy code
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := 'imageres.dll' ;
end ;
// =============================================================================
// 指定ファイル内のすべてのアイコンを取得する
// 保存のみ(上書き保存)
// usesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LDllName : string ;
LIconIndex : UINT;
LIcon : TIcon;
LExeDir : string ;
LCount : Integer;
begin
LDllName := Trim(Edit1.Text);
LExeDir := ExtractFilePath(Application.ExeName) + 'ICONS' + PathDelim;
ForceDirectories(LExeDir);
// アイコンの総数を取得
LCount := -1 ;
LCount := ExtractIcon(hInstance, PChar(LDllName), LCount);
LIcon := TIcon.Create;
try
for LIconIndex := 0 to LCount - 1 do begin
LIcon.Handle := ExtractIcon(hInstance, PChar(LDllName), LIconIndex);
if LIcon.Handle > 0 then begin
LIcon.SaveToFile(LExeDir + LDllName + '_' + IntToStr(LIconIndex) + '.ico' );
end ;
Application.ProcessMessages;
end ;
finally
FreeAndNil(LIcon);
end ;
end ;
03_ ExtractIconEx 関数を使用したアイコンの取得
ExtractIconEx 関数は,大きいアイコンと小さいアイコンの両方を同時に取得できます.
大きいサイズは 32 x 32, 小さいサイズは 16 x 16 です.
Windows API には TIcon と言うクラスありません.取得できるのはアイコンのハンドルです.そのハンドルの値を TIcon.Handle に代入して TIcon を描画します.
32 x 32 または 16 x 16 のサイズのアイコンが EXE, DLL 内にない場合は,適当なサイズのアイコンから作成されて取得するようになっています.
[備考]
上の最後のリンクの記事では,ExtractIconEx 関数で取得したアイコンのハンドルを使用してアイコンの画像を描画しています.TIcon は使用していません.このような場合は,DestroyIcon 関数でアイコンのハンドルを破棄する必要があります.
TIcon にアイコンバンドルを代入した場合は DestroyIcon による破棄は必要ありません.TIcon が処理します.
図7
設計時の画面
図8
実行結果
リスト4
ExtractIconEx 関数を使用して,指定番号の大きいアイコンと小さいアイコンを取り出す
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
Image1: TImage;
Image2: TImage;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
ShellAPI;
{$R *.dfm}
// =============================================================================
// ExtractIconEx関数を使用して大きいアイコンと小さいアイコンを取り出す
// usesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LSrcFileName : string ;
LIconIndex : UINT;
LhIconLarge : HICON;
LhIconSmall : HICON;
LIcon : TIcon;
LSaveFileName : string ;
begin
LSrcFileName := 'imageres.dll' ;
LIconIndex := 94 ;
LSaveFileName := ExtractFilePath(Application.ExeName) + LSrcFileName
+ '_' + IntToStr(LIconIndex);
// アイコンを取り出す
ExtractIconEx(PChar(LSrcFileName), LIconIndex, LhIconLarge, LhIconSmall, 1 );
if LhIconLarge > 0 then begin
LIcon := TIcon.Create;
try
LIcon.Handle := LhIconLarge;
Image1.Canvas.Draw(0 , 0 , LIcon);
LIcon.SaveToFile(LSaveFileName + '_Large.ico' );
finally
FreeAndNil(LIcon);
end ;
end ;
if LhIconSmall > 0 then begin
LIcon := TIcon.Create;
try
LIcon.Handle := LhIconSmall;
Image2.Canvas.Draw(0 , 0 , LIcon);
LIcon.SaveToFile(LSaveFileName + '_Small.ico' );
finally
FreeAndNil(LIcon);
end ;
end ;
end ;
end .
04_ PrivateExtractIcons 関数を使用したアイコンの取得
PrivateExtractIcons 関数は,アイコンを任意のサイズで取り出せます.「任意のサイズのアイコンを取り出す」のではありません.
自分自身がアプリケーションにアイコンを格納する時もそうですが,EXE や DLL には,全てのサイズのアイコンが格納されているわけではありません.指定したサイズのアイコンが格納されていない場合は,拡大あるいは縮小された画像となります.
下図は EXCEL.EXE 内の最初のアイコンと Delphi で作成した EXE ファイルのアイコンを取得して表示しています.
図9
設計時の画面
TSpinEdit でアイコンの番号を指定できるようにしてみた
Label1 は取得したアイコンの ID 値の表示用
アイコンを取得するとディスクに自動保存するようにしている
図10
48 x 48 のサイズのアイコン
図11
256 x 256 サイズのアイコン
ExtractIcon 関数では全て 32 x 32 のサイズでアイコンを取得するようになっています.
Shell32.dll 内には 256 x 256 サイズのアイコンも格納されています.PrivateExtractIcons 関数を使用すると 32 x 32 以外のサイズのアイコンも取得できます.下図はその例です.
このアイコンは複数のサイズや色数を持つのアイコンが格納されているグループアイコンになっています.グループアイコンを保存すると,マルチアイコンファイルとなりますが,PrivateExtractIcons 関数はマルチアイコンとしては取得,保存できません.グループの中の 1 つだけを取得して保存することになります.
図12
Shell32.dll 内の 256 x 256 アイコン
この 15 番目のアイコンはグループアイコンとなっている
128 x 128 を選択すると 256 x 256 を縮小したアイコンとなる
Shell32.dll 内のアイコンのほどんどがグループアイコン
全てのアイコンに 256 x 256 サイズがあるわけではない
下の図は Shell32.dll 内の 6 番目のアイコンを 256 x 256 のサイズで取得していますが,拡大されたアイコン画像になっています.
本サンプルを利用すると,元のアイコンのサイズを調べることができます.この画像のアイコンの場合は元のサイズは 48 x 48 と推定できます.ただし,大きなサイズが縮小されている場合は判定できません.また,本サンプル以外のサイズのアイコンが格納されている場合があります.したがって,必ずしも確実な結果とは言えない場合があります.
[備考]
指定サイズのアイコンがない場合,内部で指定サイズのアイコンを生成して取得するようになっているのは Windows の特性とも言えます.
Windows では,エクスプローラやデスクトップ等,コンピュータでの表示場所によってアイコンのサイズが異なります.もし,目的のサイズだけしか取得できない場合,EXE や DLL 内にそのサイズのアイコンが存在しないと,アイコンを表示しないことになります.
図13
Shell32.dll 内の 6 番目のアイコン
256 x 256 のサイズで取得
拡大されたアイコンとなっている
図14
48 x 48 のサイズで取得した結果
リソース ID が 256 x 256 で取得した時と同じ
154 なっている
図15
32 x 32 のサイズで取得した結果
リソース ID が 155 に変わっている
したがって,256 x 256 のサイズで取得した時のこのアイコンの元のサイズは 48 x 48
リスト5
PrivateExtractIcons 関数を使用して,任意のサイズでアイコンを取得する
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, Spin;
type
TForm1 = class (TForm)
Button1: TButton;
Image1: TImage;
RadioGroup1: TRadioGroup;
Edit1: TEdit;
Label1: TLabel;
SpeedButton1: TSpeedButton;
OpenDialog1: TOpenDialog;
SpinEdit1: TSpinEdit;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
{$R *.dfm}
function PrivateExtractIcon(lpszFile : PChar;
nIconIndex : Integer;
cxIcon : Integer;
cyIcon : Integer;
phicon : PHandle;
piconid : PUINT;
nIcons : UINT;
flags : UINT): UINT; stdcall ;
external 'user32.dll' name 'PrivateExtractIconsW' ;
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
OpenDialog1.InitialDir := ExpandFileName('..\' );
RadioGroup1.ItemIndex := 2 ;
Edit1.Text := '' ;
end ;
// =============================================================================
// PrivateExtractIcon関数を使用してアイコンを任意のサイズで取得する
// 全てのサイズのアイコンが格納されているわけではない
// 格納されていないサイズを指定すると拡大または縮小した画像となる
//
// PrivateExtractIcon関数でアイコンが取得できのは以下のファイル
// .EXE, .DLL, .ICO, .CUR, .BMP, .ANI
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LSrcFileName : string ;
LIconIndex : UINT;
LIconSize : Integer;
LhIcon : HICON;
LnIcons : UINT;
LnIconId : UINT;
LIcon : TIcon;
begin
Image1.Picture.Assign(nil );
LSrcFileName := Trim(Edit1.Text);
LIconIndex := SpinEdit1.Value;
LIconSize := 32 ;
LnIcons := 1 ;
if RadioGroup1.ItemIndex = 0 then LIconSize := 16 ;
if RadioGroup1.ItemIndex = 1 then LIconSize := 24 ;
if RadioGroup1.ItemIndex = 2 then LIconSize := 32 ;
if RadioGroup1.ItemIndex = 3 then LIconSize := 48 ;
if RadioGroup1.ItemIndex = 4 then LIconSize := 64 ;
if RadioGroup1.ItemIndex = 5 then LIconSize := 128 ;
if RadioGroup1.ItemIndex = 6 then LIconSize := 256 ;
// アイコンを取り出す
// 関数の戻り値は取得したアイコンの数
if PrivateExtractIcon(PChar(LSrcFileName),
LIconIndex,
LIconSize,
LIconSize,
@LhIcon,
@LnIconId,
LnIcons,
LR_LOADFROMFILE) = LnIcons then begin
LIcon := TIcon.Create;
try
Label1.Caption := 'IconID : ' + IntToStr(LnIconId);
if LhIcon <> 0 then begin
LIcon.Handle := LhIcon;
Image1.Canvas.Draw(0 , 0 , LIcon);
// 描画したアイコンを保存
// だだし全て同じ名前で上書き保存
LIcon.SaveToFile(IntToStr(LIconSize) + '.ico' );
Application.ProcessMessages;
end ;
finally
FreeAndNil(LIcon);
end ;
end ;
end ;
// =============================================================================
// ダイアログを表示してファイルを選択
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
OpenDialog1.OptionsEx := [];
OpenDialog1.FileName := '' ;
if not OpenDialog1.Execute then exit;
OpenDialog1.InitialDir := ExtractFileDir(OpenDialog1.FileName);
Edit1.Text := OpenDialog1.FileName;
SpinEdit1.Value := 0 ;
Button1Click(nil );
end ;
end .
PrivateExtractIcons 関数の第 5 引数の値を nil にすると,DLL または EXE に含まれているアイコンの数を取得することができます.その場合,ほとんどの引数は使われません.
下図は,Shell32.dll 内のアイコン数を取得した結果です.
図16
DLL または EXE 内のアイコン数を取得
リスト6
DLL または EXE 内のアイコン数を取得
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LSrcFileName : string ;
LnIcons : UINT;
begin
LSrcFileName := 'c:\windows\System32\Shell32.dll' ;
// DLL または EXE 内のアイコン数を取得
// DLL, EXE 以外では不可
LnIcons := PrivateExtractIcon(PChar(LSrcFileName),
0 ,
0 ,
0 ,
nil ,
nil ,
0 ,
LR_LOADFROMFILE);
MessageBox(Handle, PChar(IntToStr(LnIcons)), 'アイコン数' , MB_ICONINFORMATION);
end ;
05_ SHGetStockIconInfo 関数を使用したアイコンの取得
SHGetStockIconInfo 関数は,主にシステムリソースのアイコンを取り出す関数です.
取得するアイコンのサイズとアイコンの種類を定数名で指定します.
SHGetStockIconInfo 関数は Windows Vista 以降で使用可能です.
図17
設計時の画面
図18
実行結果
リスト7
システムリソースのアイコンを取り出す SHGetStockIconInfo 関数の使用例
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
Image1: TImage;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
ShellAPI;
{$R *.dfm}
// -----------------------------------------------------------------------------
// システム関係のアイコンを取得する関数
// SHGetStockIconInfoはWindows Vista以降で使用可能
// これらのアイコンは"C:\Windows\system32\imageres.dll"に格納されている
//
// usesにShellAPIが必要
// -----------------------------------------------------------------------------
function GetStockIcon(Id: Integer; ASize: Cardinal; AIcon: TIcon): HRESULT;
var
LSHIconInfo : TSHStockIconInfo;
LuFlags : Cardinal;
LResCode : HResult;
begin
Result := 0 ;
LSHIconInfo.cbSize := SizeOf(LSHIconInfo);
LuFlags := SHGSI_ICON or ASize;
LResCode := SHGetStockIconInfo(id, LuFlags, LSHIconInfo);
Result := LResCode;
if Result = S_OK then AIcon.Handle := LSHIconInfo.hIcon;
end ;
// =============================================================================
// システムのアイコン画像を取得する
// 必要であればビットマップ画像等に変換して使用する
// SHGSI_LARGEICONは大きいアイコン
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LIcon : TIcon;
LFileName : string ;
begin
LFileName := ExtractFilePath(Application.ExeName);
LIcon := TIcon.Create;
try
if GetStockIcon(SIID_HELP, SHGSI_LARGEICON, LIcon) = S_OK then begin
Image1.Canvas.Draw(0 , 0 , LIcon);
LIcon.SaveToFile(LFileName + 'SIID_HELP.ico' );
end ;
if GetStockIcon(SIID_SHIELD, SHGSI_LARGEICON, LIcon) = S_OK then begin
Image1.Canvas.Draw(50 , 0 , LIcon);
LIcon.SaveToFile(LFileName + 'SIID_SHIELD.ico' );
end ;
if GetStockIcon(SIID_WARNING, SHGSI_LARGEICON, LIcon) = S_OK then begin
Image1.Canvas.Draw(100 , 0 , LIcon);
LIcon.SaveToFile(LFileName + 'SIID_WARNING.ico' );
end ;
if GetStockIcon(SIID_INFO, SHGSI_LARGEICON, LIcon) = S_OK then begin
Image1.Canvas.Draw(150 , 0 , LIcon);
LIcon.SaveToFile(LFileName + 'SIID_INFO.ico' );
end ;
if GetStockIcon(SIID_ERROR, SHGSI_LARGEICON, LIcon) = S_OK then begin
Image1.Canvas.Draw(200 , 0 , LIcon);
LIcon.SaveToFile(LFileName + 'SIID_ERROR.ico' );
end ;
finally
FreeAndNil(LIcon);
end ;
end ;
end .
06_ LoadIcon 関数を使用してリソースのアイコンを取得
LoadIcon 関数は,EXE や DLL 内のアイコンの番号ではなく,リソースの ID 値を指定してアイコンを取得します.いくつかの定数名が用意されています.
LoadIcon 関数は,第 1 引数の値を 0 にすると システムリソースからのアイコン取得となります.その確認用のサンプルです.
用意されている定数名の IDI_SHIELD (盾マークのアイコン) は,筆者の Windows 7 では 128 x 128 サイズのアイコンになってしまいます.Windows 10 では 32 x 32 です.
Windows 7 では,次の値にすると 32 x 32 のサイズになることを確認しています.
LIcon.Handle := LoadIcon(0, PChar(MakeIntResource(106)));
図19
Windows 7(UP1) での実行結果
IDI_SHIELD のアイコンは 128 x 128
図20
Windows 10 ビルド 14393 での実行結果
IDI_SHIELD のアイコンも 32 x 32
取得結果のアイコンのデザインは,取得元と使用する関数によって違いがあるようです.
下図は,上の図と同じ Windows 10 ビルド 14393 での結果ですが,ExtractIcon 関数を使用して imageres.dll から取得したアイコンです.
図21
ExtractIcon 関数を使用して取得したアイコン
リスト8
LoadIcon 関数を使用してシステムリソースのアイコンを取得
copy code
// =============================================================================
// LoadIcon関数でシステムリソースのアイコンを取得
// LoadIcon関数の第1引数を0にして,第2引数にID値を指定する
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LhIcon : HICON;
LIcon : TIcon;
begin
LIcon := TIcon.Create;
try
LIcon.Handle := LoadIcon(0 , IDI_QUESTION);
Image1.Canvas.Draw(0 , 0 , LIcon);
LIcon.Handle := LoadIcon(0 , IDI_SHIELD);
Image1.Canvas.Draw(50 , 0 , LIcon);
LIcon.Handle := LoadIcon(0 , IDI_WARNING);
Image1.Canvas.Draw(100 , 0 , LIcon);
LIcon.Handle := LoadIcon(0 , IDI_INFORMATION);
Image1.Canvas.Draw(150 , 0 , LIcon);
LIcon.Handle := LoadIcon(0 , IDI_HAND);
Image1.Canvas.Draw(200 , 0 , LIcon);
finally
FreeAndNil(LIcon);
end ;
end ;
07_ ファイルのアイコンを SHGetFileInfo 関数で取得
ここまでのサンプルは EXE や DLL からアイコンを取得しています.
エクスプローラでフォルダ内のファイルをリスト表示すると,EXE ファイルや DLL ファイル以外のファイルにも左端にアイコンが描画されています.これらのアイコンは,そのファイルの拡張子に関連付けされたアイコンです.
これらのアイコンはシステムに登録されています.これらのアイコンは,以下のリンクのサンプルで使用している SHGetFileInfo 関数で取得できますが,SHGetFileInfo 関数単独のサンプルとしてここに掲載しておきます.取得するアイコン画像がフラグの組み合せでどのように違うのかを確認できるようにしてみました.
TOpenDialog でファイルを選択するようにしていますが,TOpenDialog は,デフォルトの設定のままだとショートカットのファイルは,そのリンク先のファイル名を検出します.そこで,この機能を無効にしています.
図22
設計時の画面
アイコン取得のフラグの組み合わせをテストできるようにしてみた
アイコンを保存するコードはない
図23
32 x 32 サイズのアイコン
図24
ショートカットファイルのアイコンの例
リスト9
ファイルのアイコンを SHGetFileInfo 関数で取得
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons;
type
TForm1 = class (TForm)
OpenDialog1: TOpenDialog;
Image1: TImage;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
Button1: TButton;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
CheckBox3: TCheckBox;
CheckBox4: TCheckBox;
CheckBox5: TCheckBox;
CheckBox6: TCheckBox;
CheckBox7: TCheckBox;
CheckBox8: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
ShellApi;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
OpenDialog1.InitialDir := ExpandFileName('..\' );
Edit1.Text := ExtractFilePath(Application.ExeName) + 'Project1.exe' ;
Edit1.AutoSelect := False;
CheckBox1.Checked := True;
end ;
// =============================================================================
// ダイアログを表示してファイルを選択
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
// .lnkファイルの場合,リンク先でなく.lnkファイルそのものを取得するようにする
// TOpenDialogは.lnkの場合,デフォルトではリンク先のファイル名を取得する
OpenDialog1.Options := OpenDialog1.Options + [ofNoDereferenceLinks];
OpenDialog1.OptionsEx := [];
OpenDialog1.FileName := '' ;
if not OpenDialog1.Execute then exit;
OpenDialog1.InitialDir := ExtractFileDir(OpenDialog1.FileName);
Edit1.Text := OpenDialog1.FileName;
Button1Click(nil );
end ;
// =============================================================================
// SHGetFileInfo関数を使用してファイルのアイコンを取得
// このアイコンはエクスプローラに描画されているアイコン
// 拡張子に関連付けされアプリのアイコン
//
// usesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFileName : String ;
LSHFileInfo : TSHFileInfo;
LuFlags : Cardinal;
LIcon : TIcon;
begin
if Edit1.Text = '' then exit;
LFileName := Edit1.Text;
Image1.Picture.Assign(nil );
LIcon := TIcon.Create;
try
// 取得するアイコンのサイズ等の指定
LuFlags := 0 ;
if CheckBox1.Checked then LuFlags := LuFlags or SHGFI_ICON;
if CheckBox2.Checked then LuFlags := LuFlags or SHGFI_LARGEICON;
if CheckBox3.Checked then LuFlags := LuFlags or SHGFI_LINKOVERLAY;
if CheckBox4.Checked then LuFlags := LuFlags or SHGFI_OPENICON;
if CheckBox5.Checked then LuFlags := LuFlags or SHGFI_SELECTED;
if CheckBox6.Checked then LuFlags := LuFlags or SHGFI_SHELLICONSIZE;
if CheckBox7.Checked then LuFlags := LuFlags or SHGFI_SMALLICON;
if CheckBox8.Checked then LuFlags := LuFlags or SHGFI_ADDOVERLAYS;
// 指定したフラグでファイルのアイコンを取得
SHGetFileInfo(PChar(LFileName), 0 , LSHFileInfo, SizeOf(LSHFileInfo), LuFlags);
LIcon.Handle := LSHFileInfo.hIcon;
// 取得したアイコンを描画
Image1.Canvas.Draw(0 , 0 , LIcon);
finally
FreeAndNil(LIcon);
end ;
end ;
end .
08_ ファイルのアイコンをシステムのイメージリストから取得
上のサンプルコードでは 48 x 48 等の大きなサイズのアイコンは取得できません.
48 x 48 と 256 x 256 サイズのアイコンを取得するには,SHGetFileInfo 関数で直接アイコンのハンドルを取得するのではなく,アイコンのインデックスを取得します.
指定サイズのアイコンのシステムイメージのリストを取得して,そのリストから目的のインデックスのアイコンを取得します.システムイメージのリストの取得には SHGetImageList 関数を使用します.
図25
設計時の画面
本サンプルには,アイコン保存用のコードは実装していない
図26
48 x 48 サイズのアイコン
図27
256 x 256 サイズのアイコン
Windows XP 時代までのアプリには,256 x 256 サイズのアイコンがない場合がある
リスト10
ファイルのアイコンをシステムのイメージリストから取得
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, CommCtrl;
type
TForm1 = class (TForm)
OpenDialog1: TOpenDialog;
Image1: TImage;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
Button1: TButton;
RadioGroup1: TRadioGroup;
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
function GetSystemImageList(ASHILValue: Cardinal): HIMAGELIST;
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
ShellApi;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
OpenDialog1.InitialDir := ExpandFileName('..\' );
Edit1.Text := ExtractFilePath(Application.ExeName) + 'Project1.exe' ;
Edit1.AutoSelect := False;
RadioGroup1.ItemIndex := 3 ;
end ;
// =============================================================================
// ダイアログを表示してファイルを選択
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
OpenDialog1.OptionsEx := [];
OpenDialog1.FileName := '' ;
if not OpenDialog1.Execute then exit;
OpenDialog1.InitialDir := ExtractFileDir(OpenDialog1.FileName);
Edit1.Text := OpenDialog1.FileName;
Button1Click(nil );
end ;
// =============================================================================
// ファイルのアイコンを取得して描画
// このアイコンはエクスプローラで描画されているアイコン
//
// TSHFileInfo,SHGetFileInfoの使用にはusesにShellAPIが必要
// HIMAGELIST,ImageList_GetIconの使用にはusesにCommCtrlが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFileName : string ;
LSHFileInfo : TSHFileInfo;
LIconIndex : Integer;
LhIcon : TIcon;
LSHILValue : Cardinal;
hImgList : HIMAGELIST;
begin
LFileName := Edit1.Text;
Image1.Picture.Assign(nil );
LhIcon := TIcon.Create;
try
// 指定ファイルのアイコンのインデックスを取得
SHGetFileInfo(PChar(LFileName),
0 ,
LSHFileInfo,
Sizeof(TSHFileInfo),
SHGFI_SYSICONINDEX);
LIconIndex := LSHFileInfo.iIcon;
// アイコンのサイズの指定
LSHILValue := 0 ;
if RadioGroup1.ItemIndex = 0 then LSHILValue := SHIL_SMALL;
if RadioGroup1.ItemIndex = 1 then LSHILValue := SHIL_SYSSMALL;
if RadioGroup1.ItemIndex = 2 then LSHILValue := SHIL_LARGE;
if RadioGroup1.ItemIndex = 3 then LSHILValue := SHIL_EXTRALARGE;
if RadioGroup1.ItemIndex = 4 then LSHILValue := SHIL_JUMBO;
// 指定サイズのアイコンを格納したシステムのイメージリストを取得
hImgList := GetSystemImageList(LSHILValue);
// イメージリストから指定インデックスのアイコンのハンドルを取得
LhIcon.Handle := ImageList_GetIcon(hImgList, LIconIndex, ILD_NORMAL);
// 取得したアイコンを描画
Image1.Canvas.Draw(0 , 0 , LhIcon);
finally
LhIcon.Free;
end ;
end ;
// -----------------------------------------------------------------------------
// SHGetImageList関数を使用してシステムのイメージリストを取得
// 取得するアイコンのサイズを引数で指定する
// 定数名はShellAPI.pasに定義されている
//
// Delphi XEにはSHGetImageList関数が実装されているが,32ビットのEXEを64ビット
// Windowsで実行すると,「特権命令」違反が発生する
// 本コードのようにLoadLivraryでDLLをロードし関数のアドレスを取得して実行する
// と例外が発生しない
// 64ビットのEXEで実行すれば「特恵命令」は発生しない
// -----------------------------------------------------------------------------
function TForm1.GetSystemImageList(ASHILValue: Cardinal): HIMAGELIST;
type
TSHGetImageList = function (iImageList: integer;
const riid: TGUID;
var ppv: Pointer): HRESULT; stdcall ;
const
IID_IImageList: TGUID= '{46EB5926-582E-4017-9FDF-E8998DAA0950}' ;
var
LDllHandle : THandle;
LSHGetImageList : TSHGetImageList;
begin
Result := 0 ;
LDllHandle := LoadLibrary('Shell32.dll' );
if LDllHandle <> 0 then begin
try
LSHGetImageList := GetProcAddress(LDllHandle, 'SHGetImageList' );
if @LSHGetImageList <> nil then begin
LSHGetImageList(ASHILValue, IID_IImageList, Pointer(Result));
end ;
finally
FreeLibrary(LDllHandle);
end ;
end ;
end ;
end .
09_ ファイルのアイコンを ExtractAssociatedIcon 関数 で取得
ExtractAssociatedIcon 関数または ExtractAssociatedIconEx 関数を使用してファイルのアイコンを取得するサンプルです.システムの既定サイズである 32 x 32 サイズのアイコンを取得します.
下図はエクセルのブックファイルの例ですが,ExtractAssociatedIconEx 関数で取得したアイコンは,エクセルのプログラムのアイコンになっています.ExtractAssociatedIconEx 関数ではアイコンが取得できないファイルもあるようです
図28
ExtractAssociatedIcon 関数での取得結果
図29
ExtractAssociatedIconEx 関数での取得結果
リスト11
ファイルのアイコンを ExtractAssociatedIcon 関数 で取得
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, Spin;
type
TForm1 = class (TForm)
OpenDialog1: TOpenDialog;
Image1: TImage;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
Button1: TButton;
Label1: TLabel;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
ShellApi;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := '' ;
Edit1.AutoSelect := False;
end ;
// =============================================================================
// ダイアログを表示してファイルを選択
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
// .lnkファイルの場合,リンク先でなく.lnkファイルそのものを取得するようにする
// TOpenDialogは.lnkの場合,デフォルトではリンク先のファイル名を取得する
OpenDialog1.Options := OpenDialog1.Options + [ofNoDereferenceLinks];
OpenDialog1.OptionsEx := [];
OpenDialog1.FileName := '' ;
if not OpenDialog1.Execute then exit;
Edit1.Text := OpenDialog1.FileName;
Button1Click(nil );
end ;
// =============================================================================
// ファイルのアイコンをExtractAssociatedIcon関数 で取得
//
// usesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFileName : String ;
LpiIcon : Word;
LIcon : TIcon;
begin
if Edit1.Text = '' then exit;
LFileName := Edit1.Text;
Image1.Picture.Assign(nil );
LIcon := TIcon.Create;
try
LpiIcon := 0 ;
LIcon.Handle := ExtractAssociatedIcon(HInstance, PChar(LFileName), LpiIcon);
// 取得したアイコンを描画
Image1.Canvas.Draw(0 , 0 , LIcon);
Label1.Caption := 'piIcon : ' + IntToStr(LpiIcon);
finally
FreeAndNil(LIcon);
end ;
end ;
// =============================================================================
// ファイルのアイコンをExtractAssociatedIconEx関数 で取得
// DLLの場合,存在しないインデックスを指定すると例外が発生することがある
//
// usesにShellAPIが必要
// =============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LFileName : String ;
LpiIconIndex : Word;
LpiIconID : Word;
LIcon : TIcon;
begin
if Edit1.Text = '' then exit;
LFileName := Edit1.Text;
Label1.Caption := '' ;
Image1.Picture.Assign(nil );
LIcon := TIcon.Create;
try
LpiIconIndex := 0 ;
LpiIconID := 0 ;
LIcon.Handle := ExtractAssociatedIconEx(HInstance,
PChar(LFileName),
LpiIconIndex,
LpiIconID);
// 取得したアイコンを描画
Image1.Canvas.Draw(0 , 0 , LIcon);
Label1.Caption := 'piIconIndex : ' + IntToStr(LpiIconIndex) + sLineBreak
+ 'plIconID : ' + IntToStr(LpiIconID);
finally
FreeAndNil(LIcon);
end ;
end ;
end .
10_ EXE または DLL 内のアイコンを列挙 - EnumResourceNames
ここまでのサンプルでは,アイコンを取得する専用の関数を使用しています.アイコンは EXE や DLL にリソースとして埋め込まれています.本サンプルは,それらのアイコンをリソースから直接取り出して保存するサンプルです.
下図は「リソースエディタ」と呼ばれているツールを使用して,EXE 内のリソースを表示した結果です.ツリー構造になっています.これまでの関数も,本サンプルでも,この中の 'Icon Group' というディレクトリ内のアイコンを取得します.'Icon Group' 等はリソースの型と呼ばれています.
このディレクトリの階層下には複数のアイコンが格納されている場合があります.'Icon Group' は「アプリケーションアイコン」と呼ばれているものです.下図のツリー構造にあるその他のリソースは,アプリケーションが内部で使用する画像や文字列等が格納されています.
Delphi で作成した EXE には,'MAINICON' という名前でアイコンが格納されています.EXECEL.EXE には数字の名前が複数あり,それらにもアイコンが格納されています.これらの名前はリソース ID (リソース識別子) と呼ばれています.
各々のリソース ID には通常複数のアイコンがあります.これらをグループアイコンと言います.本サンプルは,グルーブアイコンを取り出してマルチアイコンファイルとして保存します.
[備考 1]
下図で使用している Resource Hacker と XN Resource Editor というツールでは GUI でリソースの削除,追加,編集,保存等の操作ができます.
Resource Hacker も XN Resource Editor も Delphi で作られています.
[備考 2]
EXE または DLL のリソース情報は PE ヘッダの .rsrc セッションにあります.アイコンではありませんが,以下の記事にリソースを操作するサンプルがあります.
[備考 3]
後述の TKIcon を使用すると短いコーディングでアイコンの列挙が可能です.
図30
Delphi で作成した EXE
MAINICON にアプリケーションアイコンがある
これは Resource Hacker によるリソース表示
Resource Hacker は Delphi 製
図31
EXCEL.EXE の場合
識別子 257 にアプリケーションアイコンがある
これは Resource Hacker によるリソース表示
図32
XN Resource Editor で表示
画像の編集機能付き
XN Resource Editor は Delphi 製
本サンプルは icon Group だけのアイコンを取り出すのが目的です.したがって,リソース内のツリー構造を直接調べることはしません.指定した型のリソースを列挙する EnumResourceNames 関数を使用して Icon Group のアイコンを列挙していきます.
コーディングの考え方は,窓プログラミングに説明記事があります.ネタキリラボに具体的な Delphi のコードが掲載されていましたので,それを利用しています.変更した内容を添付のソースコードの先頭部に書いておきました.サンプルの動作は,
スピンエディトで取得するリソース ID (識別子) の値を指定する
[MMAINICON] をチェックすると,リソース ID が MAINICON のアイコンだけを取得する
[All Icon Group] をチェックすると上記の設定に関係なく全てのアイコンを取得する
[Save Bitmap ICON (256 x 256] をチェックすると,内部の PNG 形式のアイコンをビットマップ形式に変換して保存できる
[Button1] をクリックすると実際に取得してディスクに保存する
本サンプルの EXE があるフォルダ内に [ICONS] というフォルダを作成して保存する
となっています.
下図は本サンプルの設計時の画面と実行時の設定の例です.
図33
設計時の画面
図34
実行時の設定例
Shell32.dll 内の 15 番目のアイコンだけを取得して保存する例
設定が 16 となっているが,Shell32.dll 内の 15 番目のリソース ID が 16 であるため
次の図は [All Icon Group] をチェックして,imageres.dll 内のすべてのアイコンを取得して保存した結果です.筆者の環境では 219 個ありました.これらのほんどがマルチアイコンです.
図35
imageres.dll 内の全てのアイコンを取得して保存
imageres.dll には 256 x 256 サイズのアイコンが多く格納されている
リスト12
EXE または DLL 内のアイコンを EnumResourceNames 関数で列挙して保存
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, Spin;
type
TForm1 = class (TForm)
Button1: TButton;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
OpenDialog1: TOpenDialog;
CheckBox1: TCheckBox;
SpinEdit1: TSpinEdit;
CheckBox2: TCheckBox;
CheckBox3: TCheckBox;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
function EnumResNameProc(hModule: HWND; lpszType: LPCTSTR;
lpszName: PChar; lParam: PString): LongBool; stdcall ;
var
Form1: TForm1;
implementation
uses
pngimage;
{$R *.dfm}
type
// リソース内のグループアイコン用のレコード
PIconGroupDirEntry = ^TIconGroupDirEntry;
TIconGroupDirEntry = packed record
bWidth : Byte;
bHeight : Byte;
bcolorCount : Byte;
bReserved : Byte;
bPlanes : Word;
wBitCount : Word;
dwImageSize : DWORD;
dwId : Word;
end ;
TIconGroup = packed record
idReserved : Word;
idType : Word;
idCount : Word;
end ;
// 保存アイコンアイコン)用のレコード
PIconDirEntry = ^TIconDirEntry;
TIconDirEntry = packed record
bWidth : Byte;
bHeight : Byte;
bColorCount : Byte;
bReserved : Byte;
wPlanes : Word;
wBitCount : Word;
dwBytesInRes : DWORD;
dwImageOffset : DWORD;
end ;
// アイコンのヘッダ部
// リソース内と保存アイコン共用
PIconHeader = ^TIconHeader;
TIconHeader = packed record
idReserved : Word;
idtype : Word;
idCount : Word;
end ;
PIconImage = ^TIconImage;
TIconImage = packed record
iconHeader : TBitmapInfoHeader;
icColors : RGBQUAD;
icXOR : Byte;
icAND : Byte;
end ;
TIcoGroupItemArray = array of TIconGroupDirEntry;
TIconDirEntryArray = array of TIconDirEntry;
// -----------------------------------------------------------------------------
// 指定IDのアイコンのデータを取得して引数の値にセット
// アイコンのデータが格納されているデータのポインタを取得
// ヘッダ部に続いてアイコンのデータを取得する時に使用する値
//
// グループ内のアイコンの数だけこのルーチンを実行する
// つまり,1つづつアイコンを取り出してマルチアイコンにしている
// したがって,グループの(サイズと色数の違う)アイコンを個別保存することも可能
// ただし,PNG形式の扱いに注意が必要
// -----------------------------------------------------------------------------
function StoreIconData(hModule: THandle; ID: Word; var pMem: PByte): Boolean;
var
LhIcon : HRSRC;
LhLoadIcon : HRSRC;
LpLoadIcon : PByte;
begin
LpLoadIcon := nil ;
// リソースを探してロードする
LhIcon := FindResource(hModule, MakeIntResource(ID), RT_ICON);
if LhIcon <> 0 then begin
LhLoadIcon := LoadResource(hModule, LhIcon);
if LhLoadIcon <> 0 then begin
// リソースのロックしてリソースのポインタを取得
LpLoadIcon := LockResource(LhLoadIcon);
pMem := LpLoadIcon;
end ;
end ;
Result := LpLoadIcon <> nil ;
end ;
// -----------------------------------------------------------------------------
// EnumResourceNamesのコールバック関数
// このコールバック関数内でリソースのグループアイコンを取得
// アイコン情報の構造体に各メンバの値をセット
// 保存先のファイルストリームにヘッダ情報とアイコンデータを書き込む
//
// usesにpngimageが必要
// -----------------------------------------------------------------------------
function EnumResNameProc(hModule: HWND; lpszType: LPCTSTR; lpszName: LPTSTR;
lParam: PString): LongBool;
var
LpResAddr : PByte;
LpResDataPoint : PByte;
LpIconHeader : PIconHeader;
LIcoHeader : TIconHeader;
LGroupEntryArray : TIcoGroupItemArray;
LIcoEntryArray : TIconDirEntryArray;
LhGIcon : HWND;
LhLoadGIcon : HWND;
LHeaderSize : Cardinal;
LIcoCount : Cardinal;
LSaveFileName : string ;
LFileStream : TFileStream;
LpIconData : PByte;
LResStrArg : string ;
LResIDStr : string ;
LIconImageRec : TBytes;
LBmpHeaderSize : Cardinal;
LIsPngImag : Boolean;
LNextHeaderPos : Cardinal;
LNextImageOffset : Cardinal;
LStream : TBytesStream;
LPngImage : TPngImage;
LBitmap : TBitmap;
LIcon : TIcon;
LIconInfo : TIconInfo;
LImgDataSize : Cardinal;
i : Integer;
begin
Result := True;
// リソース名のチェック
// 上位WORD(2バイト)が 0ならIDは数値.それ以外は文字列のポインタ
// ここで作成した文字列は保存用のファイル名として使用する
LResStrArg := Trim(lparam^);
if HiWord(Cardinal(lpszName)) = 0 then begin
LResIDStr := IntToStr(Cardinal(lpszName));
end else begin
LResIDStr := lpszName;
end ;
if not ((LResStrArg = '-1' ) or (LResStrArg = '0' ) or (LResStrArg = '' )) then begin
if (LResStrArg <> LResIDStr) then exit;
end ;
// グループアイコン(引数のリソース識別子lpszName)のリソースを検索
LhGIcon := FindResource(hModule, lpszName, RT_GROUP_ICON);
if LhGIcon = 0 then exit;
// 見つけたリソースをロード
LhLoadGIcon := LoadResource(hModule, LhGIcon);
if LhLoadGIcon = 0 then Exit;
// リソースをロックして、メモリアドレスを取得
LpResAddr := LockResource(LhLoadGIcon);
// アイコンファイルヘッダ読み出してアイコン数を取得
LpIconHeader := PIconHeader(LpResAddr);
LIcoCount := LpIconHeader.idCount;
// 構造体のサイズを設定
SetLength(LGroupEntryArray, LIcoCount);
SetLength(LIcoEntryArray, LIcoCount);
LpResDataPoint := Pointer(Cardinal(LpIconHeader) + SizeOf(TIconHeader));
// 構造体の情報をコピー
CopyMemory(LGroupEntryArray, LpResDataPoint, SizeOf(TIconGroupDirEntry) * LIcoCount);
// 保存用のファイル名を生成して保存用のファイルストリームを作成
// ファイル名にはリソースのIDの値を先頭に付ける
LSaveFileName := ExtractFilePath(Application.ExeName)
+ 'ICONS\ID_' + LResIDStr + '.ico' ;
ForceDirectories(ExtractFileDir(LSaveFileName));
// アイコン画像保存用ファイルストリーム作成
LFileStream := TFileStream.Create(LSaveFileName, fmCreate);
LFileStream.Position := 0 ;
LStream := TBytesStream.Create;
LPngImage := TPngImage.Create;
LBitmap := TBitmap.Create;
LIcon := TIcon.Create;
try
// アイコンファイルヘッダの情報設定とヘッダの書き込み
LIcoHeader.idReserved := 0 ;
LIcoHeader.idCount := LIcoCount;
LIcoHeader.idtype := 1 ;
LFileStream.Write (LIcoHeader, SizeOf(TIconHeader));
// アイコンファイルのヘッダサイズの計算
LHeaderSize := SizeOf(TIconHeader) + SizeOf(TIconDirEntry) * LIcoCount;
// 保存先のヘッダの書き込み開始位置の初期値と
// 保存先の画像データ書き込み開始位置の初期値
LNextHeaderPos := LFileStream.Position;
LNextImageOffset := LHeaderSize;
for i := 0 to LIcoCount - 1 do begin
if StoreIconData(hModule, LGroupEntryArray[i].dwId, LpIconData) then begin
// 画像データの先頭にあるBitmapInfoHeader構造体のサイズを調べる
// BitmapInfoHeaderがない場合はPNG形式の画像データ
SetLength(LIconImageRec, SizeOf(TIconImage));
CopyMemory(LIconImageRec, LpIconData, SizeOf(TIconImage));
LBmpHeaderSize := PIconImage(LIconImageRec)^.iconHeader.biSize;
if LBmpHeaderSize = SizeOf(TBitmapInfoHeader) then begin
LIsPngImag := False;
end else begin
LIsPngImag := True;
end ;
// 構造体の情報の書き込み
LIcoEntryArray[i].bWidth := LGroupEntryArray[i].bWidth;
LIcoEntryArray[i].bHeight := LGroupEntryArray[i].bHeight;
LIcoEntryArray[i].bColorCount := LGroupEntryArray[i].bcolorCount;
LIcoEntryArray[i].wBitCount := LGroupEntryArray[i].wBitCount;
LIcoEntryArray[i].wPlanes := LGroupEntryArray[i].bPlanes;
// PNG画像をビットマップ形式でファイル内に格納する場合
// PNG-->BMP--ICONの順で変換
if LIsPngImag and Form1.CheckBox3.Checked then begin
// PNG画像のデータをメモリストリームに読み込む
LStream.SetSize(LGroupEntryArray[i].dwImageSize);
CopyMemory(LStream.Memory, LpIconData, LGroupEntryArray[i].dwImageSize);
LPngImage.LoadFromStream(LStream);
// ビットマップに変換
// Drawすると背景が初期値の白になる
LBitmap.Assign(LPngImage);
// TIconInfo構造体の値をセットしてICONに変換
FillChar(LIconInfo, SizeOf(TIconInfo), #0 );
LIconInfo.fIcon := True;
LIconInfo.xHotspot := LBitmap.Width;
LIconInfo.yHotspot := LBitmap.Height;
LIconInfo.hbmColor := LBitmap.Handle;
LIconInfo.hbmMask := LBitmap.MaskHandle;
LIcon.Handle := CreateIconIndirect(LIconInfo);
// 変換したらメモリストリームに格納
LStream.Position := 0 ;
LIcon.SaveToStream(LStream);
// ヘッダ部を除いた画像テータ部分のみのサイズ
LImgDataSize := LStream.Size - SizeOf(TIconHeader) - SizeOf(TIconDirEntry);
// 新しいデータサイズとデータ位置までの値をヘッダ部のメンバにセット
LIcoEntryArray[i].dwBytesInRes := LImgDataSize;
LIcoEntryArray[i].dwImageOffset := LNextImageOffset;
end else begin
// PNG形式ではない場合(ビットマップ形式)
// データサイズとデータ位置までの値をヘッダ部のメンバにセット
LIcoEntryArray[i].dwBytesInRes := LGroupEntryArray[i].dwImageSize;
LIcoEntryArray[i].dwImageOffset := LNextImageOffset;
LImgDataSize := LIcoEntryArray[i].dwBytesInRes;
LStream.SetSize(LImgDataSize);
CopyMemory(LStream.Memory, LpIconData, LImgDataSize);
end ;
// ヘッダ部の書き込み位置へ移動してヘッダ部のデータを書き込む
LFileStream.Position := LNextHeaderPos;
LFileStream.Write (LIcoEntryArray[i], SizeOf(TIconDirEntry));
LNextHeaderPos := LNextHeaderPos + SizeOf(TIconDirEntry);
// ヘッダ部を書き込んだら画像データを書き込む
LFileStream.Position := LNextImageOffset;
if LIsPngImag and Form1.CheckBox3.Checked then begin
LFileStream.Write (LStream.Bytes[SizeOf(TIconHeader) + SizeOf(TIconDirEntry)],
LImgDataSize);
end else begin
LFileStream.Write (LStream.Bytes[0 ], LImgDataSize);
end ;
LNextImageOffset := LNextImageOffset + LImgDataSize;
end ;
Application.ProcessMessages;
end ;
finally
FreeAndNil(LFileStream);
FreeAndNil(LStream);
FreeAndNil(LIcon);
FreeAndNil(LBitmap);
FreeAndNil(LPngImage);
end ;
// 列挙の終了条件を判定
if (LResStrArg = '0' ) or (LResStrArg = '' ) then Result := False;
if (LResStrArg = LResIDStr) then Result := False;
end ;
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := '' ;
SpinEdit1.Value := 0 ;
OpenDialog1.InitialDir := ExpandFileName('..\' );
OpenDialog1.Filter := 'EXE, DLL ファイル |*.exe;*.dll|'
+ '実行ファイル (*.exe)|*.exe|DLL ファイル (*.dll)|*.dll' ;
end ;
// =============================================================================
// ダイアログを表示して,アイコンを取得するEXEまたはDLLファイルを選択
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
if OpenDialog1.Execute then begin
Edit1.Text := OpenDialog1.FileName;
OpenDialog1.InitialDir := ExtractFileDir(Edit1.Text));
end ;
end ;
// =============================================================================
// EXE,DLL内のアイコンのアイコンを取得して保存
// 取得するのはicon Groupのアイコンのみ
//
// 本アプリのフォルダ内にICONSというディレクトリを作成してそこに保存
// 保存先フォルダ作成と保存はコールバック関数内で行う(ディレクトリは自動作成)
//
// Button1のDefaultプロパティをTrueにしているので,[Enter]で実行可能
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFilePath : string ;
LResName : string ;
LhModule : THandle;
begin
LFilePath := Edit1.Text;
// ---------------------------------------------------------------------------
// LResName(リソース名)の指定方法
//
// '0'または空文字の場合は最初に検出したアイコンのみを取得
// '-1'にすると全てのアイコンを取得
// 名前を指定すればその名前のアイコンだけを取得
// (Delphi製のEXEのアイコンの場合は'MAINICON'だが,最初のアイコン取得でOK)
// ---------------------------------------------------------------------------
LResName := IntToStr(SpinEdit1.Value);
// チェックボックスの設定を優先
if CheckBox1.Checked then LResName := 'MAINICON' ;
if CheckBox2.Checked then LResName := '-1' ;
// EXEまたはDLLをロードする
// LoadLibrary関数ではなくLoadLibraryEx関数を使用する
LhModule := LoadLibraryEx(PChar(LFilePath),
0 ,
LOAD_LIBRARY_AS_DATAFILE or
LOAD_WITH_ALTERED_SEARCH_PATH);
try
// EnumResourceNames関数を実行してリソースを列挙する
if LhModule <> 0 then begin
EnumResourceNames(LhModule, RT_GROUP_ICON, @EnumResNameProc, LPARAM(@LResName));
end ;
finally
FreeLibrary(LhModule);
end ;
end ;
end .
前項のサンプルで保存したアイコンの多くはマルチアイコンファイルです.
本サンプルは,マルチアイコンファイルに格納されているアイコンの情報を取得して表示します.
アイコンのヘッダ部とアイコンデータ用の構造体 (レコード型) を定義して,メモリに格納したアイコンから情報を読み出します.
256 x 256 サイズのアイコンは TIcon では扱えない場合があります.上のサンプルで保存したファイル内の 256 x 256 サイズのアイコンも,メモリストリームから読み出すだけでは TIcon に読み込んで表示できません.
そこで,本サンプルでは,[Save Bitmap format (256 x 256)] をチェックすると,内部フォーマットが PNG のアイコンを,ビットマップ形式のアイコンに変換して保存できるようにしています.チェックしない場合は元の形式のままで保存します.ビットマップ形式で保存したアイコンのファイルサイズは大きくなります.
ビットマップ形式で保存したアイコンは TIcon で読み出して描画することができます.
[備考]
アイコンのテータの実体はビットマップです.BitmapInfoHeader 構造体があり,そこにビットマップの情報があります.
Windows Vista からは,128 x 128 から 256 x 256 サイズのサイズのアイコンが扱えるようになっていますが,マイクロソフトのガイドラインでは,256 x 256 サイズのアイコンは PNG フォーマットにすべきとしています.
図36
設計時の画面
情報表示用の TListBox と
TImage を配置
図37
実行例
上のサンプルで取得して保存した Shell32.dll 内の 15 番目のアイコン
アイコンファイルを読み出すとディスクに自動保存する
ファイル名はコード内で勝手に決めている
図38
内部フォーマットの違いとファイルサイズ
上がビットマップ形式
下か PNG 形式
PNG 形式のアイコンは TIcon では扱えない
リスト13
マルチアイコンファイルの情報表示と,アイコンを取り出して保存するサンプル
PNG 形式のアイコンを,TIcon で扱えるビットマップ形式のアイコンとして保存可能
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, ExtDlgs;
type
TForm1 = class (TForm)
Image1: TImage;
Panel1: TPanel;
ListBox1: TListBox;
Panel2: TPanel;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
OpenPictureDialog1: TOpenPictureDialog;
CheckBox1: TCheckBox;
procedure SpeedButton1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure ListBox1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
plExtractMultiIcon;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
ActiveControl := Edit1;
Edit1.Text := ExpandFileName('..\Multi.ico' );
OpenPictureDialog1.InitialDir := ExtractFileDir(Edit1.Text);
end ;
// =============================================================================
// マルチアイコンファイルの情報を取得とアイコンの保存
// 添付のplExtractMultiIconユニットを使用
//
// CheckBox1のOnClickイベントと共有
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
LIconFilePath : string ;
LOriginStream : TBytesStream;
LIconEntryRec : TIconDirEntryArray;
LIconCount : Integer;
LIconWidth : Integer;
LIconHeight : Integer;
LBitCount : Integer;
LText : string ;
LSaveFileDir : string ;
i : Integer;
begin
if Sender is TSpeedButton then begin
if not OpenPictureDialog1.Execute then exit;
Edit1.Text := OpenPictureDialog1.FileName;
OpenPictureDialog1.InitialDir := ExtractFileDir(OpenPictureDialog1.FileName);
end ;
if Trim(Edit1.Text) = '' then exit;
Image1.Picture.Assign(nil );
LIconFilePath := Edit1.Text;
LOriginStream := TBytesStream.Create;
try
// ストリームにアイコンファイルを読み込む
LOriginStream.LoadFromFile(LIconFilePath);
LIconCount := GetMultiIconEntryInfo(LOriginStream, LIconEntryRec);
ListBox1.Items.Clear;
ListBox1.Items.BeginUpdate;
ListBox1.Items.Add(' Entry width Height Bits ' );
ListBox1.Items.Add('----------------------------' );
for i := 0 to LIconCount - 1 do begin
LIconWidth := LIconEntryRec[i].hWidth;
LIconHeight := LIconEntryRec[i].hHeight;
if LIconWidth = 0 then LIconWidth := 256 ;
if LIconHeight = 0 then LIconHeight := 256 ;
if LIconEntryRec[i].wBitCount = 0 then begin
LBitCount := LIconEntryRec[i].bColorCount;
end else begin
LBitCount := LIconEntryRec[i].wBitCount;
end ;
LText := Format('%4d%8d%7d%7d' , [i, LIconWidth, LIconHeight, LBitCount]);
ListBox1.Items.Add(LText);
end ;
ListBox1.Items.EndUpdate;
// マルチアイコンファイルから個別にアイコンを取り出してディスクに保存
LSaveFileDir := ExpandFileName('ICONS' );
SaveMultiIconToFiles(LOriginStream, LSaveFileDir, CheckBox1.Checked);
finally
FreeAndNil(LOriginStream);
end ;
end ;
// =============================================================================
// Edit1のOnKeyDownイベント処理
// [Enter]でEit1に表示しているファイルを読み込んで情報を表示
// =============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_RETURN then begin
SpeedButton1Click(Sender);
Key := 0 ;
end ;
end ;
// =============================================================================
// ListBox1のOnClickイベント処理
// 選択したアイコンを描画
// 添付のplExtractMultiIconユニットを使用
// =============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
LIconFilePath : string ;
LStream : TBytesStream;
LPicture : TPicture;
begin
Image1.Picture.Assign(nil );
if Trim(Edit1.Text) = '' then exit;
if ListBox1.ItemIndex < 2 then exit;
LIconFilePath := Edit1.Text;
LStream := TBytesStream.Create;
LPicture := TPicture.Create;
try
// ストリームにアイコンファイルを読み込む
LStream.LoadFromFile(LIconFilePath);
GetPictureFromMultiIcon(LStream, ListBox1.ItemIndex - 2 , LPicture);
Image1.Picture.Assign(LPicture);
finally
FreeAndNil(LStream);
FreeAndNil(LPicture);
end ;
end ;
end .
下に示すコードは,上のサンプルで使用するために作成したユニットのコードです.
リスト14
マルチアイコンファイル内のアイコンを個別に全て取り出して保存する関係の関数類
copy code
unit plExtractMultiIcon;
interface
uses
Windows, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
// アイコンファイルのヘッダ部
PIconFileHeader = ^TIconFileHeader;
TIconFileHeader = packed record
idReserved : Word;
idType : Word;
idCount : Word;
end ;
// 各アイコンの情報用レコード型
PIconDirEntry = ^TIconDirEntry;
TIconDirEntry = packed record
hWidth : Byte;
hHeight : Byte;
bColorCount : Byte;
bReserved : Byte;
wPlanes : Word;
wBitCount : Word;
dwBytesInRes : DWORD;
dwImageOffset : DWORD;
end ;
PIconImage = ^TIconImage;
TIconImage = packed record
iconHeader : TBitmapInfoHeader;
icColors : RGBQUAD;
icXOR : Byte;
icAND : Byte;
end ;
PIconDirEntryArray = ^TIconDirEntryArray;
TIconDirEntryArray = array of TIconDirEntry;
function GetMultiIconEntryInfo(AFileName: string ;
var AEntryRecArray: TIconDirEntryArray): Cardinal; overload ;
function GetMultiIconEntryInfo(ASrcStream: TBytesStream;
var AEntryRecArray: TIconDirEntryArray): Cardinal; overload ;
function SaveMultiIconToFiles(AFileName: string ; ASaveDir: string ;
AIsSaveIconFormat: Boolean): Cardinal; overload ;
function SaveMultiIconToFiles(ASrcStream: TBytesStream; ASaveDir: string ;
AIsSaveIconFormat: Boolean): Cardinal; overload ;
function GetPictureFromMultiIcon(AFileName: string ; AIconNo: Cardinal;
APicture: TPicture): Boolean; overload ;
function GetPictureFromMultiIcon(ASrcStream: TBytesStream; AIconNo: Cardinal;
APicture: TPicture): Boolean; overload ;
implementation
uses
pngimage;
// -----------------------------------------------------------------------------
// マルチアイコンファイルから情報を取得
// 引数類については次の関数を参照
// -----------------------------------------------------------------------------
function GetMultiIconEntryInfo(AFileName: string ;
var AEntryRecArray: TIconDirEntryArray): Cardinal;
var
LSrcStream : TBytesStream;
begin
LSrcStream := TBytesStream.Create;
try
LSrcStream.LoadFromFile(AFileName);
Result := GetMultiIconEntryInfo(LSrcStream, AEntryRecArray);
finally
FreeAndNil(LSrcStream);
end ;
end ;
// -----------------------------------------------------------------------------
// マルチアイコンファイルのストリームから情報を取得
// 引数のエントリ構造体の配列に各アイコンのメンバ値をセットして返す
//
// ASrcStream : マルチアイコンファイルの内容を格納したストリーム
// AEntryRecArray : TIconDirEntry構造体の配列
// 戻り値は取得したアイコンの数
// -----------------------------------------------------------------------------
function GetMultiIconEntryInfo(ASrcStream: TBytesStream;
var AEntryRecArray: TIconDirEntryArray): Cardinal;
var
LIcoHeaderRec : TIconFileHeader;
begin
ASrcStream.Position := 0 ;
// ストリームからアイコンのヘッダを読み込む
ASrcStream.ReadBuffer(LIcoHeaderRec, SizeOf(LIcoHeaderRec));
Result := LIcoHeaderRec.idCount;
// アイコンのエントリ情報を読み込む
SetLength(AEntryRecArray, Result);
ASrcStream.ReadBuffer(AEntryRecArray[0 ], SizeOf(TIconDirEntry) * Result);
end ;
// -----------------------------------------------------------------------------
// マルチアイコンファイル内の各アイコンを取り出してディスクに保存
// 引数類については次の関数を参照
// -----------------------------------------------------------------------------
function SaveMultiIconToFiles(AFileName: string ; ASaveDir: string ;
AIsSaveIconFormat: Boolean): Cardinal;
var
LSrcStream : TBytesStream;
begin
LSrcStream := TBytesStream.Create;
try
LSrcStream.LoadFromFile(AFileName);
Result := SaveMultiIconToFiles(LSrcStream, ASaveDir, AIsSaveIconFormat);
finally
FreeAndNil(LSrcStream);
end ;
end ;
// -----------------------------------------------------------------------------
// マルチアイコンファイルのストリーム内の各アイコンを取り出してディスクに保存
//
// AFileName : マルチアイコンファイルの内容を格納したストリーム
// ASaveDir : 保存先のディレクトリ名(内部で自動作成)
// AIsSaveIconFormat : 256x256のPNG形式のアイコンをICON形式で保存するか
// Trueならビットマップ形式のフォーマットで保存
// Falseなら元のファイル内に格納されている形式
// ビットマップ形式で保存するとファイルサイズは大きくなる
// PNG形式のアイコンはTICONでは扱えない
// 戻り値は取得したアイコンの数
//
// usesにpngimageが必要
// -----------------------------------------------------------------------------
function SaveMultiIconToFiles(ASrcStream: TBytesStream; ASaveDir: string ;
AIsSaveIconFormat: Boolean): Cardinal;
var
LIcoHeaderRec : TIconFileHeader;
LIcoDirRec : array of TIconDirEntry;
LIconCount : Integer;
LIconWidth : Integer;
LIconHeight : Integer;
LBitCount : Integer;
LSaveStream : TBytesStream;
LPngDataStream : TBytesStream;
LIcoSaveDirRec : TIconDirEntry;
LIcoImageRec : TIconImage;
LImgStartPos : Integer;
LImgSize : Integer;
LIsPngImage : Boolean;
LIcon : TIcon;
LPngImage : TPngImage;
LBitmap : TBitmap;
LIconInfo : TIconInfo;
LSaveFileName : string ;
i : Integer;
begin
Result := 0 ;
if Trim(ASaveDir) = '' then exit;
if ASrcStream = nil then exit;
if ASrcStream.Size = 0 then exit;
ASrcStream.Position := 0 ;
// ストリームからアイコンのヘッダを読み込む
ASrcStream.ReadBuffer(LIcoHeaderRec, SizeOf(LIcoHeaderRec));
LIconCount := LIcoHeaderRec.idCount;
// アイコンのエントリ情報を読み込む
SetLength(LIcoDirRec, LIconCount);
ASrcStream.ReadBuffer(LIcoDirRec[0 ], SizeOf(TIconDirEntry) * LIconCount);
// 保存先のディレクトリを作成
ForceDirectories(ASaveDir);
LSaveStream := TBytesStream.Create;
LPngDataStream := TBytesStream.Create;
LIcon := TIcon.Create;
LPngImage := TPngImage.Create;
LBitmap := TBitmap.Create;
try
for i := 0 to LIconCount - 1 do begin
// 個別のアイコンデータを格納するストリームを初期化
LSaveStream.Clear;
// アイコン数を1にしてヘッダ部の該当メンバの値を変更
LIcoHeaderRec.idCount := 1 ;
LSaveStream.WriteBuffer(LIcoHeaderRec, SizeOf(TIconFileHeader));
// エントリ情報をセットしてストリームに書き込む
LIcoSaveDirRec.hWidth := LIcoDirRec[i].hWidth;
LIcoSaveDirRec.hHeight := LIcoDirRec[i].hHeight;
LIcoSaveDirRec.wBitCount := LIcoDirRec[i].wBitCount;
LIcoSaveDirRec.bColorCount := LIcoDirRec[i].bColorCount;
LIcoSaveDirRec.dwBytesInRes := LIcoDirRec[i].dwBytesInRes;
LIcoSaveDirRec.wPlanes := LIcoDirRec[i].wPlanes;
LIcoSaveDirRec.dwImageOffset := SizeOf(TIconFileHeader)
+ SizeOf(TIconDirEntry);
LSaveStream.WriteBuffer(LIcoSaveDirRec, SizeOf(TIconDirEntry));
// TIconImage構造体のデータを取得
// PNG形式かどうかを調べるために使用
ASrcStream.Position := LIcoDirRec[i].dwImageOffset;
ASrcStream.ReadBuffer(LIcoImageRec, SizeOf(TIconImage));
LImgStartPos := LIcoDirRec[i].dwImageOffset;
LImgSize := LIcoDirRec[i].dwBytesInRes;
// 画像データを保存用のストリームに書き込む
LSaveStream.WriteBuffer(ASrcStream.Bytes[LImgStartPos], LImgSize);
// IconImageヘッダ(BitmapInfoHeaderヘッダ)のサイズを調べる
if LIcoImageRec.iconHeader.biSize = SizeOf(TBitmapInfoHeader) then begin
// ビットマップ形式のアイコン
LIsPngImage := False;
end else begin
// ビットマップヘッダの情報がない時は256x256のPNG形式のデータと判定
LIsPngImage := True;
// PNG画像データは別のストリームに格納
// PNG形式をビットマップ形式で保存する時に使用する
LPngDataStream.Clear;
LPngDataStream.WriteBuffer(ASrcStream.Bytes[LImgStartPos], LImgSize);
end ;
if LIsPngImage then begin
LPngDataStream.Position := 0 ;
LPngImage.LoadFromStream(LPngDataStream);
LIconWidth := LPngImage.Width;
LIconHeight := LPngImage.Height;
LBitCount := LPngImage.Header.BitDepth;
if AIsSaveIconFormat then begin
LPngDataStream.Position := 0 ;
LPngImage.LoadFromStream(LPngDataStream);
// PNG画像をビットマップ形式のアイコンで保存する場合
// PNG-->BMP-->ICONの順で変換する
LBitmap.Assign(LPngImage);
// TIconInfo構造体の値をセット
FillChar(LIconInfo, SizeOf(TIconInfo), #0 );
LIconInfo.fIcon := True;
LIconInfo.xHotspot := LBitmap.Width;
LIconInfo.yHotspot := LBitmap.Height;
LIconInfo.hbmColor := LBitmap.Handle;
LIconInfo.hbmMask := LBitmap.MaskHandle;
LIcon.Handle := CreateIconIndirect(LIconInfo );
// メモリストリームに格納
LIcon.SaveToStream(LSaveStream);
end ;
end else begin
LIconWidth := LIcoImageRec.iconHeader.biWidth;
LIconHeight := LIcoImageRec.iconHeader.biHeight;
LBitCount := LIcoImageRec.iconHeader.biBitCount;
end ;
// 保存ファイル名を生成して保存
LSaveFileName := IncludeTrailingPathDelimiter(ASaveDir) + IntToStr(i) + '_'
+ IntToStr(LIconWidth) + 'x' + IntToStr(LIconHeight) + '_'
+ IntToStr(LBitCount) + 'bit.ico' ;
LSaveStream.SaveToFile(LSaveFileName);
Application.ProcessMessages;
end ;
finally
FreeAndNil(LSaveStream);
FreeAndNil(LPngDataStream);
FreeAndNil(LIcon);
FreeAndNil(LPngImage);
FreeAndNil(LBitmap);
end ;
Result := LIconCount;
end ;
// -----------------------------------------------------------------------------
// マルチアイコンファイル内の指定番目のアイコン画像を取得
// 引数類については次の関数を参照
// -----------------------------------------------------------------------------
function GetPictureFromMultiIcon(AFileName: string ; AIconNo: Cardinal;
APicture: TPicture): Boolean;
var
LStream : TBytesStream;
begin
LStream := TBytesStream.Create;
try
LStream.LoadFromFile(AFileName);
Result := GetPictureFromMultiIcon(LStream, AIconNo, APicture);
finally
FreeAndNil(LStream);
end ;
end ;
// -----------------------------------------------------------------------------
// マルチアイコンファイルのストリーム内の指定番目のアイコン画像を取得
// 取得した画像は引数のTPictureオブジェクトに格納する
// PNG形式のアイコンはTPngImageのオブジェクト
// ビットマップ形式のアイコンはTIconオブジェクト
//
// AFileName : マルチアイコンファイルの内容を格納したストリーム
// AIconNo : 何番目のアイコンを取得するかの指定
// 0ベースの値(1番目のアイコン画像は0)
// APicture : TPictureオブジェクト
// ここにアイコン画像を格納して返す
// 取得した画像を保存すれば指定番目の画像の保存となる
//
// usesにpngimageが必要
// -----------------------------------------------------------------------------
function GetPictureFromMultiIcon(ASrcStream: TBytesStream; AIconNo: Cardinal;
APicture: TPicture): Boolean;
var
LIcoHeaderRec : TIconFileHeader;
LSaveEntryRec : TIconDirEntry;
LIconImageRec : TIconImage;
LSrcImgStartPos : Integer;
LSrcImageSize : Cardinal;
LSaveStream : TBytesStream;
LIsPngImage : Boolean;
LIcon : TIcon;
LPngImage : TPngImage;
begin
Result := False;
if ASrcStream = nil then exit;
if ASrcStream.Size = 0 then exit;
LSaveStream := TBytesStream.Create;
LIcon := TIcon.Create;
LPngImage := TPngImage.Create;
try
// アイコンデータを格納するストリームの書き込み位置を0に
LSaveStream.Position := 0 ;
// ストリームからアイコンのヘッダを読み込む
ASrcStream.Position := 0 ;
ASrcStream.ReadBuffer(LIcoHeaderRec, SizeOf(LIcoHeaderRec));
// アイコン数を1にしてヘッダ部の該当メンバの値を保存用ストリームに書き込む
LIcoHeaderRec.idCount := 1 ;
LSaveStream.WriteBuffer(LIcoHeaderRec, SizeOf(TIconFileHeader));
// 指定番目のアイコンのエントリ情報を読み込む
ASrcStream.Position := SizeOf(LIcoHeaderRec) + SizeOf(TIconDirEntry) * AIconNo;
ASrcStream.ReadBuffer(LSaveEntryRec, SizeOf(TIconDirEntry));
// 元の値を退避
LSrcImgStartPos := LSaveEntryRec.dwImageOffset;
LSrcImageSize := LSaveEntryRec.dwBytesInRes;
// 画像データの開始位置を保存用ストリームに書き込む
LSaveEntryRec.dwImageOffset := SizeOf(TIconFileHeader) + SizeOf(TIconDirEntry);
LSaveStream.WriteBuffer(LSaveEntryRec, SizeOf(TIconDirEntry));
// TIconImage構造体のデータを取得
// PNG形式かどうかを調べるために使用
ASrcStream.Position := LSrcImgStartPos;
ASrcStream.ReadBuffer(LIconImageRec, SizeOf(TIconImage));
// IconImageヘッダ(BitmapInfoHeaderヘッダ)のサイズを調べる
if LIconImageRec.iconHeader.biSize = SizeOf(TBitmapInfoHeader) then begin
LIsPngImage := False;
// 画像データ格納用のストリームに現在の位置から書き込む
LSaveStream.WriteBuffer(ASrcStream.Bytes[LSrcImgStartPos], LSrcImageSize);
end else begin
// ビットマップヘッダの情報がない場合はPNG形式画像と判定
LIsPngImage := True;
// 画像データ格納用のストリームに書き込む
// PNG形式の画像にはEntry部はないので書き込み対象から除外
LSaveStream.Position := 0 ;
LSaveStream.WriteBuffer(ASrcStream.Bytes[LSrcImgStartPos], LSrcImageSize);
end ;
LSaveStream.Position := 0 ;
if LIsPngImage then begin
LPngImage.LoadFromStream(LSaveStream);
APicture.Assign(LPngImage);
end else begin
LIcon.LoadFromStream(LSaveStream);
APicture.Assign(LIcon);
end ;
Result := True;
finally
FreeAndNil(LSaveStream);
FreeAndNil(LIcon);
FreeAndNil(LPngImage);
end ;
end ;
end .
12_ マルチアイコン操作用の TKIcon クラス
前項のサンプルはマルチアイコンファイルのアイコンを取り出して保存するだけです.
下のリンクのサイトにマルチアイコン操作用の TKIcon というクラスがソースコード付きで提供されています.それを使用すると前項のようなコードを作成する必要はありません.TKIcon ではリソースファイルからもアイコンが取得できます.
TKIcon を使用すると,マルチアイコンの作成や取得が簡単なコーディングで実装できます.
下図は添付のデモプロジェクトを Delphi XE(UP1) Pro でコンパイルして実行した結果です.
今回は 7_kcontrols_1.7.zip
というファイルをダウンロードしてテストしています.[source] フォルダにライブラリパスを通して,uses に KIcon を追加して使用します.TKIcon だけを使用するのであれば,コンポーネントのインストールは必要ありません.
2022年11月現在,ダウンロードのファイル名は KControls-master.zip となっています.
図39
Kicon のデモプログラム
パッケージはインストールしていない
[source] フォルダにライブラリのパスを追加
図の右側で示している以外のファイルは [kicon] 内から削除した
Demo.dpr を IDE で開いてコンパイルして実行
このデモだけで十分実用性がある
上図の TKIon に添付のデモプログラムは,マルチアイコンから各アイコンを取得したり,マルチアイコンを作成するプログラムとなっています.TKIconにはそれ以外の機能も実装されています.
例えば以下のコードはファイルのアイコンを取得しますが,上の方のサンプルでは,システム既定のサイズだけしか取得てきなかったり,取得するサイズを指定する必要がありました.TKIcon を使用するとマルチアイコンとして取得できますから,そのような手間は必要ありません.
マルチアイコンから任意のサイズ等のアイコンを取り出す方法については,TKIcon に添付のデモプログラム,あるいは後述の TKIcon の記事を参考にしてください.
リスト15
ファイルのアイコンをマルチアイコンとして取得
copy code
uses
KIcon;
{$R *.dfm}
// =============================================================================
// LKIcon.LoadFromAssocFileの使用例
// ファイルのアイコンをマルチアイコンファイルで取得して保存
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFilePath : string ;
LKIcon : TKIcon;
LSaveFilePath : string ;
begin
LFilePath := Application.ExeName;
LSaveFilePath := 'FileIcon-' + ExtractFileName(LFilePath) + '.ico' ;
LKIcon := TKIcon.Create;
try
LKIcon.LoadFromAssocFile(LFilePath);
LKIcon.SaveToFile(LSaveFilePath);
finally
FreeAndNil(LKIcon);
end ;
end ;
次のコードは拡張子に関連付けしてあるアイコンを取得します.これももちろんマルチアイコンとして取得します.
リスト16
拡張子に関連付けされているアイコンを,マルチアイコンで取得
copy code
uses
KIcon;
{$R *.dfm}
// =============================================================================
// LKIcon.LoadFromAssocExtensionの使用例
// 拡張子に関連付けされたアイコンをマルチアイコンファイルで取得して保存
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFileExt : string ;
LKIcon : TKIcon;
LSaveFilePath : string ;
begin
LFileExt := '.xls' ;
LSaveFilePath := 'FileExt-' + Copy(LFileExt, 2 , Length(LFileExt)) + '.ico' ;
LKIcon := TKIcon.Create;
try
LKIcon.LoadFromAssocExtension(LFileExt);
LKIcon.SaveToFile(LSaveFilePath);
finally
FreeAndNil(LKIcon);
end ;
end ;
EXE や DLL のリソースのアイコンを列挙して取得するサンプルを既に掲載していますが,これも次のように短いコードで実現できます.
リスト17
EXE または DLL内のリソースのアイコンを列挙して取得.マルチアイコンとしで保存
copy code
function EnumResNameProc(hModule: HWND;
lpszType: LPCTSTR;
lpszName: PChar;
lParam: PString): LongBool; stdcall ;
var
Form1: TForm1;
implementation
uses
KIcon;
{$R *.dfm}
// =============================================================================
// LKIcon.LoadFromResourceの使用例
// EXE,DLL内のアイコンを列挙して保存
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFilePath : string ;
LhModule : THandle;
begin
// システム的にパスが通っているのでディレクトリ名は省略可能
LFilePath := 'shell32.dll' ;
// EXEまたはDLLをロードする
// LoadLibrary関数ではなくLoadLibraryEx関数を使用する
LhModule := LoadLibraryEx(PChar(LFilePath),
0 ,
LOAD_LIBRARY_AS_DATAFILE or
LOAD_WITH_ALTERED_SEARCH_PATH);
try
// EnumResourceNames関数を実行してリソースを列挙する
if LhModule <> 0 then begin
ForceDirectories(ExtractFilePath(Application.ExeName) + 'ICONS' );
EnumResourceNames(LhModule, RT_GROUP_ICON, @EnumResNameProc, 0 );
end ;
finally
FreeLibrary(LhModule);
end ;
ShowMessage('END' );
end ;
// -----------------------------------------------------------------------------
// EnumResourceNamesのコールバック関数
// このコールバック関数内でリソースのアイコンを取得して保存
// -----------------------------------------------------------------------------
function EnumResNameProc(hModule: HWND; lpszType: LPCTSTR; lpszName: LPTSTR;
lParam: PString): LongBool;
var
LKIcon : TKIcon;
LResIDStr : string ;
LSaveFileName : string ;
begin
Result := True;
LKIcon := TKIcon.Create;
try
// リソース名のチェック
// 上位WORD(2バイト)が 0ならIDは数値.それ以外は文字列のポインタ
if HiWord(Cardinal(lpszName)) = 0 then begin
LResIDStr := IntToStr(Cardinal(lpszName));
LKIcon.LoadFromResource(hModule, cardinal(lpszName));
end else begin
LResIDStr := lpszName;
LKIcon.LoadFromResource(hModule, LResIDStr);
end ;
// 保存用のファイル名を生成して保存
// ファイル名にはリソースのIDの値を先頭に付ける
LSaveFileName := ExtractFilePath(Application.ExeName)
+ 'ICONS\ID_' + LResIDStr + '.ico' ;
LKIcon.SaveToFile(LSaveFileName);
finally
FreeAndNil(LKIcon);
end ;
end ;
以下のリンクの記事に TKIcon を使用したサンプルがあります.参考にしてください.
マルチアイコンは,画像があれば,前項のサンプルや次項のようなツールを使用して作成することができます.しかし,画像そのものは作成しなければなりません.
下図は,画像の作成,編集機能を実装したマルチアイコンが操作可能なそソフトの例です.最初の図は IcoFX というアイコン画像編集用ツールです.前述のサンプルを使用して取得したマルチアイコンファイルを読み込んで表示しているところです.
.ico 以外の画像形式も扱えます.また,EXE や DLL からもアイコンを取り出すこともできます.マルチアイコンも作成できます.IcoFX は現在有料となっています.
その次の図は KH IconStudio2015 というアプリです.こちらも画像編集機能を実装したアイコン画像の編集ソフトです.マルチアイコンの作成や編集もできます.アイコン,マウスカーソル,ビットマップ,PNG 画像,GIF 画像,WebP 形式の画像での保存も可能になっています.
無料の画像編集ソフトとしては,IrfanView, GIMP 等もあります.GIMP 使用をしてマルチアイコンを作成する手順を解説しているウエブページもあります.
[備考 3]
Delphi 7 等の旧い Delphi には各種の形式の画像が作成,編集可能なイメージエディタが付属していましたが,現在の Delphi にはこのようなイメージエディタは付属していません.
[備考 2]
画像が作成済みであれば,以下の [コンポーネントのアイコン作成支援プログラム] でもマルチアイコンファイルを作成できます.
図40
アイコン画像の編集ツール
IcoFX Version 1.6.3
Delphi 製
図41
KH IconStudio2015
図42
Greenfish Icon Editor Pro
フリーウェア
メニュー類の表示は各国語に対応している
Delphi 製
ソースコードも入手可能
その下の図は DEKO さんが提供している MuRIC というツールです.MuRIC は画像の編集機能はありませんが,マルチアイコンの作成やマルチアイコンから各アイコンの取り出しができます.
図43
マルチアイコンの作成と取り出し用のツール MuRIC
14_ マルチアイコンファイルの作成 - ImageMagick
ImageMagick を使用したマルチアイコンファイルの作成テストです.
ImageMagick は,UNIX 系の OS で利用されている画像処理用のコマンドラインツールですが,Windows 用も提供されています.今回は ImageMagick のサイトから以下の 32 ビット用のファイルをダウンロードして,その中にある convert.exe だけを使用しています.
インストール用のファイルをダウンロードしてインストールすれば,タイプライブラリの取り込みでコンポーネントラッパーを作成することもできます.
Version
Description
ImageMagick-7.0.3-0-portable-Q16-x86.zip
Portable Win32 static at 16 bits-per-pixel component. Just copy to your host and run (no installer, no Windows registry entries).
[備考]
タイプライブラリの取り込みを行って ImageMagick を利用する場合,32 ビットの EXE から利用する時は,ファイル名に x86 があるファイルをダウンロードしてインストールします.64 ビットの EXE から利用する時は,x64 のファイルをダウンロードしてインストールします.
また,[install ImageMagickObject OLE Control for VBscript,Visual Basic, and WSH] にチェックをしてインストールしないと,タイプライブラリは生成されません.
タイプライブラリの取り込みを行って,ActiveX として使用する場合は,実行環境にも ImageMagick をインストールする必要があります.
今回は DLL (タイプライブラリ) を使用しませんから,実際には 32 ビット用のでも 64 ビット用のでも動作します.DLL とは違い,32 ビットの EXE からも 64 ビットの EXE は実行できます.
図44
ImageMagick のインストール
[Install ImageMagickObject...]
をチェックするとタイプライブラリが作成される
下のコードがサンプルですが,マルチアイコンファイルに格納するアイコンファイルも,作成するアイコンのファイル名も決め打ちにしています.元のファイルも作成したファイルも,本サンプルの EXE がある 1 つ上の階層のフォルダに置くようにしています.
添付のアイコンのデザインは,アイコンのサイズによって違うデザインとしています.こうしておくと,アイコンを取得する関数,あるいはアイコンを表示する場面で,実際にどのサイズのアイコンを取得して表示するかを確認することができます.
このサンプルで作成したマルチアイコンファイル内のアイコンのピクセル当たりのビット数は,元のアイコンファイルに関係なく,全て 32 ビットとなります.
図45
実行時の画面
16.ico ~ 256.ico ファイルをまとめてマルチアイコンにする
Multi.ico が作成したマルチアイコンのファイル
エクスプローラでの表示が [中アイコン] (48 x 48) なので 48 x 48 のアイコンが表示されている
他のアイコンは拡大または縮小表示されている
リスト18
ImageMagick を使用してマルチアイコンファイルを作成
(ライブラリのパスではなく) パスが通った場所に ImageMagick の convert.exe が必要
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons;
type
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
ShellAPI,
ShlObj;
{$R *.dfm}
// =============================================================================
// ImageMagickを利用したマルチアイコンファイルの作成
// usesにShellAPI, ShlObjが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LCurDir : string ;
LCmdExeName : string ;
LCmdParams : string ;
begin
LCurDir := ExpandFileName('..\' );
LCmdExeName := 'convert.exe' ;
// アイコンのファイル名をコマンドのパラメータとして指定
// 最後が保存ファイル名
LCmdParams := LCurDir + '16.ico'
+ ' ' + LCurDir + '24.ico'
+ ' ' + LCurDir + '32.ico'
+ ' ' + LCurDir + '48.ico'
+ ' ' + LCurDir + '64.ico'
+ ' ' + LCurDir + '128.ico'
+ ' ' + LCurDir + '256.ico'
+ ' ' + LCurDir + 'Multi.ico' ;
// マルチアイコンファイル作成
ShellExecute(Handle, '' , PChar(LCmdExeName), PChar(LCmdParams), nil , SW_HIDE);
// 念のためアイコンの変更をシステムに通知
// 別のマルチアイコンを同じ名前で作成した保存した場合,更新されないことがある
// (筆者のWindows 7の場合)
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, nil , nil );
end ;
end .
15_ TIconEx と LoadFromFile, LoadImage 関数によるアイコン取得
Delphi 標準の TIcon は幅と高さ (width, Height プロパティの値) を変更できません.中村拓男氏が提供している TIconEx を使用するとサイズの変更が可能です.その動作確認テストです.
TIconEx は,DHGL のサポートサイトから DHGL をダウンロードすると,その中に含まれています.
図46
TIconEx の動作確認テスト
元のサイズが 24 x24 のアイコンを 256 x 256 のサイズで表示
マルチアイコンの場合は,TIcon のWidth, Height プロパティの値に応じたアイコンをロードする
ただし, 256 x 256 サイズは取得できない
マルチアイコンで 256 x 256 を指定した場合は 128 x 128 以下のアイコンが読み込まれて拡大表示される
リスト19
TIconEx の使用例
元のアイコンのサイズに関係なく,指定サイズのアイコンを作成して描画
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, ExtDlgs;
type
TForm1 = class (TForm)
Image1: TImage;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
OpenPictureDialog1: TOpenPictureDialog;
RadioGroup1: TRadioGroup;
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
IconEx;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := '' ;
Edit1.AutoSelect := False;
RadioGroup1.ItemIndex := 2 ;
OpenPictureDialog1.InitialDir := ExpandFileName('..\' );
end ;
// =============================================================================
// ダイアログを表示してファイルを選択し,アイコンを描画
// TIconEx(中村拓男氏作)を使用する例
//
// [添付のマルチアイコンファイルの場合の動作]
// 256サイズを指定しても128サイズのアイコンが読み込まれる
//
// usesにIcoExが必要
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
LFileName : string ;
LIcon : TIconEx;
LIconSize : Integer;
begin
if Sender is TSpeedButton then begin
OpenPictureDialog1.OptionsEx := [];
OpenPictureDialog1.FileName := '' ;
if not OpenPictureDialog1.Execute then exit;
Edit1.Text := OpenPictureDialog1.FileName;
OpenPictureDialog1.InitialDir := ExtractFileDir(Edit1.Text);
end ;
if Edit1.Text = '' then exit;
LFileName := Edit1.Text;
Image1.Picture.Assign(nil );
LIcon := TIconEx.Create;
try
LIconSize := 32 ;
if RadioGroup1.ItemIndex = 0 then LIconSize := 16 ;
if RadioGroup1.ItemIndex = 1 then LIconSize := 24 ;
if RadioGroup1.ItemIndex = 2 then LIconSize := 32 ;
if RadioGroup1.ItemIndex = 3 then LIconSize := 48 ;
if RadioGroup1.ItemIndex = 4 then LIconSize := 64 ;
if RadioGroup1.ItemIndex = 5 then LIconSize := 128 ;
if RadioGroup1.ItemIndex = 6 then LIconSize := 256 ;
// TIconExではサイズの指定が可能
// そのサイズに最も近いアイコンを取得できる
// 該当サイズがない場合は拡大または縮小画像となる
LIcon.Width := LIconSize;
LIcon.Height := LIconSize;
LIcon.LoadFromFile(LFileName);
// 取得したアイコンを描画
Image1.Canvas.Draw(0 , 0 , LIcon);
finally
FreeAndNil(LIcon);
end ;
end ;
// =============================================================================
// Edit1のOnKeyDownイベント処理
// [Enter]でEdit1のファイルを読み込んで描画
// =============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_RETURN then begin
SpeedButton1Click(nil );
Key := 0 ;
end ;
end ;
end .
LoadImage 関数を使用すると,取得サイズを指定できます.TIconEx のように拡大または縮小したアイコンの画像が描画できます.
下図は,上と同じ,元のサイズが 24 x 24 のアイコンを,256 x 256 のサイズのアイコンとして取得した例です.
[備考]
LoadImage 関数では,EXE, DLL のファイルを指定してアイコンを取り出すことはできません.
EXE や DLL からアイコンを取得するには,リソースのモジュールハンドルが必要です.
図47
LoadImage 関数でアイコンを読み込んで描画
関数の引数の横と高さの値を共に 0 にすると元のサイズで取得できる
ただし,マルチアイコンの場合は,最初のアイコンを取得するので,そのアイコンのサイズとなる
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LSrcFileName : string ;
LIconSize : Integer;
LhIcon : HICON;
LIcon : TIcon;
begin
Image1.Picture.Assign(nil );
LSrcFileName := 'D:\000\24.ico' ;
LIconSize := 256 ;
// アイコンを取り出す
LhIcon := LoadImage(0 ,
PChar(LSrcFileName),
IMAGE_ICON,
LIconSize,
LIconSize,
LR_LOADFROMFILE);
if LhIcon <> 0 then begin
LIcon := TIcon.Create;
try
LIcon.Handle := LhIcon;
Image1.Canvas.Draw(0 , 0 , LIcon);
finally
FreeAndNil(LIcon);
end ;
end ;
end ;
16_ TKIcon と LoadFromFile 関数によるアイコン取得
前述の TKIcon を使用すると 256 x 256 のアイコンも取得できます.PNG 形式のアイコンも取得できます.前項の TIconEx のサンプルと同じような動作をするサンプルです.
マルチアイコンの場合は,指定したサイズのアイコンを表示します.指定サイズのアイコンがない場合は近いサイズのアイコンを取得して表示します.シングルアイコンの場合は,指定サイズの大きさにリサイズして表示します.
前項の
LoadFromFile, LoadImage 関数による両方の機能を持つサンプルと言えます.
図48
TKIcon によるアイコンの取得と表示
TIconEx では取得できない 256 x 256 のアイコンも取得可能
PNG 形式のアイコンも取得可能
シングルアイコンの場合はリサイズして表示
リスト21
TKIcon と LoadFromFile 関数によるアイコンの取得
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, ExtDlgs;
type
TForm1 = class (TForm)
Image1: TImage;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
OpenPictureDialog1: TOpenPictureDialog;
RadioGroup1: TRadioGroup;
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses
Kicon;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := '' ;
Edit1.AutoSelect := False;
RadioGroup1.ItemIndex := 2 ;
OpenPictureDialog1.InitialDir := ExpandFileName('..\' );
end ;
// =============================================================================
// ダイアログを表示してファイルを選択し,アイコンを描画
// TKIconを使用する例
//
// usesにKIcoが必要
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
LFileName : string ;
LIcon : TKIcon;
LIconSize : Integer;
LDimenstion : TKIconDimension;
begin
if Sender is TSpeedButton then begin
OpenPictureDialog1.OptionsEx := [];
OpenPictureDialog1.FileName := '' ;
if not OpenPictureDialog1.Execute then exit;
Edit1.Text := OpenPictureDialog1.FileName;
OpenPictureDialog1.InitialDir := ExtractFileDir(Edit1.Text);
end ;
if Edit1.Text = '' then exit;
LFileName := Edit1.Text;
Image1.Picture.Assign(nil );
LIcon := TKIcon.Create;
try
LIconSize := 32 ;
if RadioGroup1.ItemIndex = 0 then LIconSize := 16 ;
if RadioGroup1.ItemIndex = 1 then LIconSize := 24 ;
if RadioGroup1.ItemIndex = 2 then LIconSize := 32 ;
if RadioGroup1.ItemIndex = 3 then LIconSize := 48 ;
if RadioGroup1.ItemIndex = 4 then LIconSize := 64 ;
if RadioGroup1.ItemIndex = 5 then LIconSize := 128 ;
if RadioGroup1.ItemIndex = 6 then LIconSize := 256 ;
// TIconExではサイズの指定が可能
// そのサイズに最も近いアイコンを取得できる
// 該当サイズがない場合は拡大または縮小画像となる
LIcon.LoadFromFile(LFileName);
if LIcon.IconCount = 1 then begin
// シングルアイコンの場合はリサイズ描画
Image1.Canvas.StretchDraw(Rect(0 , 0 , LIconSize, LIconSize), LIcon);
end else begin
// マルチアイコンの場合
// 取得サイズを設定して取得
// ない場合は大きいサイズを取得することがある
LIcon.DisplayAll := False;
LIcon.OptimalIcon := True;
LDimenstion.Width := LIconSize;
LDimenstion.Height := LIconSize;
LIcon.RequestedSize := LDimenstion;
// 取得したアイコンを描画
Image1.Canvas.Draw(0 , 0 , LIcon);
end ;
finally
FreeAndNil(LIcon);
end ;
end ;
// =============================================================================
// Edit1のOnKeyDownイベント処理
// [Enter]でEdit1のファイルを読み込んで描画
// =============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_RETURN then begin
SpeedButton1Click(nil );
Key := 0 ;
end ;
end ;
end .
TKIcon は TPicture と統合化,一体化できます.おそらくアイコン画像の表示処理の簡便さが目的と思われます.
TKIcon に添付のデモプログラムにもコードがありますが,その部分だけを再掲しておきます.
アイコン画像を TPicture で扱うには RegisterKIcon を実行しておきます.TPicture にアイコン画像ファイルを読み込むと,TPicture.Graphic で TKIcon にアクセス可能になります.ただし,TKIcon でキャストする必要があります.TKIcon.CurrentIndex でアイコン画像のインデックスを指定すれば自動的に該当インデックスにアクセスできます.
本サンプルでは,読み込んだアイコン画像を,フォーム左側の TSpeedButton をクリックするとインデックスの値をインクリメントして,そのインデックスのアイコンを TImaage に描画します.
図49
TKIcon と TPicture の統合化テスト
左のボタンをクリックすると,読み込んだアイコンを順番に TImage に表示する
リスト22
アイコン画像ファイルを TImage.Picture に読み込んで表示
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Buttons, ExtDlgs, KIcon;
type
TForm1 = class (TForm)
Image1: TImage;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
OpenPictureDialog1: TOpenPictureDialog;
Label1: TLabel;
SpeedButton2: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure SpeedButton2Click(Sender: TObject);
private
{ Private 宣言 }
FKIcon : TKIcon;
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := '' ;
Edit1.AutoSelect := False;
OpenPictureDialog1.InitialDir := ExpandFileName('..\..\' );
// TPictureとTKIconを統合
RegisterKIcon;
end ;
// =============================================================================
// ダイアログを表示してファイルを選択
// アイコンファイルをTImage.Pictureにロードする
// =============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
LFileName : string ;
begin
if Sender is TSpeedButton then begin
OpenPictureDialog1.OptionsEx := [];
OpenPictureDialog1.FileName := '' ;
if not OpenPictureDialog1.Execute then exit;
Edit1.Text := OpenPictureDialog1.FileName;
OpenPictureDialog1.InitialDir := ExtractFileDir(Edit1.Text);
LFileName := OpenPictureDialog1.FileName;
end else begin
if Edit1.Text = '' then exit;
LFileName := Edit1.Text;
end ;
// TImage.Picture.GraphicをTKIconとする
// TKIcon.Createは不要.解放処理も不要
Image1.Picture.LoadFromFile(LFileName);
FKIcon := TKIcon(Image1.Picture.Graphic);
FKIcon.DisplayAll := False;
Edit1.Text := Edit1.Text;
Label1.Caption := 'アイコン数 : ' + IntToStr(FKIcon.IconCount);
end ;
{$WRITEABLECONST ON}
// =============================================================================
// アイコンのインデックスを変更してそのアイコンを表示
// =============================================================================
procedure TForm1.SpeedButton2Click(Sender: TObject);
const
LIconIndex : Integer = -1 ;
begin
if not Assigned(FKIcon) then exit;
// OptimalIconをFlaseにする
FKIcon.OptimalIcon := False;
inc(LIconIndex);
if LIconIndex >= FKIcon.IconCount then begin
LIconIndex := 0 ;
end ;
// インデックスを変更すると自動でTImageにそのインデックスのアイコンを表示する
FKIcon.CurrentIndex := LIconIndex;
end ;
{$WRITEABLECONST OFF}
// =============================================================================
// Edit1のOnKeyDownイベント処理
// [Enter]でEdit1のファイルを読み込んで描画
// =============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_RETURN then begin
SpeedButton1Click(nil );
Key := 0 ;
end ;
end ;
end .
現在のインデックスのアイコンだけを単独で保存するには,そのアイコンをビットマップとして取得します.そのビットマップをアイコンにして保存します.アイコンのデータはビットマップまたは PNG 形式の画像として格納されています.
PNG 形式の画像を取り出すには CopyToPng メソッドを使用します.PNG 形式は IconData メソッドで取得した TKIconData レコード型の IsPNG
メンバの値で判定できます.以下のコードでは PNG 形式のアイコンもビットマップで取得しています.アイコン画像に変換してファイルに保存するので,どちらで取得しても結果は同じです.
ビットマップは 32 ビットでの取得になりますので,以下のコードでは TNkDIB の機能を利用して減色処理をするようにしています.
[備考]
下のコード内の LKIconData は TKIconData レコード型の変数です.このレコード型のメンバから各々のアイコンの幅と高さ,ビット深さ,PNG かビットマップか等の値が取得できます.
したがって,これらの値を調べることによって,含まれているアイコンの内,任意のサイズのアイコンだけを取り出す等の操作が可能です.
リスト23
マルチアイコンのアイコンを単独で保存する例
copy code
// 現在のインデックスのアイコンを単独アイコンファイルとして保存する例
// TNkDIBは24ビットに変換して処理していることに注意
// アイコンはビットマップとして格納されている.それを取り出して処理
LKIcon := TKIcon.Create;
LBitmap := TBitmap.Create;
LNkDIB := TNkDIB.Create;
try
FKIcon.CopyToBitmap(LIconIndex, LBitmap);
LKIconData := FKIcon.IconData[LIconIndex];
// 減色処理
LNkDIB.Assign(LBitmap);
if LKIconData.Bpp = 4 then begin
LNkDIB.PixelFormat := nkPf4Bit;
LBitmap.Assign(LNkDIB);
end else
if LKIconData.Bpp = 8 then begin
LNkDIB.PixelFormat := nkPf8Bit;
LBitmap.Assign(LNkDIB);
end ;
LKIcon.Add(MakeHandles(LBitmap.Handle, LBitmap.MaskHandle));
// 保存
LKIcon.SaveToFile(IntToStr(LIconIndex) + '.ico' );
finally
FreeAndNil(LKIcon);
FreeAndNil(LBitmap);
FreeAndNil(LNkDIB);
end ;
Delphi には,アプリで利用可能なアイコンが付属しています.以下の場所にあります.Delphi のバージョンが上がる度にアイコンの数が増えています.Delphi 7 以前に付属していた画像類は数も少なく,今となってはデザイン的にも不満があるかも知れません.
実際に添付されているのは .icon 形式の画像ではなく,.bmp, .jpg, .png 等の形式の画像です.一般的な意味での「アイコン」として使用可能となっています.
本家の glyFX では多くのアイコンが提供されています.
C:\Program Files (x86)\Common Files\Borland Shared\Images //Delphi 7 以前
C:\Program Files (x86)\Common Files\CodeGear Shared\Images\GlyFX //Delphi XE
C:\Program Files (x86)\Embarcadero\RAD Studio\12.0\Images\GlyFX\glyFX.zip //Delphi XE5
C:\Program Files (x86)\Embarcadero\Studio\15.0\Images\GlyFX\glyFX.zip //Delphi XE7