Entry

プログラミングメモ - 簡易ビットマップファイル読み込みクラス

2009年07月09日

画像処理なんかをごにょごにょやろうと思うとき,まず最初に突き当たるのが,画像のデータをどのように持つか,といったことだったりします。結果を読み書きできないと,自分が何をやってるのか分からないわけで,こゆもんは欠かせません。

この点,もちろん Windows API には GDI の便利ライブラリがあるし,その他各種画像関連のライブラリも,独自のデータ構造で画像データを管理しています。んなもんで,わざわざこゆのを作るのは,車輪の再発明的でもあったりする。けど,たかだかファイルに入出力の機能を付けるためだけに,特定のライブラリ(例えば gdi32.lib とか)に依存するのもどうかとは思うわけです。

つことで,ごく簡単なビットマップファイルの読み書きクラスを作ってみました。どこら辺が簡易かというと,こんなところ。

  • パレットを持たない DIB 24 bit 限定。
  • BITMAPINFOHEADER を持つファイル限定。BITMAPCOREHEADER や最近のヘッダ(BITMAPV4HEADER / BITMAPV5HEADER)には対応してない。
  • VC の #pragma ディレクティブを使ってるので,これを理解できる処理系でしかコンパイルできない(もっとも,他の処理系でも簡単に移植できるはず)。
  • 今気が付いたけれど,setPixel() メソッドがないから編集できないや(難しくないので,自分で実装してみてください)。

ま,読んで書ければ十分だと思うので,最後の setPixel() だけ作れば,自前の実験くらいには使えると思います。また,自前のユーティリティなので,配布物としてはまとめてません。自前の読み書きクラスを作る際のご参考にでも。

で,ソース。まずヘッダです。BitmapImage.h とかにしておく。

#ifndef BITMAP_IMAGE_H__INCLUDED
#define BITMAP_IMAGE_H__INCLUDED

#include <fstream>

typedef long Int32;
typedef unsigned long UInt32;
typedef unsigned short UInt16;
typedef short Int16;
typedef unsigned char UInt8;
typedef char Int8;

class BitmapImage {
private:
    // BITMAPFILEHEADER
#pragma pack(push, 2)
    struct BitmapFileHeader {
        UInt16 bfType;
        UInt32 bfSize;
        UInt16 bfReserved1;
        UInt16 bfReserved2;
        UInt32 bfOffBits;
    };
#pragma pack(pop)
    // BITMAPINFOHEADER
    struct BitmapInfoHeader {
        UInt32 biSize;
        Int32 biWidth;
        Int32 biHeight;
        UInt16 biPlanes;
        UInt16 biBitCount;
        UInt32 biCompression;
        UInt32 biSizeImage;
        Int32 biXPelsPerMeter;
        Int32 biYPelsPerMeter;
        UInt32 biClrUsed;
        UInt32 biClrImportant;
    };
public:
    BitmapImage();
    virtual ~BitmapImage();
public:
    void unload();
    // TODO: filename as Unicode.
    void read(const char* filename) throw (std::exception);
    void write(const char* filename) const throw (std::exception);
public:  // accessor
    bool isLoaded() const;
    Int32 getWidth() const;
    Int32 getHeight() const;
    UInt16 getBitCount() const;
    UInt32 getPixel(Int32 x, Int32 y);
protected:
    void readPixels(UInt32* dst, const Int8* src, UInt32 length) const;
    void writePixels(Int8* dst, const UInt32* src, UInt32 length) const;
    void setBitmapFileHeader(BitmapFileHeader& bfh) const;
    void setBitmapInfoHeader(BitmapInfoHeader& bih) const;
    UInt32 getRowLength(UInt32 width, UInt16 bitCount) const;
private:
    Int32 width_;
    Int32 height_;
    UInt32* pixels_;
    UInt16 bitCount_;
};

#endif  // BITMAP_IMAGE_H__INCLUDED

次に実装ファイル。BitmapImage.cpp とかにしておく。

#include <algorithm>
#include <cassert>
#include "BitmapImage.h"

BitmapImage::BitmapImage() : width_(0), height_(0), pixels_(0), bitCount_(0) {
}

BitmapImage::~BitmapImage() {
    unload();
}

void
BitmapImage::unload() {
    if (pixels_ != 0) {
        delete[] pixels_;
        pixels_ = 0;
    }
    width_ = 0;
    height_ = 0;
    bitCount_ = 0;
}

void
BitmapImage::read(const char* filename) throw (std::exception) {
    assert(filename != 0);
    assert(!isLoaded());
    // open file
    std::ifstream ifs(filename, std::ios::in | std::ios::binary);
    if (!ifs.good()) {
        throw std::exception("file open.");
    }
    // read BITMAPFLEHEADER
    BitmapFileHeader bfh;
    if (!ifs.read(reinterpret_cast<Int8*>(&bfh), sizeof(bfh)).good()) {
        throw std::exception("file read.");
    }
    if (bfh.bfType != 0x4D42) {  // 4D42 == "BM"
        throw std::exception("invalid file type.");
    }
    // read BITMAPINFOHEADER
    BitmapInfoHeader bih;
    if (!ifs.read(reinterpret_cast<Int8*>(&bih), sizeof(bih)).good()) {
        throw std::exception("file read.");
    }
    if (bih.biBitCount != 24) {
        throw std::exception("invalid bitmap depth.");
    }
    // read pixels
    Int32 rowLength = getRowLength(bih.biWidth, bih.biBitCount);
    UInt32* tmp = 0;
    Int8* buf = 0;
    try {
        ::size_t size = bih.biWidth * bih.biHeight;
        tmp = new UInt32[size];
        buf = new Int8[rowLength];
        UInt32* cur = 0;
        for (int i = (bih.biHeight - 1); i >= 0; --i) {
            if (!ifs.read(buf, rowLength).good()) {
                throw std::exception("file read.");
            }
            cur = tmp + (bih.biWidth * i);
            readPixels(cur, buf, bih.biWidth);
        }
        delete[] buf;
    } catch (std::bad_alloc& e) {
        if (tmp != 0) {
            delete[] tmp;
        }
        if (buf != 0) {
            delete[] buf;
        }
        throw e;
    }
    // set members
    width_ = bih.biWidth;
    height_ = bih.biHeight;
    pixels_ = tmp;
    bitCount_ = bih.biBitCount;
}

void
BitmapImage::write(const char* filename) const throw (std::exception) {
    assert(filename != 0);
    assert(isLoaded());
    std::ofstream ofs(filename, std::ios::out | std::ios::binary);
    if(!ofs.good()) {
        throw std::exception("file open.");
    }
    // write BITMAPFILEHEADER
    BitmapFileHeader bfh;
    setBitmapFileHeader(bfh);
    if (!ofs.write(reinterpret_cast<Int8*>(&bfh), sizeof(bfh)).good()) {
        throw std::exception("file write.");
    }
    // write BITMAPINFOHEADER
    BitmapInfoHeader bih;
    setBitmapInfoHeader(bih);
    if (!ofs.write(reinterpret_cast<Int8*>(&bih), sizeof(bih)).good()) {
        throw std::exception("file write.");
    }
    // write pixels
    Int32 rowLength = getRowLength(bih.biWidth, bih.biBitCount);
    Int8* buf = 0;
    try {
        UInt32* cur = 0;
        buf = new Int8[rowLength];
        std::fill(buf, buf + rowLength, 0);
        for (int i = (height_ - 1); i >= 0; --i) {
            cur = pixels_ + (width_ * i);
            writePixels(buf, cur, width_);
            if (!ofs.write(buf, rowLength).good()) {
                throw std::exception("file write.");
            }
        }
        delete[] buf;
    } catch (std::bad_alloc& e) {
        if (buf != 0) {
            delete[] buf;
        }
        throw e;
    }
}

void
BitmapImage::setBitmapFileHeader(BitmapFileHeader& bfh) const {
    assert(isLoaded());
    UInt32 pixelSize = getRowLength(width_, bitCount_) * height_;
    UInt32 bfhSize = sizeof(BitmapFileHeader);
    UInt32 bihSize = sizeof(BitmapInfoHeader);

    bfh.bfType = 0x4D42;
    bfh.bfSize = bfhSize + bihSize + pixelSize;
    bfh.bfReserved1 = 0;
    bfh.bfReserved2 = 0;
    bfh.bfOffBits = bfhSize + bihSize;
}

void
BitmapImage::setBitmapInfoHeader(BitmapInfoHeader& bih) const {
    UInt32 pixelSize = getRowLength(width_, bitCount_) * height_;

    bih.biSize = sizeof(BitmapInfoHeader);
    bih.biWidth = width_;
    bih.biHeight = height_;
    bih.biPlanes = 1;
    bih.biBitCount = bitCount_;
    bih.biCompression = 0;
    bih.biSizeImage = pixelSize;
    bih.biXPelsPerMeter = 0;
    bih.biYPelsPerMeter = 0;
    bih.biClrUsed = 0;
    bih.biClrImportant = 0;
}

void
BitmapImage::readPixels(UInt32* dst, const Int8* src, UInt32 length) const {
    // now is 24 bit only
    for (int i = 0; i < length; ++i) {
        *dst = 0;
        // blue
        *dst |= static_cast<UInt32>(static_cast<UInt8>(*src));
        *dst <<= 8;
        ++src;
        // green
        *dst |= static_cast<UInt32>(static_cast<UInt8>(*src));
        *dst <<= 8;
        ++src;
        // red
        *dst |= static_cast<UInt32>(static_cast<UInt8>(*src));
        ++dst;
        ++src;
    }
}

void
BitmapImage::writePixels(Int8* dst, const UInt32* src, UInt32 length) const {
    // now is 24 bit only
    for (int i = 0; i < length; ++i) {
        // blue
        *dst = static_cast<UInt8>((static_cast<UInt32>(*src) & 0xFF0000) >> 16);
        ++dst;
        // green
        *dst = static_cast<UInt8>((static_cast<UInt32>(*src) & 0x00FF00) >> 8);
        ++dst;
        // red
        *dst = static_cast<UInt8>(static_cast<UInt32>(*src) & 0x0000FF);
        ++dst;
        ++src;
    }
}

UInt32
BitmapImage::getRowLength(UInt32 width, UInt16 bitCount) const {
    return ((width * bitCount + 31) & ~31) >> 3;
}

bool
BitmapImage::isLoaded() const {
    return pixels_ != 0;
}

Int32
BitmapImage::getWidth() const {
    return width_;
}

Int32
BitmapImage::getHeight() const {
    return height_;
}

UInt16
BitmapImage::getBitCount() const {
    return bitCount_;
}

UInt32
BitmapImage::getPixel(Int32 x, Int32 y) {
    assert(isLoaded());
    assert(x >= 0 && x < getWidth());
    assert(y >= 0 && y < getHeight());
    return *(pixels_ + (getWidth() * y) + x);
}

こんな風に使います。

#include <iostream>
#include "BitmapImage.h"

int
main(int argc, char* argv[]) {
    BitmapImage bi;

    try {
        bi.read("C:\\tmp\\test.bmp");
        if (bi.isLoaded()) {
            std::cout << bi.getWidth() << std::endl;
            std::cout << bi.getHeight() << std::endl;
            std::cout << bi.getBitCount() << std::endl;
            bi.write("C:\\tmp\\out.bmp");
        }
    } catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

使えるようにするには,もう少し工夫が必要かな……。使えるようになってから公開しろよ,ってな噂もあるんだけども。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN