C++Builderのフォーム情報(*.dfm)ではアイコン等の画像を16進文字列で保存しているようで、似たようなことをやってみる。(使いどころはパッとは思い浮かばないが)。
とりあえずVclの例。
サンプルプログラム
TFormにTButtonとTImageを2つずつと、TOpenPictureDialogを設置。TImageは2つともProportionalプロパティをtrueにしておく。
画像を16進文字列で保存
手順は以下の通り
①画像ファイルをTMemoryStreamに読み込む
②サイズの同じunsigned char配列を用意し、MemoryStreamからデータを書き込む
③1バイトずつ16進文字列に変換
④文字列をファイル保存
なお、C++Builderは、デフォルトではcharがsignedなので、明示的にunsigned charを指定している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include <memory> // shared_ptr #include <sstream> // stringstream #include <iomanip> // "setw": #include <IOUtils.hpp> // TPath #include <jpeg.hpp> #include <pngimage.hpp> #include <gifimg.hpp> void __fastcall TForm1::Button1Click(TObject *Sender) { if( OpenPictureDialog->Execute() ) { // 表示テスト用、Image1に画像を読込・表示 Image1->Picture->LoadFromFile( OpenPictureDialog->FileName ); // ①MemoryStreamにファイルから画像を読み込む std::shared_ptr<TMemoryStream> ms( new TMemoryStream ); ms->LoadFromFile( OpenPictureDialog->FileName ); /* ImageからMemotyStreamに書き込んでも可 Image1->Picture->SaveToStream( ms.get() ); ms->Position = 0; // MemotyStreamの読み書き位置オフセットを先頭に */ // ②unsigned char 配列にMemoryStreamの内容を書き込む const long long size = (long long)( ms->Size ); unsigned char* buffer = new unsigned char[size+1]; ms->Read( buffer, size ); // ③配列要素の値を16進文字列に変換 std::stringstream ss; ss << std::setfill('0') << std::hex; // 0~Fまでは先頭0埋め、16進 for( long long i = 0; i < size; ++i ) { ss << std::setw( 2 ); // 二桁にセット ss << static_cast<int>( buffer[i] ); } delete [] buffer; // ④文字列をファイルに書き込む String HexStr = ss.str().c_str(); TFile::WriteAllText( L"test.txt", HexStr ); } } |
C:\Windows\Web\Wallpaper\Theme2\img7.jpgを読み込んだ結果:
保存されたテキストデータをエディタで見てみる。
テキストファイルから画像を読込、表示
復元手順は以下の通り。
①文字列のテキストファイルを読み込む
②2文字ずつ数値変換しunsigned char配列に格納
③MemoryStreamにデータを書き込む
④TImageにMemoryStreamから読み込む
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void __fastcall TForm1::Button2Click(TObject *Sender) { // ①ファイルから文字列を読み込む String AllText = TFile::ReadAllText( L"test.txt" ); // ②必要な長さのunsigned char配列を確保 AnsiString AText = AllText; // std::stringに格納するためAnsiStringに変換 std::string all_text = AText.c_str(); const long long count = static_cast<long long>( all_text.length() / 2 ); unsigned char* buffer = new unsigned char[count+1]; // ③文字列から2文字ずつ数値に変換し、unsigned char配列に格納 for( long long i = 0; i < all_text.length(); i += 2 ) { std::string tmp = all_text.substr( i, 2 ); buffer[i/2] = stoi( tmp, nullptr, 16 ); } buffer[count] = '\0'; // ④unsigned char 配列の値をMemotyStreamに書き込む std::shared_ptr<TMemoryStream> ms( new TMemoryStream ); ms->WriteBuffer( buffer, count ); ms->Position = 0; // Image2に画像データを読込・表示 Image2->Picture->LoadFromStream( ms.get() ); delete [] buffer; } |
復元して右側のImageに表示した結果:
FireMonkeyの場合
FireMonkeyでも、ほぼ同様なコードで可能。
違いは
・OpenPictureDialogがないので代わりにOpenDialogを使う
・Image関連のコードの修正(Pictureの代わりにBitmapを使う)
・jpeg/png/gif用のインクルード不要
だけ。
Image関連の違いは以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** Image1に画像画像を読込・表示 **/ // VCL: Image1->Picture->LoadFromFile( OpenPictureDilalog->FileName ); // FireMonkey: Image1->Bitmap->LoadFromFile( OpenDialog->FileName ); /** Image1からMemotyStreamに書き込む **/ // VCL: Image1->Picture->SaveToStream( ms.get() ); // FireMonkey: Image1->Bitmap->SaveToStream( ms.get() ); /** Image2にMemoryStreamから画像を読込・表示 **/ // VCL: Image2->Picture->LoadFromStream( ms.get() ); // FireMonkey: Image2->Bitmap->LoadFromStream( ms.get() ); |
対象ファイル
TImageに読込・表示できる画像ならおおおむね可能な模様。JPEGのほかPNG/BMP/GIF/TIF、さらにアイコンファイル(ico)でOKだった。メタファイル(emf)はVclでないと読み込めない模様。
ただし、C:\Windows\Web\Wallpaper\Theme1配下にあるWindows10壁紙用のjpeg画像は、VCLの場合だけ失敗した。テキストとして保存はできても、復元でTImageに読み込む時点で「サポートされていないストリーム形式」のエラーが出て、復元不能だった。理由は謎。
C++Builder10.2.3/Windows10(64bit/バージョン20H2)で確認。