none
icoファイルをLoadImageで読み込む場合の色数とサイズについて RRS feed

  • 質問

  • VS2013でMFCダイアログベースの開発を行っています。

    複数のサイズが収まったicoファイルをLoadImageで読み込む場合について、2点教えて頂けないでしょうか。

    ①読み込んだアイコンの色数が32bitになる。
    4bitのビットマップが収まっているicoファイルをLoadImageで読み込むと色数が32bitになってしまいます。
    ビットマップの色数(4bit)で読み込むことは可能でしょうか。

    ●読み込み方法

    	HICON hIcon = (HICON)LoadImage(0,
    		"test.ico",
    		IMAGE_ICON,
    		32,
    		32,
    		LR_LOADFROMFILE);
    
    	ICONINFO IconInfo;
    	GetIconInfo(hIcon, &IconInfo);
    
    	BITMAP  bmp;
    	GetObject(IconInfo.hbmColor, sizeof(BITMAP), &bmp);
    
    	//読み込んだ結果(bmp構造体のウォッチ情報)
    	//bmType	0
    	//bmWidth	32
    	//bmHeight	32
    	//bmWidthBytes	128
    	//bmPlanes	1
    	//bmBitsPixel	32 ←★色数が32bitになっている。4bitになって欲しい
    	//bmBits	0x00000000

    ●読み込むicoファイル
     以下の2つのビットマップを収めたicoファイルです。
      16*16(4bit)のビットマップ
      32*32(4bit)のビットマップ

     読み込むicoファイルの作成方法
      VisualStudioを立ち上げ、新規作成/ファイルからアイコンファイルを選択し名前を付けて保存します。

    ●備考
     icoファイルの中身をバイナリで確認したところ、アイコンヘッダー部の色数は8bit、ビットマップ情報部の色数は4bitになっていることを確認しました。
     エクスプローラー上でicoファイルを右クリックし、プロパティの詳細から色数(ビット深さ)を確認すると32になっていました。
     この32はどこから求めているのでしょうか…。


    ②読み込むアイコン内に指定サイズのビットマップがあるか検知する
    icoファイル内にLoadImageで指定したサイズのビットマップがあることの判別は可能でしょうか。

    ①のicoファイルに対してLoadImageの第4第5引数のサイズに20を指定すると16*16のサイズのビットマップが読み込まれ20*20に拡大されてしまいます。
    指定したサイズのビットマップがない場合、読み込まない方法はありますか。


    2017年1月24日 7:16

