|
Windows関連
スクリーンセーバー作成法
半透明ウインドウの性能
bootfont.bin
キャビネット形式
ウインドウスタイルをいじる
Java製ソフトをServiceに登録する
イベントログにメッセージを出力する
コントロールパネルにアイコンを追加する
Solaris関連
OpenGL
Solaris設定
ディレクトリの読み込み
主筆プラグイン開発
Sun Studio 11
マルチスレッドでの開発
door
音を出す
Blade100の正しい虐め方
パッケージの作成
画像入出力
BMPファイル
ICOファイル
ANIファイル
JPEGファイル
減色アルゴリズム
減色アルゴリズムの並列化
その他アルゴリズムなど
自由軸回転
Base64
文字列操作
CPU利用率の取得
正規表現ライブラリ
メタボールを作る
メタボールを作る2
正規表現とNFA・DFA
C言語の構文解析
マルチスレッド開発の傾向と対策
|
.aniファイル形式
このページではWindowsで使用されているアニメーションカーソルのファイル形式を説明したいと思います。
(.bmpから.icoときたら.curのような気もするけど、それを飛ばして.aniなのは何故なのか、ということはあまり気にするな。)
RIFFフォーマット
.aniファイルは、RIFFフォーマットと言うものに従い、複数の.icoファイルにいくつかの属性を付加して一つのファイルに押し込めたような構造になっている。
このRIFFフォーマットというものは、以下のように「識別子」「データ長」「データ」を一セットとして「チャンク」を構成し、そのチャンクを複数個格納する、という形式である。
また、「データ」の中には、さらに別のチャンクを含むこともある。
また、「識別子」は4バイトでchar型が4文字、「データ長」は4バイトの整数値でリトルエンディアンで記録されている。
ついでに言うと、RIFFおよびLIST以外のチャンクの長さは偶数バイトである必要があり、実際のデータが奇数バイトとなる場合は最後に1バイトのパディングが付加されるらしい。
なお、RIFFフォーマット自体はもっと詳細な事がいろいろと決まっているらしいが、ここでは特に知ったことではない
.aniにおけるRIFFフォーマット
.aniでは、以下のような構造となっている。
| 識別子 ”RIFF” |
| データ長 |
| データ |
| 識別子 ”ACON” |
| データ |
| 識別子 ”LIST” |
| データ長 |
| データ |
| 識別子 ”INFO” |
| データ |
| 識別子 ”INAM” |
| データ長 |
| データ(ファイルの名前) |
|
|
|
| 識別子 ”anih” |
| データ長 |
| データ(アニメーションカーソルのヘッダ情報) |
| 識別子 ”rate” |
| データ長 |
| データ(各コマの表示時間) |
| 識別子 ”seq ” |
| データ長 |
| データ(各コマで表示する絵) |
| 識別子 ”LIST” |
| データ長 |
| データ |
| 識別子 ”fram” |
| データ |
| 識別子 ”icon” |
| データ長 |
| データ(.icoファイル) |
(「icon」チャンクは複数回現れる。)
|
|
|
詳しいことは知らないが、識別子の後にデータ長が現れないチャンクも存在する。
また、同じ階層に於いては、各チャンクの出現する順番は特に決まっている、という事はないらしい。
つまり、「INAM」と「IART」は順不同で、「anih」と「rate」と「seq 」と「LIST」は順不同だ、ということだ。
(ついでに言うと、上記の階層構造はちょっと間違っているかもしれない。自信がない。)
各チャンクについて
次に、各チャンクごと、注意事項などを説明する。
「INAM」「IART」チャンク
このチャンクには、付加的な文字列の情報が記録されているだけである。
文字列はNULLで終わっている。
また、「データ長」にはNULLも含めたバイト数が記録されている。
しかし、(NULLも含めた)文字列長が奇数バイトの場合は、チャンクの末端に1バイトのパディングが存在する。
「anih」チャンク
「anih」チャンクのデータは以下のような構造となっている。
struct tagANIHeader {
DWORD cbSizeOf; // ANIHeader構造体のサイズ(バイト数)
DWORD cFrames; // 記録されている「icon」チャンクの数
DWORD cSteps; // 表示するコマの数
DWORD cx; // 使用しない
DWORD cy; // 使用しない
DWORD cBitCount; // 使用しない
DWORD cPlanes; // 使用しない
DWORD JifRate; // 各コマの表示時間のデフォルト値
DWORD flags; // 知らない
} ANIHeader;
「rate」チャンク
「rate」チャンクにはANIHeader.cSteps個の整数型(4バイト)のデータが記録されている。
この値は、各コマを表示する時間を1/60単位で示したものである。
なお、このチャンクが存在しない場合、各コマはANIHeader.JifRateの間だけ表示される。
「seq 」チャンク
このチャンクの名前は’s’ ’e’ ’q’ ’ ’で、最後に半角空白が含まれる。
このチャンクにはANIHeader.cSteps個の整数型(4バイト)のデータが記録されている。
この値は、各コマで表示する絵がどれなのかを示す番号が格納されている。
例えば、「icon」チャンクが以下のように4つ記録されていて、
「seq 」チャンクが以下のように記録されていた場合、
表示される絵は「A」->「B」->「C」->「D」->「C」->「B」となる。
また、このチャンクが存在しなかった場合には、各コマは「icon」チャンクに記録されていた順番通りに表示される。
「icon」チャンク
「icon」チャンクに記録されているデータは、.icoファイルの内容そのものである。
読み込んでみる
以上をふまえて、例のごとく.aniファイルを読み込むプログラムを作ってみる。
今回は、とりあえず読み込んだ情報を標準出力に表示しつつ、記録されているアイコンのデータを.icoファイルとして出力したいと思います。
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define DWORD unsigned int
// 識別子を比較するためのもの
#define V( a, b, c, d ) ( ( (d) << 24 ) + ( (c) << 16 ) + ( (b) << 8 ) + (a) )
// 出力した「icon」チャンクの数をカウントする。
int IconCnt;
// 識別子を読み込む
unsigned int ReadTag( FILE *infile )
{
unsigned int tag;
if ( fread( &tag, 1, 4, infile ) < 4 )
return -1;
printf( "%c%c%c%c\n", tag & 0xFF,
( tag >> 8 ) & 0xFF, ( tag >> 16 ) & 0xFF, tag >> 24
);
return tag;
}
// 「ACON」チャンクを読み込む
unsigned int Read_ACON( FILE *infile )
{
// 特にすることはない
return ReadTag( infile );
}
// 「LIST」チャンクを読み込む
unsigned int Read_LIST( FILE *infile )
{
unsigned int length;
// データ長の読み込みのみ
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
return -1;
printf( "LIST length = %d\n", length );
return ReadTag( infile );
}
// 「INAM」チャンクを読み込む
unsigned int Read_INAM( FILE *infile )
{
unsigned int length;
char *pBuf;
// データ長を読み込む
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
return -1;
printf( "INAM length = %d\n", length );
// パディングを処理する。
if ( length & 0x01 ) ++length; // 偶数バイト単位にパディングがあるか?
// 文字列を読み込む
pBuf = (char*)malloc( sizeof( char ) * length + 1 );
if ( fread( pBuf, sizeof( char ), length, infile ) < length )
return -1;
pBuf[ length ] = '\0';
printf( "Title = %s\n", pBuf );
free( pBuf );
return ReadTag( infile );
}
// 「IART」チャンクを読み込む
unsigned int Read_IART( FILE *infile )
{
unsigned int length;
char *pBuf;
// データ長を読み込む
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
return -1;
printf( "IART length = %d\n", length );
// パディングを処理する。
if ( length & 0x01 ) ++length;
// 文字列を読み込む
pBuf = (char*)malloc( sizeof( char ) * length + 1 );
if ( fread( pBuf, sizeof( char ), length, infile ) < length )
return -1;
pBuf[ length ] = '\0';
printf( "Author = %s\n", pBuf );
free( pBuf );
return ReadTag( infile );
}
// 「INFO」チャンクを読み込む
unsigned int Read_INFO( FILE *infile )
{
// 特にすることはない
return ReadTag( infile );
}
// 「fram」チャンクを読み込む
unsigned int Read_fram( FILE *infile )
{
// 特にすることはない
return ReadTag( infile );
}
// 「icon」チャンクを読み込む
unsigned int Read_icon( FILE *infile )
{
unsigned int length;
char *pBuf; // データ読み込み用のバッファ
char fname[ 1024 ]; // ファイル名生成用のバッファ
FILE *outfile;
// データ長を読み込む
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
return -1;
printf( "icon length = %d\n", length );
// データを読み込む
pBuf = (char*)malloc( sizeof( char ) * length );
if ( fread( pBuf, sizeof( char ), length, infile ) < length )
return -1;
// 出力するファイル名を生成する
snprintf( fname, 1024, "icon%03d.ico", IconCnt );
IconCnt++;
// ファイルを出力する
outfile = fopen( fname, "wb" );
fwrite( pBuf, sizeof( char ), length, outfile );
fclose( outfile );
free( pBuf );
return ReadTag( infile );
}
// 「anih」チャンクを読み込む
unsigned int Read_anih( FILE *infile )
{
unsigned int length;
struct tagANIHeader {
DWORD cbSizeOf;
DWORD cFrames;
DWORD cSteps;
DWORD cx;
DWORD cy;
DWORD cBitCount;
DWORD cPlanes;
DWORD JifRate;
DWORD flags;
} ANIHeader;
// データ長を読み込む
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
return -1;
printf( "anih length = %d\n", length );
// 各データを読み込む
if ( fread( &ANIHeader.cbSizeOf, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.cFrames, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.cSteps, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.cx, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.cy, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.cBitCount, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.cPlanes, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.JifRate, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
if ( fread( &ANIHeader.flags, sizeof( DWORD ), 1, infile ) < 1 )
return -1;
printf( "ANIHeader.cbSizeOf = %d\n", ANIHeader.cbSizeOf );
printf( "ANIHeader.cFrames = %d\n", ANIHeader.cFrames );
printf( "ANIHeader.cSteps = %d\n", ANIHeader.cSteps );
printf( "ANIHeader.cx = %d\n", ANIHeader.cx );
printf( "ANIHeader.cy = %d\n", ANIHeader.cy );
printf( "ANIHeader.cBitCount = %d\n", ANIHeader.cBitCount );
printf( "ANIHeader.cPlanes = %d\n", ANIHeader.cPlanes );
printf( "ANIHeader.JifRate = %d\n", ANIHeader.JifRate );
printf( "ANIHeader.flags = 0x%08X\n", ANIHeader.flags );
return ReadTag( infile );
}
// 「rate」チャンクを読み込む
unsigned int Read_rate( FILE *infile )
{
unsigned int length;
long *pBuf;
unsigned int i;
// データ長を読み込む
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
return -1;
printf( "rate length = %d\n", length );
// データを読み込む
pBuf = (long*)malloc( length ); // 「データ長」にはバイト数が記録されている
if ( fread( pBuf, sizeof( char ), length, infile ) < length ) return -1;
for ( i = 0; i < length / 4; i++ )
printf( "%d, ", pBuf[i] );
printf( "\n" );
free( pBuf );
return ReadTag( infile );
}
// データ長を読み込む
unsigned int Read_seq( FILE *infile )
{
unsigned int length;
long *pBuf;
unsigned int i;
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
return -1;
printf( "seq length = %d\n", length );
pBuf = (long*)malloc( length );
if ( fread( pBuf, sizeof( char ), length, infile ) < length )
return -1;
// 4バイト整数型として表示
for ( i = 0; i < length / 4; i++ )
printf( "%d, ", pBuf[i] );
printf( "\n" );
free( pBuf );
return ReadTag( infile );
}
// main関数
void main()
{
unsigned int tag;
unsigned int length;
FILE *infile = fopen( "c:\\WINNT\\cursors\\banana.ani", "rb" );
// 最初のタグを読む
tag = ReadTag( infile );
// 一番最初のタグは「RIFF」である必要がある
if ( tag != V( 'R', 'I', 'F', 'F' ) ) {
printf( "This file is not RIFF format.\n" );
goto ERR_EXIT;
}
// 「RIFF」チャンクのデータ長を読み込む
if ( fread( &length, sizeof( int ), 1, infile ) < 1 )
goto ERR_EXIT;
printf( "file length = %d\n", length );
// 次の識別子を読み込む
tag = ReadTag( infile );
while ( !feof( infile ) && !ferror( infile ) && tag != -1 ) {
// 次のチャンクの種類別に処理を振り分ける
switch ( tag ) {
case V( 'A', 'C', 'O', 'N' ):
tag = Read_ACON( infile );
break;
case V( 'L', 'I', 'S', 'T' ):
tag = Read_LIST( infile );
break;
case V( 'I', 'N', 'A', 'M' ):
tag = Read_INAM( infile );
break;
case V( 'I', 'A', 'R', 'T' ):
tag = Read_IART( infile );
break;
case V( 'f', 'r', 'a', 'm' ):
tag = Read_fram( infile );
break;
case V( 'i', 'c', 'o', 'n' ):
tag = Read_icon( infile );
break;
case V( 'a', 'n', 'i', 'h' ):
tag = Read_anih( infile );
break;
case V( 'r', 'a', 't', 'e' ):
tag = Read_rate( infile );
break;
case V( 's', 'e', 'q', ' ' ):
tag = Read_seq( infile );
break;
case V( 'I', 'N', 'F', 'O' ):
tag = Read_INFO( infile );
break;
default:
printf( "Unexpected tag\n" );
tag = -1;
}
}
ERR_EXIT:
fclose( infile );
return 0;
}
なお、上記のプログラムはRIFFフォーマットの階層構造を一切無視した作りになっており、「LIST」チャンクが複数回登場しうる事などを考慮すると危険なプログラムであるといえます。
だから、実際に.aniファイルを扱うプログラムを作る場合には、上記のプログラムをそのままコピーして使うのは避けた方がいいかと思います。
|
|