BMPはMicrosoftとIBMが開発したファイル形式で、Windows版とOS/2版がある。ここではWindows版の32bit,24bitしか考えない。
以下Wikipedia:
https://ja.wikipedia.org/wiki/Windows_bitmap
BMPファイルは、横一行のデータのバイト数が4の倍数でなければいけないという制約がある。32bit BMPの場合は1画素が4バイトなので必ずこの条件を満たす。従って考える必要はない(考える必要がないだけで制約が取り払われているわけではない)。
問題は24bitの時で、例えば3×2の画像の一行の画素数は3なので、1行3×(24/8)=9バイトになる。9より大きいもっとも小さな4の倍数は12なので、10,11,12バイト目はダミーのデータで埋めてやる必要がある。
しかも、32bit BMPは24bit BMPに比べて扱えないソフトが多いとの理由で、.bmp形式なら24bit BMPが世の中では推奨されている。だから24bit BMPを無視できない。
1.データが4byte境界の条件を満たしていないなら、満たしているデータを作成する。
2.BITMAPFILEHEADER構造体に値を設定する
3.BITMAPINFO構造体に値を設定する
4.上記2,3をバイナリでファイルに書き出す
5.画像データをファイルに書き出す
BMPファイル形式はMicrosoftが考案した形式だが、Windowsに用意されているのはヘッダの構造体だけだ。データの4byte境界を修正する関数はおろかそのエラーチェックをする関数も用意されていない。圧縮もサポートしていると言ってもヘッダに圧縮状態のフラグを指定できるだけで、使いたかったら自分で圧縮しなければならない。ヘッダの出力もやってることはただのfwriteで、sizeof(BITMAPFILEHEADER)バイトだけ、sizeof(BITMAPINFO)バイトだけバイナリ出力しているに過ぎない。
#pragma once //! @brief 与えられた画素数がビットマップの要求する4byte境界に何バイト足りないかを返す //! @param [in] width 画素数 //! @param [in] pixelsize 1ピクセルのビット数 //! @return 足して4の倍数になる値 int getbmp4line_err(const unsigned int width, const unsigned int pixelbits); //! @brief 幅と高さと画素のビット数からビットマップデータにしたときに何バイトになるかを算出する //! @param [in] width 画像のピクセル数(幅) //! @param [in] height 画像のピクセル数(高さ) //! @param [in] pixelbit 1ピクセルのビット数 //! @return 4バイト境界に合わせたときのバイト数 unsigned int getbmpdata_memsize(const unsigned int width, const unsigned int height, const unsigned int pixelbits); //! @brief データをスキャンライン4倍バイトデータにしたものを作成 //! @param [out] dst 調整後の画素データの格納先アドレス //! @param [in] src 元データの先頭アドレス //! @param [in] width 画像幅(ピクセル) //! @param [in] height 画像高さ(ピクセル) //! @param [in] pixelbit 1ピクセルのビット数 //! @return そのままBMPにできるデータ配列 //! @note dstは確保済みの必要がある。このバイト数はgetbmpdatasizeで取得できる void data_to_bmp4lines(unsigned char* dst,const void* src, const unsigned int width, const unsigned int height, const unsigned int pixelbits); //! @brief ビットマップファイルを保存する //! @param [in] pathname ファイル名 //! @param [in] pixeldata 画像データの先頭ポインタ。4バイト境界に調整済みの必要がある //! @param [in] width 画像のピクセル幅 //! @param [in] height 画像のピクセル高さ //! @param [in] 1画素のビット数 //! @return なし void bmp_write(const wchar_t* pathname, const void* pixeldata, const unsigned int width, const unsigned int height, const unsigned int pixelbits); //! @brief ビットマップファイルを保存する関数のインタフェース //! @param [in] pathname ファイル名 //! @param [in] pixeldata 画像データの先頭ポインタ。4バイト境界の調整前のもの //! @param [in] width 画像のピクセル幅 //! @param [in] height 画像のピクセル高さ //! @param [in] 1画素のビット数 //! @return なし void call_bmp_write(const wchar_t* pathname, const void* pixeldata, const unsigned int width, const unsigned int height, const unsigned int pixelbits);
#include "BMPwriter.hpp" #include <Windows.h> #include <vector>
int getbmp4line_err(const unsigned int width, const unsigned int pixelbits) { return (4 - (width * pixelbits /8) % 4) % 4; }
unsigned int getbmpdata_memsize(const unsigned int width, const unsigned int height, const unsigned int pixelbits) { unsigned int widthbytes = width * (pixelbits / 8) + getbmp4line_err(width, pixelbits); return widthbytes * height; }
void data_to_bmp4lines(unsigned char* dst, const void* src, const unsigned int width, const unsigned int height, const unsigned int pixelbits) { unsigned int pixelbytes = (pixelbits / 8); std::uint8_t * p = (std::uint8_t*)src; int err = getbmp4line_err(width, pixelbits);//4バイト境界に何バイト足りないかを算出 size_t pos=0; for (size_t h = 0; h < height; h++) { //一行コピー for (size_t x = 0; x < width * pixelbytes; x++) { dst[pos++] = *p; p++; } if (err != 0) { // width * pixelsizeが4になるようにデータを挿入 for (size_t k = 0; k < err; k++) { dst[pos++] = 0; } } } }
void bmp_write( const wchar_t* pathname, const void* pixeldata, const unsigned int width, const unsigned int height, const unsigned int pixelbits) { const unsigned long bytecount = pixelbits / 8; const unsigned long headSize = (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO)); //4バイト境界を考慮した1スキャンラインのバイト数 const unsigned long widthBytes = width * bytecount + getbmp4line_err(width, pixelbits); const unsigned long imageBytes = (widthBytes * height); // BITMAPFILEHEADERの初期化 BITMAPFILEHEADER bmpHead = { 0 }; bmpHead.bfType = 0x4D42; // "BM" bmpHead.bfSize = headSize + imageBytes; bmpHead.bfReserved1 = 0; bmpHead.bfReserved2 = 0; bmpHead.bfOffBits = headSize; // BITMAPINFOの初期化 BITMAPINFO bmpInfo = { 0 }; bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmpInfo.bmiHeader.biWidth = width; bmpInfo.bmiHeader.biHeight = height; bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biBitCount = pixelbits; bmpInfo.bmiHeader.biCompression = BI_RGB; bmpInfo.bmiHeader.biSizeImage = 0; bmpInfo.bmiHeader.biXPelsPerMeter = 0; bmpInfo.bmiHeader.biYPelsPerMeter = 0; bmpInfo.bmiHeader.biClrUsed = 0; bmpInfo.bmiHeader.biClrImportant = 0; FILE * fp = _wfopen(pathname, L"wb" ); fwrite(&bmpHead, sizeof(BITMAPFILEHEADER), 1, fp); fwrite(&bmpInfo, sizeof(BITMAPINFO), 1, fp); fwrite(pixeldata, 1 , imageBytes, fp); fclose(fp); }
void call_bmp_write(const wchar_t* pathname, const void* pixeldata, const unsigned int width, const unsigned int height, const unsigned int pixelbits) { //BMP保存時の画像のメモリサイズを求める int retsize = getbmpdata_memsize(width, height, pixelbits); std::vector<std::uint8_t> save; save.resize(retsize); //4バイト境界の条件を満たしたデータを作成する data_to_bmp4lines(&save[0], pixeldata, width, height, pixelbits); //BMPファイルに保存する bmp_write(pathname, &save[0], width, height, pixelbits); }
#include <iostream> #include <vector> #include "BMPwriter.hpp" struct data32bit { unsigned char p[4]; data32bit() {} data32bit( unsigned char b, unsigned char g, unsigned char r, unsigned char u ) { p[0] = b; p[1] = g; p[2] = r; p[3] = u; } data32bit( unsigned char b, unsigned char g, unsigned char r ) { p[0] = b; p[1] = g; p[2] = r; p[3] = 0; } }; struct data24bit { unsigned char p[3]; data24bit() {} data24bit( unsigned char b, unsigned char g, unsigned char r ) { p[0] = b; p[1] = g; p[2] = r; } }; template<typename dtype> void create_data(std::vector<dtype>& img,int width,int height) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pos = y * width + x; if (y == x) { img[pos] = dtype(255, 0, 0); } else { img[pos] = dtype(0, 0, 255); } if (x == width - 1) img[pos] = dtype(0, 255, 0); } } }
int main() { //画像データ std::vector<data24bit> img; unsigned int bits = 24; unsigned int width = 7; unsigned int height = 9; img.resize(width*height); create_data(img,width,height); std::cout << "scanline bytes % 4 = " << (width * bits / 8) % 4; call_bmp_write(L"C:\\data\\a.bmp",&img[0], width, height, bits); int k; std::cin >> k; }