私は現在 C++ (Java から来ています) を学んでおり、C++ で IO ストリームを適切に使用する方法を理解しようとしています。
画像のピクセルを含むクラスがImage
あり、ストリームから画像を読み取るために抽出演算子をオーバーロードしたとします。
istream& operator>>(istream& stream, Image& image)
{
// Read the image data from the stream into the image
return stream;
}
これで、次のような画像を読み取ることができます。
Image image;
ifstream file("somepic.img");
file >> image;
しかし今、同じ抽出演算子を使用して、カスタム ストリームから画像データを読み取りたいと考えています。圧縮形式の画像を含むファイルがあるとします。そのため、ifstream を使用する代わりに、独自の入力ストリームを実装したいと思うかもしれません。少なくとも、それが私がJavaで行う方法です。Java では、クラスを拡張してメソッドInputStream
を実装するカスタム クラスを作成しint read()
ます。とても簡単です。使用法は次のようになります。
InputStream stream = new CompressedInputStream(new FileInputStream("somepic.imgz"));
image.read(stream);
したがって、同じパターンを使用して、C++でこれを実行したいかもしれません:
Image image;
ifstream file("somepic.imgz");
compressed_stream stream(file);
stream >> image;
しかし、それは間違った方法かもしれません。クラスの拡張はistream
かなり複雑に見えますが、いくつかの検索の後、streambuf
代わりに拡張に関するいくつかのヒントを見つけました。しかし、この例は、このような単純なタスクに対して非常に複雑に見えます。
では、C++ でカスタム入出力ストリーム (またはストリームバッファ?) を実装する最良の方法は何ですか?
解決
一部の人々は、iostream をまったく使用せず、代わりに反復子、ブースト、またはカスタム IO インターフェイスを使用することを提案しました。これらは有効な代替手段かもしれませんが、私の質問は iostream に関するものでした。受け入れられた回答により、以下のコード例が得られました。読みやすくするために、ヘッダーとコードの分離はなく、std 名前空間全体がインポートされます (実際のコードではこれが悪いことであることはわかっています)。
この例は、垂直 xor エンコードされたイメージの読み取りと書き込みに関するものです。フォーマットはかなり簡単です。各バイトは 2 つのピクセルを表します (ピクセルあたり 4 ビット)。各行は前の行と xor されます。この種のエンコーディングは、画像を圧縮する準備をします (通常、圧縮しやすい 0 バイトが多くなります)。
#include <cstring>
#include <fstream>
using namespace std;
/*** vxor_streambuf class ******************************************/
class vxor_streambuf: public streambuf
{
public:
vxor_streambuf(streambuf *buffer, const int width) :
buffer(buffer),
size(width / 2)
{
previous_line = new char[size];
memset(previous_line, 0, size);
current_line = new char[size];
setg(0, 0, 0);
setp(current_line, current_line + size);
}
virtual ~vxor_streambuf()
{
sync();
delete[] previous_line;
delete[] current_line;
}
virtual streambuf::int_type underflow()
{
// Read line from original buffer
streamsize read = buffer->sgetn(current_line, size);
if (!read) return traits_type::eof();
// Do vertical XOR decoding
for (int i = 0; i < size; i += 1)
{
current_line[i] ^= previous_line[i];
previous_line[i] = current_line[i];
}
setg(current_line, current_line, current_line + read);
return traits_type::to_int_type(*gptr());
}
virtual streambuf::int_type overflow(streambuf::int_type value)
{
int write = pptr() - pbase();
if (write)
{
// Do vertical XOR encoding
for (int i = 0; i < size; i += 1)
{
char tmp = current_line[i];
current_line[i] ^= previous_line[i];
previous_line[i] = tmp;
}
// Write line to original buffer
streamsize written = buffer->sputn(current_line, write);
if (written != write) return traits_type::eof();
}
setp(current_line, current_line + size);
if (!traits_type::eq_int_type(value, traits_type::eof())) sputc(value);
return traits_type::not_eof(value);
};
virtual int sync()
{
streambuf::int_type result = this->overflow(traits_type::eof());
buffer->pubsync();
return traits_type::eq_int_type(result, traits_type::eof()) ? -1 : 0;
}
private:
streambuf *buffer;
int size;
char *previous_line;
char *current_line;
};
/*** vxor_istream class ********************************************/
class vxor_istream: public istream
{
public:
vxor_istream(istream &stream, const int width) :
istream(new vxor_streambuf(stream.rdbuf(), width)) {}
virtual ~vxor_istream()
{
delete rdbuf();
}
};
/*** vxor_ostream class ********************************************/
class vxor_ostream: public ostream
{
public:
vxor_ostream(ostream &stream, const int width) :
ostream(new vxor_streambuf(stream.rdbuf(), width)) {}
virtual ~vxor_ostream()
{
delete rdbuf();
}
};
/*** Test main method **********************************************/
int main()
{
// Read data
ifstream infile("test.img");
vxor_istream in(infile, 288);
char data[144 * 128];
in.read(data, 144 * 128);
infile.close();
// Write data
ofstream outfile("test2.img");
vxor_ostream out(outfile, 288);
out.write(data, 144 * 128);
out.flush();
outfile.close();
return 0;
}