ホーム 主筆 その他ソフト その他情報 Syuhitu.org English

Windows関連

スクリーンセーバー作成法

半透明ウインドウの性能

bootfont.bin

キャビネット形式

ウインドウスタイルをいじる

Java製ソフトをServiceに登録する

イベントログにメッセージを出力する

コントロールパネルにアイコンを追加する

スクリプトによる拡張1

スクリプトによる拡張2

ガジェットの作成

大容量メモリ

メモリ搭載量の下限に挑む

スパースファイルにする

表示されるアイコンの種類を調べてみた

メモリマップIOとエラー処理

ファイルを作る順番と速度の関係

Cryptography API: Next Generationを使う

Windows 10のアクセントカラー

iSCSIディスクにバックアップを取る

サーバプロセスを分離して実装する

サーバプロセスを分離して実装する - F#

レジストリに大量に書き込む

Solaris関連

OpenGL

Solaris設定

ディレクトリの読み込み

主筆プラグイン開発

マルチスレッドでの開発

door

音を出す

Blade100の正しい虐め方

パッケージの作成

画像入出力

BMPファイル

ICOファイル

ANIファイル

JPEGファイル

減色アルゴリズム

減色アルゴリズムの並列化

その他アルゴリズムなど

自由軸回転

Base64

文字列操作

CPU利用率の取得

正規表現ライブラリ

メタボールを作る

メタボールを作る2

正規表現とNFA・DFA

C言語の構文解析

液晶ディスプレイを解体してみた

iSCSIの理論と実装

単一フォルダにファイルを沢山作る

USB-HUBのカスケード接続

SafeIntの性能

VHDファイルのフォーマット

USBメモリに書き込み続けてみた

アイコンファイルの形式

2004年4月3日公開

Windowsで使用されるアイコンは、「*.ICO」という拡張子を持つ、イメージファイルです。ここでは、そのアイコンのファイル形式を解説したいと思います。

実のところを言うと、VisualStudioを購入すればアイコンの作成には不自由しないし、いろいろなフリーソフトだってあるんですが。

概観

一つのICOファイルには複数のイメージが含まれています。また、各イメージには背景の透過部分を示すためのマスクが存在します。

ファイルには、以下の内容が記録されています。

ICONDIR構造体
ICONDIRENTRY構造体(複数個)
ICONIMAGE構造体(複数個)

ICONDIR構造体

ICONDIR構造体は、ファイルの先頭に存在し、下記のような形式となっています。

typedef struct{
    WORD           idReserved;   // Reserved (must be 0)
    WORD           idType;       // リソースの型 ICOの場合は1
    WORD           idCount;      // 含まれているイメージの数
    ICONDIRENTRY   idEntries[1]; // ICONDIRENTRY構造体の配列
} ICONDIR, *LPICONDIR;

この構造体には、リソースの型、イメージの数、ICONDIRENTRY構造体の配列が記録されています。

もっとも、ICONDIRENTRY構造体の配列が含まれていると言っても、可変長でありその数を事前に予測することはできません。この構造体の定義でも一つ分だけ定義されています。

構造体の末尾に配列を定義して、その「構造体の範囲」からはみ出した部分にまで配列を記録する方法という方法は、C言語ではよく行われるそうです。

確保されたメモリ領域

構造体

(はみ出す)

メンバ1 ・・・ メンバn 配列[0] 配列[1] ・・・ 配列[n]

上記のように、「sizeof( 構造体 )」以上にmallocで確保しておいて、はみ出した部分を構造体の末尾の配列の延長として用いるそうです。

しかし、個人的には好きじゃありません。

ICONDIRENTRY構造体

ICONDIRENTRY構造体は、上述の通りICONDIR構造体中に配列として含まれています。下記のような形式となっています。

typedef struct{
    BYTE        bWidth;          // イメージの幅(ピクセル数)
    BYTE        bHeight;         // イメージの高さ(ピクセル数)
    BYTE        bColorCount;     // イメージの色数(256色以上の場合は0)
    BYTE        bReserved;       // 予約(0)
    WORD        wPlanes;         // Color Planes
    WORD        wBitCount;       // 一ピクセルあたりのビット数
    DWORD       dwBytesInRes;    // イメージに使用しているバイト数
    DWORD       dwImageOffset;   // ファイル中の何バイト目から記録されているか。
} ICONDIRENTRY, *LPICONDIRENTRY;

