[Mac] BMPファイルを自力で描画してみた【BPS Advent Calendar:12/19】

初投稿させていただきます!
2015年新卒入社のyoshi.kと申します。

普段の業務では、おもに自社製品である超画像(iOS版画像ビューア)の改修などを行っていますが、画像の描画は基本的にUIKitのUIImageに任せっきりです。

そこで今回、勉強も兼ねてBMPフォーマットの画像を自力で描画してみました。

BMPファイルとは

正式名称はMicrosoft Windows Bitmap Imageと呼ばれます
基本的には無圧縮のため、他の画像フォーマットよりも容量が大きくなりがちです。

BMPは大きく分けて次のような構造になっています。

  • ヘッダ: どんなファイルであるかという情報が含まれる
  • 画像データ: 1つのピクセルごとにRGB情報が割り当てられる

もう少し詳しくは次のようになっています。

名前 サイズ 説明
ヘッダファイル 14 byte ファイルの一般的な情報
情報ヘッダ 40 byte or 12 byte ファイルの詳細な情報
カラーパレット 可変(色数 × 3 byte or 4 byte) 色の定義
ない場合もある
画像データ 可変 1ピクセルごとのデータ

詳細については、「BMP ファイルフォーマット」などでググってください。

今回のBMPフォーマット画像描画は以下を前提としました。制約多いですね; ;

  • 無圧縮
  • カラーパレットなし
  • Windows Bitmap形式
  • 24-bit color

プログラム

プログラムが汚くてごめんなさい。
今回画像の描画に必要な情報は、幅、高さ、各ピクセル毎のRGBです。
まず、幅については情報ヘッダの 5 〜 8 byte、高さは 9 〜 12 byteを読み取れば取得できます。

typedef struct {
    unsigned int width;
    unsigned int height;
} ImageSize;

ImageSize read_bitmap_size(FILE *fp) {
    unsigned int size;
    ImageSize imgSize;

    fseek(fp, sizeof(char) * 14, SEEK_SET);
    fread(&size, sizeof(int), 1, fp);
    fread(&imgSize, sizeof(int), 2, fp);
    fseek(fp, sizeof(char) * size + 14, SEEK_SET);

    return imgSize;
}

次にRGBです。

1ピクセルごとに、青、緑、赤の順で色データが並びます。赤、緑、青じゃないんですね。

24-bit colorの場合、色ごとに1 byteずつ割り当てられるので、1ピクセルで計3 byte使います。

typedef struct {
    unsigned char b;
    unsigned char g;
    unsigned char r;
} Color;

Color read_rgb(FILE *fp) {
    Color biColor;
    fread(&biColor, sizeof(char), 3, fp);
    return biColor;
}

画像データは、必ず1行あたり4 byteで単位で記録されます。

画像データが4 byteで割り切れない場合には、余った部分に意味のないデータ(パディング)が置かれるので、パディングをスキップする処理が必要です。

void skip_padding(int padding, FILE *fp) {
    if (padding == 4) {
        return;
    }

    fseek(fp, sizeof(char) * padding * 3, SEEK_CUR);
}

最後に、取得した情報を元に1ピクセルごとに点を描画すれば終わりです。

今回はCocoa Applicationとして作成しましたが、何気にCocoa Applicationを作るのは初めてです。

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    FILE *fp;
    open_bitmap((char *)[kFilePath UTF8String], &fp);
    ImageSize imageSize = read_bitmap_size(fp);
    int padding = 4 - imageSize.width % 4;

    for(NSInteger y = 0; y < imageSize.height; y++) {
        for(NSInteger x = 0; x < imageSize.width; x++) {
            Color rgb = read_rgb(fp);
            NSColor *color = [NSColor colorWithDeviceRed:(CGFloat)rgb.r / 255
                                                   green:(CGFloat)rgb.g / 255
                                                    blue:(CGFloat)rgb.b / 255
                                                   alpha:1.0];
            [color set];
            NSRect rect = NSMakeRect(x, y, x, y);
            NSFrameRect(rect);
        }
        skip_padding(padding, fp);
    }
    close_bitmap(&fp);
}

結果

プレビューアプリ 作成物
after BMP after BMP

ちゃんと描画されました!
やたー。

まとめ

今回は、決まった欲しい情報だけを抜き出して描画するプログラムになってしまいました。
次回以降、もう少しデータの内容について深掘りしていければと思います。
また、別のフォーマットについてもチャレンジしていきたいです。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

yoshi.k

yoshi.kの書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