その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

BMPファイルのカラーパレットを書き換える

ゲームのキャラ画像など、一つの画像に対して配色パターンがたくさんある場合、画像ファイルは1つだけ持っておいて、カラーパレットを動的に書き換えるという方式をとるようです。
これだと容量を節約できるし、全ピクセルに対して色変換する場合と違ってパレットの書き換えだけなので4バイト×256の処理で済みます。


BMPファイルフォーマットの詳しい説明はこちら。
http://www.kk.iij4u.or.jp/~kondo/bmp/


これの、

  • RGBQUAD(Blue, Green, Red, Reserved の 4 byte)
  • 256色ビットマップ(BitCount == 8)

の場合についての変換コードを書いたので載せておきます。
(※注:下記コードはBitCount の値が 1, 4の場合や、RGBTRIPLE には対応しておりません)

+ (NSData *)convertImageData:(NSData *)data WithPalette:(NSArray *)palette {
    NSMutableData *dataOut = [NSMutableData dataWithCapacity:0];

    // ファイルヘッダ 	14
    // 情報ヘッダサイズ	4
    // 幅				4
    // 高さ				4
    // プレーン数			2
    // ピクセル毎のビット数	2
    // 圧縮タイプ			4
    // イメージデータサイズ	4
    // 水平解像度			4
    // 垂直解像度			4
    // カラーインデックス数	4
    // 重要インデックス数	4
    
    // ピクセル毎のビット数
    unsigned char pic_per_bit[2];
    [data getBytes:pic_per_bit range:NSMakeRange(14+4+4+4+2, 2)];
    
    // パレットが存在するための条件にあてはまらなければnilを返す
    // ※QUADタイプに限定
    if (*pic_per_bit != 8) {
        NSLog(@"warning : no palette");
        return nil;
    }

    // ヘッダ部分をコピー
    int offset = 14+40;    
    [dataOut appendData:[data subdataWithRange:NSMakeRange(0, offset)]];

    
    // パレット書き換え (青, 緑, 赤, 予約)
    unsigned char b;
    unsigned char g;
    unsigned char r;
    unsigned char a;
    
    for (int i=0; i<[palette count]; i++) {
        NSDictionary *paletteDic = [palette objectAtIndex:i];
        
        NSNumber *paletteB = [NSNumber numberWithInt:[[paletteDic objectForKey:@"ColorB"] intValue]];
        NSNumber *paletteG = [NSNumber numberWithInt:[[paletteDic objectForKey:@"ColorG"] intValue]];
        NSNumber *paletteR = [NSNumber numberWithInt:[[paletteDic objectForKey:@"ColorR"] intValue]];
        NSNumber *paletteA = [NSNumber numberWithInt:[[paletteDic objectForKey:@"ColorA"] intValue]];
        
        b = [paletteB unsignedCharValue];
        g = [paletteG unsignedCharValue];
        r = [paletteR unsignedCharValue];
        a = [paletteA unsignedCharValue];
                
        
        [dataOut appendBytes:&b length:1];
        [dataOut appendBytes:&g length:1];
        [dataOut appendBytes:&r length:1];
        [dataOut appendBytes:&a length:1];
        
        offset += 4;
    }
    
    [dataOut appendData:[data subdataWithRange:NSMakeRange(offset, [data length] - offset)]];


    return dataOut;
}


画像をバイナリデータとして処理するため、UIImageではなくCGImageなんとかでもなく、NSDataとして扱います。


呼び出し側では、下記のように画像ファイルからNSDataオブジェクトを作成し、上記メソッドに渡しています。

NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
NSData *dataIn = [NSData dataWithContentsOfFile:path];
NSData *dataOut = [PaletteConversion convertImageData:dataIn WithPalette:palette];

ちなみにパレットデータはこんな感じのplist形式のファイルを読み込んでます。

<array>
	<dict>
		<key>ColorA</key>
		<string>0</string>
		<key>ColorB</key>
		<string>144</string>
		<key>ColorG</key>
		<string>144</string>
		<key>ColorR</key>
		<string>144</string>
	</dict>
	<dict>
		<key>ColorA</key>
		<string>255</string>
		<key>ColorB</key>
		<string>176</string>
		<key>ColorG</key>
		<string>224</string>
		<key>ColorR</key>
		<string>248</string>
	</dict>
	...(これが256個)
</array>

ColorAとかのキーが冗長な感じですが、諸事情あってこうなっております。よしなに修正してご使用ください。