この構造体は、ICONDIR構造体のidCountに読み込んだ数だけ記録されています。

ICONDIRENTRY構造体には、ICOファイルに含まれる、一つずつのイメージの情報が記録されています。しかし、イメージに関する詳細な情報はこの後で読み込む「ICONIMAGE構造体」に全て記録されています。そのため、ICONDIRENTRY構造体で興味があるのは「dwBytesInRes」と「dwImageOffset」だけとなります。

「dwBytesInRes」と「dwImageOffset」には、この後で読み込む「ICONIMAGE構造体」が記録されている、ファイル中の位置とバイト数を示してます。

ICONIMAGE構造体

ICONIMAGE構造体は、下記のように定義されています。しかし、事実上意味をなしていません。

typedef struct{
	BITMAPINFOHEADER   icHeader;      // DIBヘッダ
	RGBQUAD         icColors[1];   // カラーテーブル
	BYTE            icXOR[1];      // イメージ
	BYTE            icAND[1];      // マスク
} ICONIMAGE, *LPICONIMAGE;

icHeaderはBMPファイルのBITMAPINFOHEADER構造体そのものです。しかし、記録されている情報にはやや相違があります。また、BITMAPINFOHEADER構造体に引き続きカラーテーブル・イメージ・マスクが記録されていますが、この構造体の定義からは形式が全く読み取れません。

イメージの形式

とりあえずはICONIMAGE構造体として読み込んだ「事実上のバイト配列」は、以下のような形式となっています。

BITMAPINFOHEADER構造体
RGBQUAD構造体(複数個・存在しない場合もある)
イメージのバイト配列
マスクのバイト配列

BITMAPINFOHEADER構造体

BITMAPINFOHEADER構造体は以下のような形式をしています。

typedef struct tagBITMAPINFOHEADER {
  DWORD  biSize;          // この構造遺体のサイズ(バイト数)
  LONG   biWidth;	         // イメージの幅(ピクセル数)
  LONG   biHeight;        // イメージの高さ(ピクセル数、イメージ+マスク)
  WORD   biPlanes;        // 面の数(常に1)
  WORD   biBitCount;      // 一ピクセルあたりのビット数
  DWORD  biCompression;   // 未使用
  DWORD  biSizeImage;     // イメージ+マスクで使用しているバイト数
                          // (当てにならない可能性がある)
  LONG   biXPelsPerMeter; // 未使用
  LONG   biYPelsPerMeter; // 未使用
  DWORD  biClrUsed;       // 未使用(値が設定されている場合もある)
  DWORD  biClrImportant;  // 未使用
} BITMAPINFOHEADER;

記録される内容が、通常のBMPファイルと異なるのは、いくつかのメンバしか使用されないということと、イメージの高さとしてマスクイメージの高さも含んでいることが挙げられます。

イメージの高さとして、本来のイメージとマスクのイメージを合計した高さを記録しているため、実際のイメージの高さは「biHeight」の半分ということになります。

また、「biSizeImage」の値ですが、この値は通常のBMPファイルのように「横幅、高さ、ピクセルあたりのビット数、カラーテーブルの数」から算出可能な値ではありません。実はICOファイルの場合、イメージの部分とマスクの部分とでは色の解像度が異なる可能性があります。そのため、計算は非常にやっかいです。そのせいか知りませんが、当てにならないこともあるようです。

RGBQUAD構造体

BITMAPINFOHEADER構造体に続いてRGBQUAD構造体が存在します。ただし、記録されているイメージが16ビットカラー以上の場合は存在しません。(最もアイコンはだいたい16色か256色であり、だいたいはRGBQUAD構造体が存在します。)

RGBQUAD構造体の数は、BITMAPINFOHEADER構造体のbiBitCountにより示されます。

biBitCount RGBQUAD構造体の数
1 2
2 4
4 16
8 256
16 0
24 0

イメージのバイト配列

イメージのバイト配列は、上記のRGBQUAD構造体の直後に存在します。RGBQUAD構造体が存在しない場合はBITMAPINFOHEADER構造体の直後に存在します。

イメージのバイト配列の記録方式は通常のBMPファイルのものと同様です。当然、パディングも存在します。

マスクのバイト配列

マスクはイメージのバイト配列に引き続いて記録されています。

マスクのバイト配列は、常に白黒1ビットです。これはBITMAPINFOHEADER構造体に示された、1ピクセルあたりのビット数には依存しません。また、マスクのサイズはイメージのサイズと同じです。

