スポンサーリンク

WindowsからBMPファイルを出力

BMPファイル形式について

BMPはMicrosoftとIBMが開発したファイル形式で、Windows版とOS/2版がある。ここではWindows版の32bit,24bitしか考えない。

以下Wikipedia:

https://ja.wikipedia.org/wiki/Windows_bitmap

BMPファイルの4バイト境界について

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を無視できない。

BMPファイルの出力手順

1.データが4byte境界の条件を満たしていないなら、満たしているデータを作成する。

2.BITMAPFILEHEADER構造体に値を設定する

3.BITMAPINFO構造体に値を設定する

4.上記2,3をバイナリでファイルに書き出す

5.画像データをファイルに書き出す

BMPファイル形式はMicrosoftが考案した形式だが、Windowsに用意されているのはヘッダの構造体だけだ。データの4byte境界を修正する関数はおろかそのエラーチェックをする関数も用意されていない。圧縮もサポートしていると言ってもヘッダに圧縮状態のフラグを指定できるだけで、使いたかったら自分で圧縮しなければならない。ヘッダの出力もやってることはただのfwriteで、sizeof(BITMAPFILEHEADER)バイトだけ、sizeof(BITMAPINFO)バイトだけバイナリ出力しているに過ぎない。

サンプルコード

BMPwriter.hpp

#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);

BMPWriter.cpp

#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;

}

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: