今回は、オブジェクトの「保存」「読み込み」を行うシリアライズ処理のインターフェースとその実装を作ってみます。シリアライズを行う対象は前回作成した読み書き機能を公開するストリームインターフェースとし、このインターフェースを実装してファイルやメモリなど保存したい対象を扱うストリームクラスを定義することにしましょう。あとは、このストリームクラスのオブジェクトをシリアライズを行うインターフェースを実装したクラスのオブジェクトに渡せばシリアライズ処理が行われるわけです。
今回は、シリアライズ処理を試してみるためにファイルに対する読み書きを行うファイルストリームクラスを定義し、そのファイルストリームに対してシリアライズ(保存)・デシリアライズ(読み込み)処理を行う32ビットDIBクラスも作ってみました。
ファイルストリームクラスD2CFileReaderStream/D2CFileWriterStreamクラスの宣言は以下のようになっています。
class D2IStreamReader { // 読み込み用ストリームインターフェース
public:
virtual UI8 read()=0;
virtual I32 read(BUF,I32)=0;
virtual I32 read(D2IBufferWriter *)=0;
virtual ~D2IStreamReader() {}
};
class D2IStreamWriter { // 書き込み用ストリームインターフェース
public:
virtual bool write(UI8)=0;
virtual I32 write(const BUF,I32)=0;
virtual I32 write(const D2IBufferReader *)=0;
virtual ~D2IStreamWriter() {};
};
// ファイル読み込み用ストリームクラス
class D2CFileReaderStream:public D2IObject,public D2IStreamReader {
public:
D2CFileReaderStream(const CSTR);
virtual bool seek(I32);
virtual bool close();
virtual ~D2CFileReaderStream();
// D2IObject
virtual UI32 getClassID() const;
virtual UI32 getObjectType() const;
virtual UI8 getObjectVersion() const;
virtual UI8 getDataType() const;
virtual UI16 getEncoding() const;
virtual void * getInterface(UI32);
virtual CSTR getCSTR();
// D2IStreamReader;
virtual UI8 read();
virtual I32 read(BUF,I32);
virtual I32 read(D2IBufferWriter *);
private:
FILE *m_fp;
};
// ファイル書き込み用ストリームクラス
class D2CFileWriterStream:public D2IObject,public D2IStreamWriter {
public:
D2CFileWriterStream(CSTR);
virtual bool seek(I32);
virtual bool close();
virtual ~D2CFileWriterStream();
// D2IObject
virtual UI32 getClassID() const;
virtual UI32 getObjectType() const;
virtual UI8 getObjectVersion() const;
virtual UI8 getDataType() const;
virtual UI16 getEncoding() const;
virtual void * getInterface(UI32);
virtual CSTR getCSTR();
// D2IStreamWriter
virtual bool write(UI8);
virtual I32 write(const BUF,I32);
virtual I32 write(const D2IBufferReader *);
private:
FILE *m_fp;
};
内部の構造は、ストリーム読み書き用のインタフェースD2IStreamReader/D2IStreamWriterをfopen()/fwrite()で実装しただけ(^^;。ファイル名を指定してD2CFileReaderStreamクラスのインスタンスを生成すると、そのインスタンスに対してファイルの内容を読み出すread()が可能になり、同様にファイル名を指定してD2CFileWriterStreamクラスのインスタンスを生成すると、そのインスタンスに対してファイルに内容を書き出すwrite()が可能になるわけですね。
このストリームを用いたシリアライズ(ストリームへの保存)とデシリアライズ(ストリームからの読み込み)は、以下のようなシリアライズインターフェースで行うようにしました。
class D2ISerialize { // シリアライズインターフェース
public:
virtual bool serialize(D2IStreamWriter *,UI32)=0;
~D2ISerialize() {}
};
class D2IDeserialize { // デシリアライズインターフェース
public:
virtual bool deserialize(D2IStreamReader *,UI32)=0;
~D2IDeserialize() {}
};
いずれも、シリアライズ・デシリアライズ用の関数を一つ(と仮想デストラクタを)持つだけ。最初の引数がストリームオブジェクトのポインタで、2番目の引数でデータ(エンコード)形式を指定するようにしました。ストリームとしては、ストリームインターフェースを渡すようになっているので、今回のようにストリームインタフェースを実装したファイルストリームはもちろん、ストリームインタフェースを実装した(あるいはストリームインターフェースを取得できる)オブジェクトならこの引数に渡すことが出来ます。メモリを読み書きするストリームやネットワークストリームを作れば、そのまま対応できるわけですね。
これらのインターフェースを実装したクラスは、指定されたデータ形式に応じて内部データを書き出したり、読み出したデータを自分の内部データにセットすることになるでしょう。
次に、このストリームへのシリアライズを試すために32ビットビットマップクラスを作ってみます。このクラスは、以下のように1ピクセル32ビット(RGB各8ビット、上位8ビット無効)のビットマップを扱うインターフェースを32ビットDIBとして実装してみました。
class D2IBitmapInterface {
public:
virtual I32 getWidth() const=0;
virtual I32 getHeight() const=0;
virtual UI32 getOrigin() const=0;
virtual ~D2IBitmapInterface() {}
};
class D2IBitmapReader:public D2IBitmapInterface {
public:
virtual UI32 getRGB24(I32,I32) const=0;
virtual UI8 getR8(I32,I32) const=0;
virtual UI8 getG8(I32,I32) const=0;
virtual UI8 getB8(I32,I32) const=0;
virtual I32 getColors() const=0;
virtual ~D2IBitmapReader() {}
};
class D2IBitmapWriter:public D2IBitmapInterface {
public:
virtual bool setRGB24(I32,I32,UI32)=0;
virtual bool setR8(I32,I32,UI8)=0;
virtual bool setG8(I32,I32,UI8)=0;
virtual bool setB8(I32,I32,UI8)=0;
virtual bool clear(I32,I32,I32)=0;
virtual ~D2IBitmapWriter() {}
};
class D2IBitmap:public D2IBitmapReader,public D2IBitmapWriter {
public:
virtual bool setSize(I32,I32)=0;
virtual void * getHeader() const=0;
virtual void * getPixels() const=0;
virtual ~D2IBitmap() {}
};
class D2IBitmap32XRGB8:public D2IBitmap {
public:
virtual UI32 getXRGB32(I32,I32) const=0;
virtual bool setXRGB32(I32,I32,UI32)=0;
virtual ~D2IBitmap32XRGB8() {}
};
class D2CDIB32:public D2IObject,public D2ISerialize,D2IDeserialize,public D2IBitmap32XRGB8 {
public:
D2CDIB32();
D2CDIB32(I32,I32);
D2CDIB32(const D2CDIB32&);
D2CDIB32& operator=(const D2CDIB32&);
bool assign(const D2IBitmapReader *);
~D2CDIB32();
//D2IObject
virtual UI32 getClassID() const;
virtual UI32 getObjectType() const;
virtual UI8 getObjectVersion() const;
virtual UI8 getDataType() const;
virtual UI16 getEncoding() const;
virtual void * getInterface(UI32);
virtual CSTR getCSTR();
// D2ISerialize
virtual bool serialize(D2IStreamWriter *,UI32);
// D2IDeserialize
virtual bool deserialize(D2IStreamReader *,UI32);
// D2IBitmapInterface
virtual I32 getWidth() const;
virtual I32 getHeight() const;
virtual UI32 getOrigin() const;
// D2IBitmapReader
virtual UI32 getRGB24(I32,I32) const throw(std::out_of_range);
virtual UI8 getR8(I32,I32) const throw(std::out_of_range);
virtual UI8 getG8(I32,I32) const throw(std::out_of_range);
virtual UI8 getB8(I32,I32) const throw(std::out_of_range);
virtual I32 getColors() const;
// D2IBitmapWriter
virtual bool setRGB24(I32,I32,UI32);
virtual bool setR8(I32,I32,UI8);
virtual bool setG8(I32,I32,UI8);
virtual bool setB8(I32,I32,UI8);
virtual bool clear(I32,I32,I32);
// D2IBitmap
virtual bool reset(I32,I32);
virtual void * getHeader() const;
virtual void * getPixels() const;
//D2IBitmap32XRGB8
virtual UI32 getXRGB32(I32,I32) const throw(std::out_of_range);
virtual bool setXRGB32(I32,I32,UI32);
protected:
virtual void * getHeaderPointer() const;
virtual UI32 * getPixelsPointer() const;
virtual bool setSize(I32,I32);
virtual bool setWidth(I32);
virtual bool setHeight(I32);
virtual bool saveBMP(D2IStreamWriter *,UI32);
virtual bool loadBMP(D2IStreamReader *,UI32);
private:
CSTR m_csStr;
D2CBuffer *m_pbuDIB;
D2CFixedBuffer *m_pbInfo,*m_pbPixels;
UI32 *m_pu32Pixels;
void *m_pvHeader;
I32 m_i32Width,m_i32Height;
};
上のメンバのうち、D2CBufferはビットマップの内部データ(32ビットBMPのメモリイメージ)を保持するためのバッファクラスです。
class D2CBuffer:public D2IBuffer {
public:
D2CBuffer();
D2CBuffer(I32);
D2CBuffer(BUF,I32);
D2CBuffer(const D2IBufferReader *);
D2CBuffer(const D2CBuffer&);
virtual ~D2CBuffer();
// D2IObject
virtual UI32 getClassID() const;
virtual UI32 getObjectType() const;
virtual UI8 getObjectVersion() const;
virtual UI8 getDataType() const;
virtual UI16 getEncoding() const;
virtual void * getInterface(UI32);
virtual CSTR getCSTR();
// D2IBufferInterface
virtual I32 getSize() const;
// D2IBufferReader
virtual BUF copy() const;
virtual UI8 read(I32) const throw(std::range_error);
virtual I32 read(I32,BUF,I32) const;
virtual bool equal(const BUF,I32) const;
virtual bool equal(const D2IBufferReader *) const;
// D2IBufferWriter
virtual bool write(I32,UI8);
virtual I32 write(I32,const BUF,I32);
virtual bool fill(I32,UI8,I32);
// D2IBufferAdministrator
virtual BUF getBuffer() const;
virtual bool resize(I32);
virtual bool clear(I32,UI8);
virtual bool assign(const D2IBufferReader *);
// D2IBuffer
virtual bool append(UI8);
virtual bool append(const BUF,I32);
virtual bool append(const D2IBufferReader *);
virtual bool insert(I32,const BUF,I32);
virtual bool insert(I32,const D2IBufferReader *);
protected:
virtual bool reset(I32);
virtual bool setSize(I32);
virtual BUF getBufferPointer() const;
virtual bool setBufferPointer(const BUF);
virtual I32 getLastIndex() const;
private:
I32 m_i32BufferSize;
BUF m_bfBuffer;
};
このバッファクラスは、前回の固定長バッファクラスにサイズ変更やポインタ取得などの機能(D2IBufferAdministrator/D2IBufferインターフェースの実装)を追加したものです。また、今回からバッファは「ストリーム機能を提供する主体」ではなく「ストリームで操作される対象」という位置付けにしたので、バッファ内部からストリームを外してあります。
32ビットDIBクラスD2CDIB32では、このバッファを用いて以下のようにビットマップのデータを作成するようにしました。
bool D2CDIB32::reset(I32 i32Arg1,I32 i32Arg2) {
if (i32Arg1<1 || i32Arg2<0)
return false;
// データサイズを計算
I32 i32Size=54+i32Arg1*i32Arg2*4;
D2CBuffer *pbuTmp=new D2CBuffer(i32Size);
if (pbuTmp->getSize()!=i32Size) {
delete pbuTmp;
return false;
}
setSize(i32Arg1,i32Arg2);
// BITMAPFILEHEADER
pbuTmp->write(0,'B');
pbuTmp->write(1,'M');
pbuTmp->write(2,(BUF)&i32Size,4);
pbuTmp->fill(6,0,4);
pbuTmp->write(10,54);
// BITMAPINFOHEADER
pbuTmp->write(14,40);
pbuTmp->fill(15,0,3);
pbuTmp->write(18,(BUF)&i32Arg1,4);
pbuTmp->write(22,(BUF)&i32Arg2,4);
pbuTmp->write(26,1);
pbuTmp->write(27,0);
pbuTmp->write(28,32);
pbuTmp->write(29,0);
I32 i32Tmp=BI_RGB;
pbuTmp->write(30,(BUF)&i32Tmp,4);
if (m_pbuDIB!=NULL)
delete m_pbuDIB;
m_pbuDIB=pbuTmp;
m_pvHeader=(void *)(m_pbuDIB->getBuffer()+14);
m_pu32Pixels=(UI32 *)(m_pbuDIB->getBuffer()+54);
return true;
}
これでDIBのヘッダ部分のポインタがm_pvHeaderに、ピクセル列部分のポインタがm_pu32Pixelsに格納されたので、他のメンバ関数ではこれらのポインタを通してDIBを操作することが出来ます。
次にデータのシリアライズ・デシリアライズは、指定されたストリームとバッファオブジェクトm_pbuDIBとの間でデータを転送し、m_i32Widthをはじめとした変数を更新することで実現できます。今回は、内部形式である32ビットBMPそのまま、24ビットBMPの2つのデータ形式に対応しました。
シリアライズ用関数は、D2ISerializeインターフェースのserialize()とD2IDeserializeインターフェースのdeserialize()です。
// ストリームへの書き出し
bool D2CDIB32::serialize(D2IStreamWriter *pswArg,UI32 u32Arg) {
if (pswArg==NULL)
return false;
switch (u32Arg) {
// メモリ上のデータをBMP形式で書き出す
case ENCODE_NONE:
case ENCODE_BMP:
case ENCODE_BMP24RGB8:
case ENCODE_BMP32XRGB8:
return saveBMP(pswArg,ENCODE_BMP24RGB8);
default:
return false;
}
}
// ストリームからの読み込み
bool D2CDIB32::deserialize(D2IStreamReader *psrArg,UI32 u32Arg) {
if (psrArg==NULL)
return false;
switch (u32Arg) {
// BMPファイル読み込み
case ENCODE_NONE:
case ENCODE_BMP32XRGB8:
case ENCODE_BMP:
case ENCODE_BMP24RGB8:
return loadBMP(psrArg,ENCODE_BMP);
}
return false;
}
bool D2CDIB32::saveBMP(D2IStreamWriter *pswArg,UI32 u32Arg) {
if (pswArg==NULL)
return false;
I32 i,j,i32Length,i32Width=getWidth(),i32Height=getHeight(),i32Size,i32Written;
D2CBuffer *pbuTmp;
switch (u32Arg) {
// 32BitBMP形式(データバッファをそのまま書き出す)
case ENCODE_NONE:
case ENCODE_BMP32XRGB8:
return (pswArg->write(m_pbuDIB)==m_pbuDIB->getSize());
// 24BitBMP形式
case ENCODE_BMP:
case ENCODE_BMP24RGB8:
if (i32Width % 4==0)
i32Length=i32Width*3;
else
i32Length=i32Width*3+(4-(i32Width*3) % 4);
i32Size=54+i32Length*i32Height;
pbuTmp=new D2CBuffer(i32Size);
if (pbuTmp->getSize()!=i32Size) {
delete pbuTmp;
return false;
}
pbuTmp->write(0,m_pbuDIB->getBuffer(),54);
pbuTmp->write(28,24);
for (i=0;i<i32Height;i++) // ピクセル列を24Bitで書き出す
for (j=0;j<i32Width;j++) {
pbuTmp->write(54+j*3+i*i32Length+2,getR8(j,i));
pbuTmp->write(54+j*3+i*i32Length+1,getG8(j,i));
pbuTmp->write(54+j*3+i*i32Length,getB8(j,i));
}
i32Written=pswArg->write(pbuTmp);
delete pbuTmp;
return i32Written==i32Size;
default:
return false;
}
}
bool D2CDIB32::loadBMP(D2IStreamReader *psrArg,UI32 u32Arg) {
I32 i,j,i32Length,i32Width,i32Height,i32Size,i32Offset,i32Bpp;
BUF bfData,bfPixels;
BUF bfTmp=(BUF)D2Malloc(6);
if (psrArg->read(bfTmp,6)!=6 || bfTmp[0]!='B' || bfTmp[1]!='M') {
D2Free(bfTmp);
return false;
}
i32Size=*((I32 *)(bfTmp+2));
D2Free(bfTmp);
bfData=(BUF)D2Malloc(i32Size-6);
if (psrArg->read(bfData,i32Size-6)!=i32Size-6) {
D2Free(bfData);
return false;
}
i32Bpp=bfData[22];
i32Width=*((UI32 *)(bfData+12));
i32Height=*((UI32 *)(bfData+16));
if ((i32Bpp!=24 && i32Bpp!=32) || *((UI32 *)(bfData+24))!=BI_RGB ||
i32Width<1 || i32Height==0) {
D2Free(bfData);
return false;
}
bool bUpset=false;
if (i32Height<0) {
i32Height=-i32Height;
bUpset=true;
}
if (i32Bpp==24 && i32Width % 4==0)
i32Length=i32Width*3;
else if (i32Bpp==24)
i32Length=i32Width*3+(4-((i32Width*3) % 4));
else
i32Length=i32Width*4;
i32Offset=*((UI32 *)(bfData+4));
if (i32Size<i32Offset+i32Length*i32Height) {
D2Free(bfData);
return false;
}
if (bUpset)
reset(i32Width,-i32Height);
else
reset(i32Width,i32Height);
bfPixels=bfData+i32Offset-6;
switch (i32Bpp) { // ピクセル形式に応じて読み込む
case 24: // 24Bit
for (i=0;i<i32Height;i++)
for (j=0;j<i32Width;j++) {
setB8(j,i,bfPixels[j*3+i*i32Length]);
setG8(j,i,bfPixels[j*3+i*i32Length+1]);
setR8(j,i,bfPixels[j*3+i*i32Length+2]);
}
return true;
case 32: // 32bit
for (i=0;i<i32Height;i++)
for (j=0;j<i32Width;j++) {
setB8(j,i,bfPixels[j*4+i*i32Length]);
setG8(j,i,bfPixels[j*4+i*i32Length+1]);
setR8(j,i,bfPixels[j*4+i*i32Length+2]);
}
return true;
default:
return false;
}
}
このビットマップクラスは、最初に
D2CDIB32 dib(256,256);
のようにインスタンスを生成してあとは
dib.setRGB24(128,128,0x00ffffff);
のようにビットマップ操作用のインターフェースを使って操作します。保存する時はまず保存するストリームを作成してそのストリームを引数にserialize()してください。下の例では、test.bmpというファイル名のファイルに書き込むファイルストリームを作成し、そのストリームにビットマップのデータをserialize()しています。
D2CFileWriterStream fws("test.bmp");
dib.serialize(&fws,ENCODE_NONE);
fws.close();
シリアライズしたデータを読み込む場合は、保存したファイルを読み出すストリームを作成し、そのストリームからdeserialize()します。
D2CFileReaderStream frs("test.bmp");
dib.deserialize(&frs,ENCODE_BMP);
frs.close();
今回のプログラムは以下の処理を行います。
プログラムと同じディレクトリにtest.bmpというファイル名の24ビットBMPファイルをおいてからプログラムを実行してみてください。処理結果が書き出されたtest2.bmpが作成されます。
#include "windef.h"
#include "define.h"
#include "interfaces.h"
#include "classes.h"
int main() {
D2CDIB32 dib;
D2CFileReaderStream frs("test.bmp");
dib.deserialize(&frs,ENCODE_BMP);
frs.close();
for (I32 i=0;i<dib.getHeight();i++)
for (I32 j=0;j<dib.getWidth();j++) {
dib.setR8(j,i,dib.getR8(j,i) ^ 0xff);
dib.setG8(j,i,dib.getG8(j,i) ^ 0xff);
dib.setB8(j,i,dib.getB8(j,i) ^ 0xff);
}
D2CFileWriterStream fws("test2.bmp");
dib.serialize(&fws,ENCODE_NONE);
fws.close();
return 0;
}