例のごとく

BMPファイル形式と同様、おもむろにHTMLに変換してみます。

#include <windows.h>
#include <stdio.h>

typedef struct{
  BYTE        bWidth;          // イメージの幅(ピクセル数)
  BYTE        bHeight;         // イメージの高さ(ピクセル数)
  BYTE        bColorCount;     // イメージの色数(256色より多い場合は0)
  BYTE        bReserved;       // Reserved ( must be 0)
  WORD        wPlanes;         // Color Planes
  WORD        wBitCount;       // 一ピクセルあたりのビット数
  DWORD       dwBytesInRes;    // イメージに使用しているバイト数
  DWORD       dwImageOffset;   // ファイル中の何バイト目から記録されているか。
} ICONDIRENTRY, *LPICONDIRENTRY;

// ファイルの先頭に存在する。
// リソースの型、イメージの数、ICONDIRENTRY構造体の配列を保持している
typedef struct{
  WORD           idReserved;   // Reserved (must be 0)
  WORD           idType;       // リソースの型 ICOの場合は1
  WORD           idCount;      // 含まれているイメージの数
  ICONDIRENTRY   idEntries[1]; // ICONDIRENTRY構造体の配列
} ICONDIR, *LPICONDIR;

typedef struct{
  BITMAPINFOHEADER   icHeader;      // DIBヘッダ
  RGBQUAD            icColors[1];   // カラーテーブル
  BYTE               icXOR[1];      // イメージ
  BYTE               icAND[1];      // マスク
} ICONIMAGE, *LPICONIMAGE;


BOOL ReadIcoFile(
  const char *FileName, ICONDIR **ppIconDir, ICONIMAGE ***ppvImgList
);
void FreeIco( ICONDIR* pIcoDir, ICONIMAGE **pvImg );
BOOL OptIco(
  const char *FileName, ICONDIR* pIcoDir, ICONIMAGE **pvImg
);
BOOL OptIco1( FILE *outfile, ICONIMAGE *pImg, int ColorCount );
BYTE* OptImage1(
  FILE *outfile, BYTE *pByte, int width, int height, int bpp, RGBQUAD *pRGB
);
BYTE* OptImage24(
  FILE *outfile, BYTE *pByte, int width, int height
);
int pad( int width, int bpp );

int main( int argc, char *argv[] )
{
  ICONDIR *pIcoDir;
  ICONIMAGE **vpIcoImage;

  if ( argc <= 2 ) return -1;

  if ( !ReadIcoFile( argv[1], &pIcoDir, &vpIcoImage ) )
    return -1;

  OptIco( argv[2], pIcoDir, vpIcoImage );

	FreeIco( pIcoDir, vpIcoImage );
	return 0;
}

// 通称:*.icoファイルを読み込む
// 引数:FileName : [入力]ファイル名
//    ppIconDir : [出力]ICONDIR構造体へのポインタ
//    ppvImgList : [出力]ICONIMAGE構造体へのポインタの配列のアドレス
// 機能:ICOファイルを読み込み、
//    ICONDIR構造体を確保し、そのアドレスをppIconDirが示すアドレスに返す
//    ICONIMAGE構造体を確保、そのアドレスを保持するポインタの配列を確保、
//       ppvImgListが示すアドレスに返す。
BOOL ReadIcoFile(
  const char *FileName,
  ICONDIR **ppIconDir,
  ICONIMAGE ***ppvImgList
)
{
  FILE *infile = NULL;
  ICONDIR *pIco = NULL;
  ICONIMAGE **pvImg = NULL;
  int i;

  infile = fopen( FileName, "rb" );	// ファイルを開く
  if ( NULL == infile ) return FALSE;

  pIco = (ICONDIR*)malloc( sizeof( ICONDIR ) );	// ICONDIR構造体を確保
  if ( NULL == pIco ) return FALSE;

  // ファイルの先頭に存在するICONDIRを読み込む
  fread( &(pIco->idReserved), sizeof( WORD ), 1, infile );
  fread( &(pIco->idType), sizeof( WORD ), 1, infile );
  fread( &(pIco->idCount), sizeof( WORD ), 1, infile );
  if ( 1 != pIco->idType ) goto ERR_EXIT;

  // ICONDIRENTRY構造体を記録する領域を確保する
  // (上記領域はICONDIR構造体の中に含まれる)
  pIco = (ICONDIR*)realloc(
    pIco, sizeof( ICONDIR ) + ( sizeof( ICONDIRENTRY ) * pIco->idCount )
  );
  if ( NULL == pIco ) goto ERR_EXIT;

  // ICONDIRENTRY構造体を読み込む
  fread( pIco->idEntries, sizeof( ICONDIRENTRY ), pIco->idCount, infile );

  // ICONIMAGE構造体のポインタの配列を確保する
  pvImg = (ICONIMAGE**)calloc( sizeof( ICONIMAGE* ), pIco->idCount );
  if ( NULL == pvImg ) goto ERR_EXIT;

  // ICONIMAGE構造体を読み込む
  for ( i = 0; i < pIco->idCount; i++ ) {
    // ICONIMAGE構造体の領域を確保する
    pvImg[i] = (ICONIMAGE*)malloc( pIco->idEntries[i].dwBytesInRes );
    if ( NULL == pvImg[i] ) goto ERR_EXIT;

  // シークして、読む
    fseek( infile, pIco->idEntries[i].dwImageOffset, SEEK_SET );
    fread( pvImg[i], 1, pIco->idEntries[i].dwBytesInRes, infile );
  }

  fclose ( infile );
  (*ppIconDir) = pIco;
  (*ppvImgList) = pvImg;
  return TRUE;

ERR_EXIT:
  fclose ( infile );
  FreeIco( pIco, pvImg );
  return FALSE;
}

// 通称:ReadIcoFile関数で確保されたメモリ領域を開放する
void FreeIco( ICONDIR* pIcoDir, ICONIMAGE **pvImg )
{
  int i;
  for ( i = 0; i < pIcoDir->idCount; i++ ) {
    free( pvImg[i] );
  }
  free( pvImg );
  free ( pIcoDir );
}

// 通称:アイコンのイメージをHTMLで出力する
// 機能:ファイルを開き、イメージを一つずつ出力させる
BOOL OptIco( const char *FileName, ICONDIR* pIcoDir, ICONIMAGE **pvImg )
{
  FILE *outfile;
  int i;

  outfile = fopen( FileName, "w" );	// ファイルを開く
  if ( NULL == outfile ) return FALSE;

  // HTMLのヘッダを出力
  fprintf( outfile, "<html><body>\n" );

  for ( i = 0; i < pIcoDir->idCount; i++ ) {	// イメージの個数分
    // 出力する
    if ( !OptIco1( outfile, pvImg[i], pIcoDir->idEntries[i].bColorCount ) ) {
      // 失敗したら終了する
      fclose( outfile );
      return FALSE;
    }
    fprintf( outfile, "\n" );	// 横線
  }

  fprintf( outfile, "</body></html>" ); 
  fclose( outfile );	// 閉じる 
  return TRUE; 
} 

