ホーム 主筆 その他ソフト その他情報 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メモリに書き込み続けてみた

.aniファイル形式

2005年4月18日公開

このページでは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”
データ長
データ(ファイルの名前)
識別子 ”IART”
データ長
データ(作者名)
識別子 ”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 」チャンクが以下のように記録されていた場合、

012321

表示される絵は「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ファイルを扱うプログラムを作る場合には、上記のプログラムをそのままコピーして使うのは避けた方がいいかと思います。


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