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; }
今回は特に何も処理をしませんでしたが、同じ画像が出力されるのを確認できました。