// 通称:一つ分のイメージを出力する
BOOL OptIco1( FILE *outfile, ICONIMAGE *pImg, int ColorCount ) 
{ 
  RGBQUAD *pRGB = pImg->icColors;	// カラーテーブルの開始位置をポイントする 
  int ColorCnt;	// カラーテーブルの要素数を保持する 
  BYTE *pXOR;	// イメージのバイト配列をポイントする 
  RGBQUAD MaskColor[ 2 ] = {	// マスク用のカラーパレット 
    { 0, 0, 0, 0 }, 
    { 0xFF, 0xFF, 0xFF, 0xFF } 
  }; 

  const int bpp = pImg->icHeader.biBitCount;        // 一ピクセルあたりのビット数 
  const int width = pImg->icHeader.biWidth;        // 幅(ピクセル数) 
  const int height = pImg->icHeader.biHeight / 2;  // 高さ(ピクセル数) 
  //	(biHeightには本来のイメージとマスクの分を合計した高さが記録されている)

  // イメージの情報とテーブルのヘッダを出力
  fprintf( outfile, "Width = %d  Height = %d  BitPerPixel = %d\n",
    width, height, bpp ); 
  fprintf( outfile,
    "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" >\n" ); 

  // 本来のイメージの部分を出力
  if ( pImg->icHeader.biBitCount < 24 ) {
    // 1,2,4,8ビットカラーのイメージを出力
    ColorCnt = 0x1 << pImg->icHeader.biBitCount;  // 色数を算出
    pXOR = (BYTE*)( pRGB + ColorCnt );  // イメージのバイト配列のアドレスを算出
    pXOR = OptImage1( outfile, pXOR, width, height, bpp, pRGB );
  }
  else {
    // 24ビットカラーのイメージを出力
    pXOR = (BYTE*)pRGB;	// 24ビットカラーの場合、カラーパレットは存在しない
    pXOR = OptImage24( outfile, pXOR, width, height );
  }

  // マスク用のイメージを出力する(マスクイメージは1ビットカラーである)
  pXOR = OptImage1( outfile, pXOR, width, height, 1, MaskColor );

  // テーブルのフッタを出力
  fprintf( outfile, "</TABLE>\n" );

  return TRUE;
} 

// 通称:1,2,4,8ビットカラーを出力
// 戻り値:pByteから、出力した分だけスキップしたポインタを返す
BYTE* OptImage1(
  FILE *outfile,
  BYTE *pByte,
  int width,
  int height,
  int bpp,
  RGBQUAD *pRGB
)
{ 
  int i, j, k, l; 
  const int BitMask = ( 0x1 << bpp ) - 1; 

  for ( i = 0; i < height; i++ ) { 

    // 行の開始を示すタグを出力
    fprintf( outfile, "<tr>\n" );

    for ( j = 0; j < width / ( 8 / bpp ); j++ ) {
      // (1バイトが複数ピクセルとして使用されている)
      for ( l = 8 / bpp - 1; l >= 0; l-- ) {

        // 1ピクセル分の値を取得
        k = ( (*pByte) >> bpp * l ) & BitMask;

        // 一升分のタグを出力
        fprintf( outfile,
          "<TD width=\"1\" height=\"1\" bgcolor=\"%02X%02X%02X\" ></TD>\n",
          pRGB[k].rgbRed, pRGB[k].rgbGreen, pRGB[k].rgbBlue
        );
      }
      pByte++;	// 次のバイトへ
    }

    // パディングをスキップする
    for ( j = 0; j < pad( width, bpp ); j++ )
      pByte++;

    // 行の終わりを示すタグを出力する
    fprintf( outfile, "</TR>\n" );
  }

  return pByte;
}

// 通称:24ビットカラーのイメージを出力
// 戻り値:pByteから出力した分だけスキップしたポインタを返す
BYTE* OptImage24( FILE *outfile, BYTE *pByte, int width, int height )
{
  int i, j;

  for ( i = 0; i < height; i++ ) {

    // 行の開始を示すタグを出力
    fprintf( outfile, "<tr>\n" );

    for ( j = 0; j < width; j++ ) {

      // 一升分のタグを出力
      fprintf( outfile,
        "<TD width=\"1\" height=\"1\" bgcolor=\"%02X%02X%02X\" border=\"0\" ></TD>\n",
        pByte[2], pByte[1], pByte[0]
      );	// バイト配列中にはBGRの順に記録されている

      // 1ピクセルで3バイト使用する
      pByte += 3;
    }

    // パディングをスキップ
    for ( j = 0; j < pad( width, 24 ); j++ )
      pByte++;

    // 行の終わりを示すタグを出力
    fprintf( outfile, "</TR>\n" );
  }
  return pByte;
}

// パディング長(バイト数)を算出
// width : 画像の幅
// bpp : 一ピクセルあたりのビット数
int pad( int width, int bpp ) 
{ 
  int r = 4 - ( width * bpp % 32 ) / 8; 
  if ( r < 4 ) 
    return r; 
  else
    return 0; 
}

ここで、ICOファイルの形式とは直接の関係はありませんが、ReadIcoFile関数について解説したいと思います。この関数は「FileName」に与えたファイルを読み込み、ICONDIR構造体とICONIMAGE構造体を構築して返す、という働きをいたします。

そして、構築したICONDIR構造体は、そのアドレスを「ppIconDir」に示されるアドレスに返します。また、ICONIMAGE構造体は複数個存在し得るので、この関数は「ICONIMAGE構造体のポインタの配列」を構築、その配列のアドレスを「ppvImgList」に返します。

そして、「FreeIco関数」は「ReadIcoFile関数」により構築された構造体を破棄する処理を行います。

なお、このプログラムでは「イメージが上下逆に記録されている」事は考慮されていません。よって、HTML化した画像は上下が逆になります。また、16ビットカラーと32ビットカラーのイメージには対応していません。


連絡先 - サイトマップ - 更新履歴
Copyright (C)  2000 - 2016 nabiki_t All Rights Reserved.