Tips 2 - OpenGLの描写内容を直接Bitmapとして出力するクラスを作った -

Overview

OpenGLでWindowに表示している内容を直接画像として出力したいと思ったが、面倒そうだったので画像出力専用の構造体と関数を作った。
OpenCVを使えばそりゃ簡単にできるが、使うライブラリはなるべく減らしたいと思った。
参考までにコードを載せる。直接bitmapにバイナリを書き込んでいるので、注意してはいるものの環境によっては動かないかもしれない。
Visual Studio 2019環境では正常に動作。
参考にしたサイトはこちら→ https://sonson.jp/blog/2006/04/03/opengl-bitmap-1/
・・・だが、ところどころ抜けていて色々とリンクも切れているので、自分で補完した。
注意点として、ダブルバッファリングしている場合はglReadPixelはデフォルトでバックサイドの情報を取得する模様。
glReadBuffer(GL_FRONT)とすれば、glReadPixelがWindowに表示されているフォアグラウンドのバッファを取得してくれる。
また、bitmapのヘッダ構造体は情報量としては54byteだが、構造体のサイズとしては4の倍数である56byteになった。
ここは環境依存かどうかわからないが、ofstreamで書き込むバイト数を直接指定してぶち込んでいる。

この関数の注意点として、Windowのwidthとheightの取得にのみGLUTの機能を使っている。
GLUTじゃない場合は要調整。
WriteBitmapを走らせるたびに連番のファイル名でbitmapイメージを一枚、image/フォルダに出力するようにしている。
当然処理がだいぶ重くなるので、デバッグや発表資料作りとかにしか使えないかなぁ。
常用するにはきつい。
typedef struct _BitmapAllHeader {
    //Bitmap header
    char    distinct1 = 'B';     // 'B'
    char    distinct2 = 'M';     // 'M'
    int     filesize = 0;      // total file size = imagedata + 54
    short   reserve1 = 0;      // 0
    short   reserve2 = 0;      // 0
    int     offset = 54;        // 14+40=54
    //Bitmap info header
    int     header = 40;            // infoheader size = 40
    int     width = 0;             // bitmap width
    int     height = 0;            // bitmap height
    short   plane = 1;             // 1
    short   bits = 24;              // 24bit color per pixel
    int     compression = 0;       // 0 -> no compression
    int     comp_image_size = 0;   // imagedata
    int     x_resolution = 3780;      // 3780 = 96dpi
    int     y_resolution = 3780;      // 3780
    int     palette_num = 0;       // 0
    int     important_palette_num = 0; //0

    _BitmapAllHeader(int w, int h) :width(w), height(h) {
        int writeWidth = (int)((width * 3 + 3) / 4) * 4;
        comp_image_size = writeWidth * height;
        filesize = comp_image_size + offset;
    };
}BitmapAllHeader;

int WriteBitmap(string filename, bool printinfo = false) {
    int width = glutGet(GLUT_WINDOW_WIDTH);
    int height = glutGet(GLUT_WINDOW_HEIGHT);
    BitmapAllHeader header(width, height);
    static int count = 1; // 画像連番作成のため

    ofstream ofs("image/" + filename + to_string(count) + ".bmp", ios::binary);
    if (!ofs) {
        cout << "Failed to open file" << endl;
        return -1;
    }

    int offset = offsetof(BitmapAllHeader, filesize);
    ofs.write((char*)&header, 2);
    ofs.write((char*)&header + offset, 52);

    GLubyte* data = (GLubyte*)malloc(width * height * 3 * (sizeof(GLubyte)));
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glReadPixels(0, 0, window_width, window_height, GL_RGB, GL_UNSIGNED_BYTE, data);
    const char zero = 0;
    for (int y = 0; y < height; y++) {
        // RGB -> BGR
        for (int x = 0; x < width; x++) {
            ofs.write((char*)(data + x * 3 + 3 * y * width + 2), sizeof(GLubyte));
            ofs.write((char*)(data + x * 3 + 3 * y * width + 1), sizeof(GLubyte));
            ofs.write((char*)(data + x * 3 + 3 * y * width), sizeof(GLubyte));
        }
        // 0 fill
        if (width * 3 % 4 != 0)
            for (int j = 0; j < 4 - (width * 3) % 4; j++)
                ofs.write(&zero, sizeof(GLubyte));
    }
    ofs.close();
    free(data);

    count++;

    if (printinfo) {
        cout << "image/" << filename << to_string(count) << ".bmp saved successfully" << endl;
    }

    return 0;
}