アウトプットブログ

勉強したことをまとめていきます。

C言語でBMPファイルのR/W

前回BMPファイルの読み込み失敗の原因としてバイトオーダーを挙げましたが、間違いでした。(それどころか問題を修正してバイトオーダー変換をかましたら逆に動きませんでした…笑)原因は構造体のバイトアライメントでした。

BMPファイルの先頭にはBMPFILEHEADERというヘッダが存在します。このヘッダサイズは14byteです。これを以下の通り構造体として定義して、関数の方でファイルリードを行っていました。
・BMPFILEHEADER構造体

typedef struct tagBITMAPFILEHEADER {
    unsigned short  bfType;
    unsigned long   bfSize;
    unsigned short  bfReserved1;
    unsigned short  bfReserved2;
    unsigned long   bfOffBits;
} BITMAPFILEHEADER;

・関数での呼び出し

fread_s(&bmpInfo->bitmapFileHeader, sizeof(BITMAPFILEHEADER), sizeof(BITMAPFILEHEADER), 1, fp);

問題はsizeof(BITMAPFILEHEADER)とした部分で、本来14byteのはずが16byteとして扱われており、結果としてファイルポインタがずれてしまっていました。以下ページの5.1を参照すると、「複合データ型のアラインメントはそれに含まれる要素のアラインメントの中で最も大きい(厳しい)ものに等しい」とあります。今回最も大きいのはunsigned longの4byteのため、16byteにアラインされたのでしょう。
データ型のアラインメントとは何か,なぜ必要なのか?


きっと良い方法があるのでしょうが、今回は動作することを目標として、逐一各ヘッダ情報を読み込むようにしたところ、読み込みに成功しました。以下、BMPのR/W関数です。BMPファイル構成については以下リンクを参考にしました。
bmp ファイルフォーマット
BMP.h

typedef struct tagBITMAPFILEHEADER {
    unsigned short  bfType;
    unsigned long   bfSize;
    unsigned short  bfReserved1;
    unsigned short  bfReserved2;
    unsigned long   bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
    unsigned long   biSize;
    long            biWidth;
    long            biHeight;
    unsigned short  biPlanes;
    unsigned short  biBitCount;
    unsigned long   biCompression;
    unsigned long   biSizeImage;
    long            biXPixPerMeter;
    long            biYPixPerMeter;
    unsigned long   biClrUsed;
    unsigned long   biClrImportant;
} BITMAPINFOHEADER;

typedef struct tagRGBQUAD {
    unsigned char rgbBlue;
    unsigned char rgbGreen;
    unsigned char rgbRed;
    unsigned char rgbReserved;
} RGBQUAD;

typedef struct tagBMPINFO {
    BITMAPFILEHEADER    bitmapFileHeader;
    BITMAPINFOHEADER    bitmapInfoHeader;
    RGBQUAD             rgbQuad[256];
    unsigned char       *imageData;
} BMPINFO;

int readBMP(char* path, BMPINFO* bmpInfo);
int writeBMP(char* path, BMPINFO bmpInfo);

BMP.c

#include <stdio.h>
#include <stdlib.h>
#include "BMP.h"

int readBMP(char* path, BMPINFO* bmpInfo)
{
    errno_t err;
    FILE *fp;

    // BMPファイルオープン&存在確認
    if ((err = fopen_s(&fp, path, "rb")) != 0) return -1;
    // BITMAPFILEHEADER/bfType読み込み
    fread_s(&bmpInfo->bitmapFileHeader.bfType, sizeof(unsigned short), sizeof(unsigned short), 1, fp);
    // BMPであることを確認
    if (bmpInfo->bitmapFileHeader.bfType != 0x4d42) return -2;
    // その他情報を読み込む
    fread_s(&bmpInfo->bitmapFileHeader.bfSize, sizeof(unsigned long), sizeof(unsigned long), 1, fp);        // bfSize
    fread_s(&bmpInfo->bitmapFileHeader.bfReserved1, sizeof(unsigned short), sizeof(unsigned short), 1, fp); // bfReserved1
    fread_s(&bmpInfo->bitmapFileHeader.bfReserved2, sizeof(unsigned short), sizeof(unsigned short), 1, fp); // bfReserved2
    fread_s(&bmpInfo->bitmapFileHeader.bfOffBits, sizeof(unsigned long), sizeof(unsigned long), 1, fp);     // bfOffBits
    // BITMAPINFOHEADER読み込み
    fread_s(&bmpInfo->bitmapInfoHeader, sizeof(BITMAPINFOHEADER), sizeof(BITMAPINFOHEADER), 1, fp);
    // BMPINFOHEADERの種類を確認
    long test = bmpInfo->bitmapInfoHeader.biSize;
    if (bmpInfo->bitmapInfoHeader.biSize != 40) return -3;
    // カラーテーブル取得
    fread_s(&bmpInfo->rgbQuad, sizeof(RGBQUAD) * 256, sizeof(RGBQUAD), 256, fp);
    // 画像データ格納用領域確保
    bmpInfo->imageData = (char*)malloc(sizeof(char) * bmpInfo->bitmapInfoHeader.biWidth * bmpInfo->bitmapInfoHeader.biHeight);
    // 画像データ取得
    fread_s(bmpInfo->imageData,
        bmpInfo->bitmapInfoHeader.biWidth * bmpInfo->bitmapInfoHeader.biHeight,
        sizeof(char),
        bmpInfo->bitmapInfoHeader.biWidth * bmpInfo->bitmapInfoHeader.biHeight,
        fp);

    fclose(fp);

    return 0;
}

int writeBMP(char* path, BMPINFO bmpInfo)
{
    errno_t err;
    FILE *fp;

    // BMPファイルオープン存在確認
    if ((err = fopen_s(&fp, path, "w+b")) != 0) return -1;
    // BMPファイル書き出し
    fwrite(&bmpInfo.bitmapFileHeader.bfType, sizeof(unsigned short), 1, fp);
    fwrite(&bmpInfo.bitmapFileHeader.bfSize, sizeof(unsigned long), 1, fp);
    fwrite(&bmpInfo.bitmapFileHeader.bfReserved1, sizeof(unsigned short), 1, fp);
    fwrite(&bmpInfo.bitmapFileHeader.bfReserved2, sizeof(unsigned short), 1, fp);
    fwrite(&bmpInfo.bitmapFileHeader.bfOffBits, sizeof(unsigned long), 1, fp);
    fwrite(&bmpInfo.bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, fp);
    fwrite(&bmpInfo.rgbQuad, sizeof(RGBQUAD), 256, fp);
    fwrite(bmpInfo.imageData, sizeof(unsigned char), bmpInfo.bitmapInfoHeader.biWidth * bmpInfo.bitmapInfoHeader.biHeight, fp);

    fclose(fp);

    return 0;
}

・メイン関数

#include <stdio.h>
#include <stdlib.h>
#include "BMP.h"

int main()
{
    char srcPath[64] = "LENNA.bmp";
    char dstPath[64] = "dstImage.bmp";

    BMPINFO bitmapInfo;

    readBMP(srcPath, &bitmapInfo);
    writeBMP(dstPath, bitmapInfo);

    free(bitmapInfo.imageData);

    return 0;
}

今回は特に何も処理をしませんでしたが、同じ画像が出力されるのを確認できました。