回答

  • ICONINFO構造体の説明にある通り、hbmColorメンバは、アイコンリソースを、
    前景用ビットマップに変換した結果の「ビットマップのハンドル」です。
    これは、hbmMaskメンバ(マスク用ビットマップ)と対で使われ、背景の透過処理を行います。
    すなわち、既に表示用のビットマップ2枚に変換されていると考えられます。

    これらの変換は当該関数を呼び出した時点で自動的に作成されると想像できるので、
    表示対象となるデバイスコンテキストに最適化されると予測できます。
    その時の判定結果が32bitだったのではないでしょうか。

    最終的にデバイスコンテキストに転送するときには、対象となる物の全てがビットマップ、ないし、画素に変換されます。
    従って 表示前のオブジェクトの情報と、それがデバイスコンテキストに転送されるときのバイナリは、一般に異なっていると言えます。
    LoadImage()関数は、様々な意味において「表示するため」に最適化されていると考えられます。
    従って「内部の情報を正確に読みだす」ためにはファイルを直接ロードすべきかもしれません。

    2017年1月24日 7:58
  • アイコン編集ツールのようなものを考えているのなら、自力ロードしかありません。

    もし、そうではなく、HighDPI 対応の一環でスケーリングの影響でサイズが変わるため、それに合わせてアイコンをロードしたいというのなら、

    LoadIconWithScaleDown というAPIがあるので、こちらの利用を検討してみてください。

    こちらと、DrawIconEx を使って、スケーリング後のアイコンサイズで描画すれば正しくDPIを反映してアイコンが描画できます。


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    2017年1月24日 8:19
  • 他の方も仰っていますがLoadImageでicoファイルの中のイメージサイズと色のビット数を取得するのは難しいようです。

    https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms997538.aspx

    上記のページの中ほどに、iconファイルに含まれるイメージのサイズと色のビット数を取得できるコードが乗っていました。
    ほとんどサンプルのままですが、下記のようなコードで取得できました。

    typedef struct
    {
    	BYTE		bWidth;			// Width, in pixels, of the image
    	BYTE		bHeight;		// Height, in pixels, of the image
    	BYTE		bColorCount;	// Number of colors in image (0 if >=8bpp)
    	BYTE		bReserved;		// Reserved ( must be 0)
    	WORD		wPlanes;		// Color Planes
    	WORD		wBitCount;		// Bits per pixel
    	DWORD		dwBytesInRes;	// How many bytes in this resource?
    	DWORD		dwImageOffset;	// Where in the file is this image?
    } ICONDIRENTRY, *LPICONDIRENTRY;
    
    typedef struct
    {
    	WORD			idReserved;   // Reserved (must be 0)
    	WORD			idType;       // Resource Type (1 for icons)
    	WORD			idCount;      // How many images?
    	ICONDIRENTRY	idEntries[1]; // An entry for each image (idCount of 'em)
    } ICONDIR, *LPICONDIR;
    
    typedef struct
    {
    	BITMAPINFOHEADER	icHeader;		// DIB header
    	RGBQUAD				icColors[1];	// Color table
    	BYTE				icXOR[1];		// DIB bits for XOR mask
    	BYTE				icAND[1];		// DIB bits for AND mask
    } ICONIMAGE, *LPICONIMAGE;
    
    {
    	HANDLE hFile = CreateFile(TEXT("test.ico"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    	LPICONDIR pIconDir = (LPICONDIR)malloc(sizeof(ICONDIR));
    	DWORD dwBytesRead;
    	ReadFile(hFile, &(pIconDir->idReserved), sizeof(WORD), &dwBytesRead, NULL);
    	ReadFile(hFile, &(pIconDir->idType), sizeof(WORD), &dwBytesRead, NULL);
    	ReadFile(hFile, &(pIconDir->idCount), sizeof(WORD), &dwBytesRead, NULL);
    	LPICONDIRENTRY pIconDirEntries = (LPICONDIRENTRY)malloc(sizeof(ICONDIRENTRY) * pIconDir->idCount);
    	ReadFile(hFile, pIconDirEntries, sizeof(ICONDIRENTRY) * pIconDir->idCount, &dwBytesRead, NULL);
    	for (int i = 0; i < pIconDir->idCount; ++i)
    	{
    		LPICONIMAGE pIconImage = (LPICONIMAGE)malloc(pIconDirEntries[i].dwBytesInRes);
    		SetFilePointer(hFile, pIconDirEntries[i].dwImageOffset, NULL, FILE_BEGIN);
    		ReadFile(hFile, pIconImage, pIconDirEntries[i].dwBytesInRes, &dwBytesRead, NULL);
    		TCHAR szText[1024];
    		wsprintf(szText, TEXT("%dx%d, %dビット\n"),
    			pIconImage->icHeader.biWidth,
    			pIconImage->icHeader.biHeight >> 1, // biHeightには本体イメージとマスクを合計した高さが記録されているので半分にする
    			pIconImage->icHeader.biBitCount);
    		OutputDebugString(szText);
    		free(pIconImage);
    	}
    	free(pIconDirEntries);
    	free(pIconDir);
    	CloseHandle(hFile);
    }

    プログラムを実行すると"出力"にicoに含まれるイメージのサイズと色のビット数が出力されます。

    ※icoファイルに含まれるpngには未対応です。対応したバージョンはこちら

    2017年1月24日 10:17
  • 自動的なリサイズが行われるのは困るのなら、PNG画像でリソースに保持しておき(埋め込みなので、配布ファイル数は増えません)、実行状況に合わせて適切なものをロードするという方法はいかがでしょうか?

    また、LoadIconWithScaleDown も一度実験的でいいので使ってみてください。ユーザーが実行時に選ぶなど、不特定なアイコンを状況に合わせて選択するという形だと、単純な話ではなくなりますが、あらかじめ用意しているものなら、たぶんかなりいい感じで使えると思います(自分が実際にその方法で使っていますので)。


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    2017年1月25日 7:58

すべての返信

  • ICONINFO構造体の説明にある通り、hbmColorメンバは、アイコンリソースを、
    前景用ビットマップに変換した結果の「ビットマップのハンドル」です。
    これは、hbmMaskメンバ(マスク用ビットマップ)と対で使われ、背景の透過処理を行います。
    すなわち、既に表示用のビットマップ2枚に変換されていると考えられます。

    これらの変換は当該関数を呼び出した時点で自動的に作成されると想像できるので、
    表示対象となるデバイスコンテキストに最適化されると予測できます。
    その時の判定結果が32bitだったのではないでしょうか。

    最終的にデバイスコンテキストに転送するときには、対象となる物の全てがビットマップ、ないし、画素に変換されます。
    従って 表示前のオブジェクトの情報と、それがデバイスコンテキストに転送されるときのバイナリは、一般に異なっていると言えます。
    LoadImage()関数は、様々な意味において「表示するため」に最適化されていると考えられます。
    従って「内部の情報を正確に読みだす」ためにはファイルを直接ロードすべきかもしれません。

    2017年1月24日 7:58
  • アイコン編集ツールのようなものを考えているのなら、自力ロードしかありません。

    もし、そうではなく、HighDPI 対応の一環でスケーリングの影響でサイズが変わるため、それに合わせてアイコンをロードしたいというのなら、

    LoadIconWithScaleDown というAPIがあるので、こちらの利用を検討してみてください。

    こちらと、DrawIconEx を使って、スケーリング後のアイコンサイズで描画すれば正しくDPIを反映してアイコンが描画できます。


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    2017年1月24日 8:19
  • 他の方も仰っていますがLoadImageでicoファイルの中のイメージサイズと色のビット数を取得するのは難しいようです。

    https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms997538.aspx

    上記のページの中ほどに、iconファイルに含まれるイメージのサイズと色のビット数を取得できるコードが乗っていました。
    ほとんどサンプルのままですが、下記のようなコードで取得できました。

    typedef struct
    {
    	BYTE		bWidth;			// Width, in pixels, of the image
    	BYTE		bHeight;		// Height, in pixels, of the image
    	BYTE		bColorCount;	// Number of colors in image (0 if >=8bpp)
    	BYTE		bReserved;		// Reserved ( must be 0)
    	WORD		wPlanes;		// Color Planes
    	WORD		wBitCount;		// Bits per pixel
    	DWORD		dwBytesInRes;	// How many bytes in this resource?
    	DWORD		dwImageOffset;	// Where in the file is this image?
    } ICONDIRENTRY, *LPICONDIRENTRY;
    
    typedef struct
    {
    	WORD			idReserved;   // Reserved (must be 0)
    	WORD			idType;       // Resource Type (1 for icons)
    	WORD			idCount;      // How many images?
    	ICONDIRENTRY	idEntries[1]; // An entry for each image (idCount of 'em)
    } ICONDIR, *LPICONDIR;
    
    typedef struct
    {
    	BITMAPINFOHEADER	icHeader;		// DIB header
    	RGBQUAD				icColors[1];	// Color table
    	BYTE				icXOR[1];		// DIB bits for XOR mask
    	BYTE				icAND[1];		// DIB bits for AND mask
    } ICONIMAGE, *LPICONIMAGE;
    
    {
    	HANDLE hFile = CreateFile(TEXT("test.ico"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    	LPICONDIR pIconDir = (LPICONDIR)malloc(sizeof(ICONDIR));
    	DWORD dwBytesRead;
    	ReadFile(hFile, &(pIconDir->idReserved), sizeof(WORD), &dwBytesRead, NULL);
    	ReadFile(hFile, &(pIconDir->idType), sizeof(WORD), &dwBytesRead, NULL);
    	ReadFile(hFile, &(pIconDir->idCount), sizeof(WORD), &dwBytesRead, NULL);
    	LPICONDIRENTRY pIconDirEntries = (LPICONDIRENTRY)malloc(sizeof(ICONDIRENTRY) * pIconDir->idCount);
    	ReadFile(hFile, pIconDirEntries, sizeof(ICONDIRENTRY) * pIconDir->idCount, &dwBytesRead, NULL);
    	for (int i = 0; i < pIconDir->idCount; ++i)
    	{
    		LPICONIMAGE pIconImage = (LPICONIMAGE)malloc(pIconDirEntries[i].dwBytesInRes);
    		SetFilePointer(hFile, pIconDirEntries[i].dwImageOffset, NULL, FILE_BEGIN);
    		ReadFile(hFile, pIconImage, pIconDirEntries[i].dwBytesInRes, &dwBytesRead, NULL);
    		TCHAR szText[1024];
    		wsprintf(szText, TEXT("%dx%d, %dビット\n"),
    			pIconImage->icHeader.biWidth,
    			pIconImage->icHeader.biHeight >> 1, // biHeightには本体イメージとマスクを合計した高さが記録されているので半分にする
    			pIconImage->icHeader.biBitCount);
    		OutputDebugString(szText);
    		free(pIconImage);
    	}
    	free(pIconDirEntries);
    	free(pIconDir);
    	CloseHandle(hFile);
    }

    プログラムを実行すると"出力"にicoに含まれるイメージのサイズと色のビット数が出力されます。

    ※icoファイルに含まれるpngには未対応です。対応したバージョンはこちら

    2017年1月24日 10:17
  • 仲澤@失業者様

    ご回答ありがとうございます。
    LoadImage()ではicoファイルのバイナリと同様の情報を読み出すことができない旨を納得できました。

    • 編集済み mnbfmt 2017年1月25日 0:38
    2017年1月25日 0:17
  • とっちゃん様

    ご回答ありがとうございます。
    以前の高DPI対応での質問では大変お世話になりました。

    今回も高DPI対応の一環ではありますが、アプリケーションのアイコンとしてicoファイルを使用するのではなく、アプリケーション内で表示するビットマップ画像を管理するのにicoファイルを用いようと考えています。
    以下のようにもともとアプリケーション内で表示していた16px*16pxのビットマップと高DPI用のビットマップを一つのicoファイルに収め、ファイル数を少なくするのが目的です。

    ●A.icoファイル
    DPI100%用の16px*16pxのAビットマップ
    DPI200%用の32px*32pxのAビットマップ
    DPI400%用の64px*64pxのAビットマップ

    ●B.icoファイル
    DPI100%用の16px*16pxのBビットマップ
    DPI200%用の32px*32pxのBビットマップ
    DPI400%用の64px*64pxのBビットマップ

    そのため、ご教示頂いたLoadIconWithScaleDownはexe内に埋め込まれたアイコンリソースを読み込む形式の関数は使用できそうにありません。
    ですが、今後の高DPI対応でアプリケーションのアイコンをどうすると検討する必要があったため、LoadIconWithScaleDownやDrawIconExの関数を教えてくださり非常に助かります。

    • 編集済み mnbfmt 2017年1月25日 0:38
    2017年1月25日 0:37
  • kenjinote様
    ご回答ありがとうございます。

    サイズと色ビット数が取得できるコードありがとうございます。
    皆さまが仰るようにバイナリ情報を直接読み込むしか方法がないようなので、提示して頂いたコードとサンプルを元にアイコンファイルの読み込み処理を作成してみようと思います。
    2017年1月25日 0:47
  • 自動的なリサイズが行われるのは困るのなら、PNG画像でリソースに保持しておき(埋め込みなので、配布ファイル数は増えません)、実行状況に合わせて適切なものをロードするという方法はいかがでしょうか?

    また、LoadIconWithScaleDown も一度実験的でいいので使ってみてください。ユーザーが実行時に選ぶなど、不特定なアイコンを状況に合わせて選択するという形だと、単純な話ではなくなりますが、あらかじめ用意しているものなら、たぶんかなりいい感じで使えると思います(自分が実際にその方法で使っていますので)。


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    2017年1月25日 7:58