[C++Builder]画像ファイルをテキストで保存、読込

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を指定している。

C:\Windows\Web\Wallpaper\Theme2\img7.jpgを読み込んだ結果:

保存されたテキストデータをエディタで見てみる。

テキストファイルから画像を読込、表示

復元手順は以下の通り。
①文字列のテキストファイルを読み込む
②2文字ずつ数値変換しunsigned char配列に格納
③MemoryStreamにデータを書き込む
④TImageにMemoryStreamから読み込む

復元して右側のImageに表示した結果:

FireMonkeyの場合

FireMonkeyでも、ほぼ同様なコードで可能。
違いは
・OpenPictureDialogがないので代わりにOpenDialogを使う
・Image関連のコードの修正(Pictureの代わりにBitmapを使う)
・jpeg/png/gif用のインクルード不要
だけ。
Image関連の違いは以下の通り。

対象ファイル

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)で確認。

[FireMonkey] ImageとBitmapの大きさを揃える

簡単そうなことなのに、ちょっとハマったのでメモ。

FireMonkeyでTImageに文字や図形などを初めて描く場合は、先にImage->Bitmapのサイズを指定する必要がある。
ImageとBitmapのサイズが異なってもよいが(WrapModeで描画結果が変わる)、ここはシンプルに同じ大きさを指定したい場合の話。たとえば以下のように書けば何の問題もない。

だがちょっとまだるっこしいので1行で書きたい。こうも書ける。

Imageの名前(この例では”Image”だが)を3回も書くのも面白くない。TImageにもTBitmapにもSizeプロパティがある。これを使おうと、以下のように書いてみると、エラーになる。

TSizeとTSizeF

FireMonkeyの場合、TImageに限らず、TMemoやTButtonなどTControl派生のコンポーネントは、位置やサイズ情報を実数値(float)でも持つ。VclのTSizeでなくTSizeF、TPointでなくTPointF、そしてTRectの代わりにTRectF
ところがTControl派生でないTBitmapの場合、Sizeプロパティは整数型のTSize。このため単純な代入は出来ない。
たとえば、四角形の領域を、指定されたビットマップから現在のビットマップへコピーするCopyFromBitmapメソッドの宣言。RectもPoint(X,Y)も整数になっている。

で、こうした実数型のクラスには整数型に変換するメンバ関数Round/Trunk/Ceilingが用意されている。それぞれ四捨五入、切り捨て、切り上げ。
 ・
というわけで、以下のように書き換えてみる。

やはりエラー。

TSizeFとTControlSize

実はTControl派生クラスのSizeプロパティはTSizeFでなくTControlSizeだった。
このTControlSizeはPixel値(整数)のメンバHeight / Widthと、TSizeF型のメンバSizeを持つ。ややこしい。

というわけで、ImageのサイズにImage->Bitmapのサイズを合わせるシンプルなコードは以下の通り。

微妙にシンプルでもない気もする。

AndoroidのToastをC++Builderから呼び出す

ラムダ式を使う。

FireMonkeyでPCのフォント一覧を取得する(手抜き版)

Vclでは、システムのフォント一覧は以下のように簡単に取れた。

Firemonkeyでは、同様の手段は提供されてない模様。
が、PCに限れば、プリンタから取得するという手段があるようだ。

MacOSでも同様に可能という情報もあるが(海外の掲示板)私は未確認。

なお、FireMonkeyでもシステムのデフォルトフォントを取得する手段は提供されている。

Windowsの場合、Vclと同様、フォント一覧に縦書き用(先頭に@)も取得してしまう。
縦書き用を排除したフォント一覧をFormのComboBoxに設定し、さらにデフォルトフォントを選択した状態にするのは以下のような感じだろうか。

C++Builder10.2.3 / Windows10(64bit)で確認。

続・C++Builderで異体字セレクタを使う サロゲートペアの場合

サロゲートペア文字にも異体字セレクタ対象の文字があることに、「C++Builderで異体字セレクタを使う」の投稿後に気付いた。

1.表示は簡単

基本は変わらない。対象文字のコードポイントの上位・下位バイトが分かっていれば、以下の数行のコードで表示できる。
なおTEditのフォントは、Windows8.1以降に標準バンドルされている游明朝を使っている。(オープンソースの花園明朝Aを使えば、より多くの異体字が表示できる)

表示結果

標準字形と異体字を並べてみる。

表示結果

2.両対応の汎用メソッド

実際にはコードポイントをいちいち探したり求めるのは面倒くさいので、実際の文字と異体字番号(ゼロから指定)から異体字を求める汎用メソッドを作ってみる。サロゲートかどうかも気にしなくてよい。
(以下のサンプルは、Stringが複数の文字からなる場合は、先頭文字の異体字を返す)

テストコード。
LabeledEditからIVS番号を取得して、文字を変換する。
2つのTEditのフォントは游明朝に設定している。

実行結果。

3.TRichEditの挙動が非サロゲートと異なる(?)

サロゲートペアの異体字をTRichEditに表示した場合、非サロゲートペアの場合と異なり、余分な空白が表示されないようだ。
異体字と標準字形を並べて表示してみると…。(左がTMemo、右がTRichEdit)

この挙動は謎。
なお、異体字の後ろにカーソルを置いた状態で、BackSpaceキーを押下すると〝先祖返り〟する挙動は、非サロゲート文字と変わらない。

C++Builder10.2.3とWindows10で確認。

C++Builderで異体字セレクタを使う

0.異体字セレクタとは

非常に大雑把かつ乱暴に言うと、山ほどある異体字を表示させるための仕組みのひとつ。
文字にいちいち固有の番号を割り当てるのではなく、番号に枝番号(符号)を付ける仕掛けである。
対応するフォントが必要だが、Windowsに標準添付の一部フォントでもそれなりに対応している。
詳しくはWikipediaなど参照してもらった方がいい。

Windows7からメモ帳でも利用可能になったので試してみた人も多いかもしれない。

1.表示は簡単

とりあえずよくある「1点しんにょうの辻」をTEditに表示する。
フォームにTEditとTButtonを一つずつ貼り付け、TEditのフォントはMS 明朝、フォントサイズは判別しやすいように30にしておく。
以下の数行のコードで、1点しんにょうの辻が表示される。

このように表示される。

2.仕組みとか

異体字セレクタは、通常の文字番号に、0xE0100から始まる枝番号を付けることで作動する。(最大で240までの枝番号が付けられるが、実際にそんなにたくさんの枝番号が定義された文字は現在ない。)
0xE0100は2byteで収まらないので、当然、wchar_tに格納できない。(C++Builderはwchar_tが2byte)
が、上位2byteは0xDB40で固定。下位2byteに0xDD00~0xDDEFまでのいずれかを格納してやればよい。

なお存在しない枝番を指定した場合、単に標準の文字が表示される。

3.フォントによって異なる枝番号

どの枝番号にどの異体字形が割り振られているかは、実はフォントによって違う。(正確に言うとフォントによって採用している字形コレクションが違う)。
異なるフォントを設定した3つのMemoに、「辻」の異体字の枝番を順に表示してみる。

実行結果

1点しんにょうの辻は、IPAmj明朝の場合は3番目、游明朝およびMS明朝なら1番目に割り振られているのが分かる。

いったん表示した後、フォントを変えると、文字が変化してしまう危険がある。

4.1文字で3文字分

文字数を数えるプログラムなどでは注意が必要。
SystemのLength()でもstd::wstringのlength()でも、3文字になる。
サロゲートペアの判定はIsSurrogateで簡単だが、異体字セレクタの場合はもう一工夫必要になるはず。

5.TMemoとTRichEditで挙動が異なる

異体字セレクタ文字は、TMemoとTRichEditで挙動・表示が違う。
1点しんにょうの辻と通常の辻の2文字をそれぞれ表示してみる。

実行結果。左がTMemo, 右がTRichEdit。

このようにRichEditは枝番分の文字の空きが取られてしまう。

ちなみに、この空きをキーボードで削除すると

枝番情報が消えるので最初の文字が通常の「辻」に戻ってしまう。

また、Memoの方でも、1点しんにょうの辻の後ろにカーソルを置きBackSpaceキーを押すと、文字は消えないがやはり先祖返りする。



以上、基本的にVclとFireMonkey共通。(ただしFireMonkeyにはRichEditはない。)
C++Builder10.2.3 / Windows10 で確認。

TDateの++演算子、欠落した日付の列挙

TDateには++演算子が使えることを今さら知った。値を一日ずつ進めてくれる。
比較演算子(<)もあるので、std::setやmapにも格納できる。

Vclアプリケーションでのテストコード。

出力結果:
2018/10/31
2018/11/01

もちろん閏月判定もやってくれる。

出力結果:
2015/02/28
2015/03/01
—————-
2016/02/28
2016/02/29
2016/03/01

前置と後置の評価タイミングも当然異なる。

出力結果:
postfix : equal
prefix : differ

もともと与えられた日付の一覧から、欠落した日付を列挙する方法を調べていて気付いたので、以下、そのサンプル。

出力結果:
2016/02/26
2016/02/28
2016/02/29
2016/03/04
2016/03/05
2016/03/08

なおTDateはTDateTimeのtypedefなので、TDateTimeでも同様に++演算子で1日ずつ値が進む。

出力結果:
2018/11/30 14:35:21
2018/12/01 14:35:21
2018/12/02 14:35:21

Vcl/FireMonkey共通。
C++Bulider10.2.3 / Windows10で確認。

ページの遷移にスライド効果を付ける[FireMonkey]

こういう感じのやつ。


FireMonkeyなのでWindows(32bit/64bit)でもAndoroidでも同じ処理。(iOSおよびiPhoneでも同様に動くはずだが、実機がないのでテストしていない)

FireMonkeyにはVclのPageControlはないので、TabControlを使う。
Vclとはかなり勝手が違う。ただしコードは1行も書く必要はない。

①FormにTabControlを貼り付け、右クリックメニュー[TTabItemの追加]からページを作成する。
slide%e5%8a%b9%e6%9e%9c_01%e3%83%bbtabcontrol01

②Tab表示を消す。TTabControlのオブジェクトインスペクタでTabPositionをNoneにする。
slide%e5%8a%b9%e6%9e%9c_02%e3%83%bbobjectinspector01

③Tab表示が消えた。
slide%e5%8a%b9%e6%9e%9c_03%e3%83%bbtabcontrol02

ここからが本題。

④フォームにActionListを貼り付けてダブルクリック。
メニューから[標準アクションの新規作成]。
slide%e5%8a%b9%e6%9e%9c_04%e3%83%bbactionlist01

⑤[標準アクションクラス]ウィンドウが出るので[タブ]の中の[NextTabItem]を選択
slide%e5%8a%b9%e6%9e%9c_05%e3%83%bbactionlist02

⑥さらに[標準アクションの新規作成]から今度は[PrevTabItem]を選択
slide%e5%8a%b9%e6%9e%9c_06%e3%83%bbactionlist03

⑦[NextTabItem]のオブジェクトインスペクタで[TabControl]プロパティに[TabControl1]を選ぶ。[PrevTabItem]も同様。ここでTransitionプロパティの値が[Slide]になっていることを確認する。ここがSlide効果の指定となる。
slide%e5%8a%b9%e6%9e%9c_07%e3%83%bbactionlist04

⑧TabItem1/TabItem2にそれぞれButtonを貼り付け、Button1のオブジェクトインスペクタで、[Action]にNextTabItemを選ぶ。
slide%e5%8a%b9%e6%9e%9c_08%e3%83%bbactiontobutton01

⑨Button1のキャプション(Textプロパティ)が自動的に[Next]に変わる。
slide%e5%8a%b9%e6%9e%9c_09%e3%83%bbactiontobutton01_2

⑩TabItem2にうつり、Button2の[Action]にこんどは[PrevTabItem]を選ぶ。
slide%e5%8a%b9%e6%9e%9c_10%e3%83%bbactiontobutton02_2

⑪Button2のキャプション(Textプロパティ)が自動的に[Prev]に変わる。
slide%e5%8a%b9%e6%9e%9c_11%e3%83%bbactiontobutton02_2

以上で終了。

各Actionの[Transition]プロパティのSlide/None選択だけで、ページ遷移時のSlide効果の有無を切り替えることが出来る。

なお、タブの標準アクションにはTChangeTabActionがあり、3ページ以上有る場合、ページ移動先の指定が可能。

C++Builder Berlin Update1で作成、Windows7(64bit)/Android4.4.2で動作確認。

ディレクトリ・ファイル操作の新旧方式

C++Bulider(RadStudio)のディレクトリやファイル操作に関しては、昔ながらのSystem.SysUtils配下の方法と、新しいSystem.IOUtils配下の方法がある。
まったく同一ではないが、基本的な操作は、どちらも似たようなことが出来る。

どちらでもいいといえば良いのだが、クロスプラットフォームを前提に開発するなら、新しいIOUtils配下にしかない関数群を使った方が便利なことが多いと思う。

どうせなら、Windows単体の開発でも、ふだんから新方式を使って慣れておいた方がいいというのが個人的な考え。

主な操作のそれぞれのやり方をまとめてみた。
(引数のpathはフルパスのディレクトリ名またはファイル名)

以下、VclとFiremonkey共通。

使い方
Includeファイル名
SysUtils System.SysUtils.hpp
IOUtils System.IOUtils.hpp
ファイル名からドライブ・ディレクトリ部分を得る
SysUtils ExtractFileDir( path )
IOUtils TPath::GetDirectoryName( path )
ディレクトリ名とファイル名からフルパスのファイル名を作る
SysUtils IncludeTrailingPathDelimiter( path )+filename
IOUtils TPath::Combine(path,filename)
ディレクトリの存在確認
SysUtils DirectoryExists(path)
IOUtils TDirectory::Exists(path)
ファイルの存在有無
SysUtils FileExists(path)
IOUtils TFile::Exists(path)
ディレクトリの作成
SysUtils CreateDir(path)
IOUtils TDirectory::CreateDirectory(path)
空のディレクトリを削除
SysUtils RemoveDir(path)
IOUtils TDirectory::Delete(path)
ディレクトリを中身ごと削除
SysUtils (自前で実装の必要あり)
IOUtils TDirectory::Delete(path,true)
ファイルを削除
SysUtils DeleteFile(path)
IOUtils TFile::Delete(path)

C++Builder Berlin Update2 / Windows7で確認。

TRectの便利関数

どのバージョンからか把握していないが、TRectにはいろいろな関数(やコンストラクタ)が実装されている。
Vclの一昔前のサンプルだと、こうした関数を使わない(ちょっと面倒くさい)処理が紹介されていたりする。
というか自分自身も昔の名残で、ついだらだらとコードを書きがちだったり。

Helpの一覧だと、実装が書いてあるだけで(RadStudio10.2 Tokypリリース直後時点)、まあ読めば分かるといえばそうなのだが、忘れがちなので一覧にしてみた。

System配下なので、Vcl・FireMonkey共通。(Delphiでも同じように使えるはず。)
以下、RはTRect、PtはTPointの変数。

動作 実例
PointからRectを作成 R = TRect(Pt1,Pt2)
Rectの値を正しくする R.Normalize()
位置をずらす R.Offset(X,Y)
R.Offset(Pt)
Rectの始点を指定する R.SetLocation(X,Y)
R.SetLocation(Pt)
Rectの始点を得る Pt = R.GetLocation()
Rectに点が含まれるか bool b = R.Intersects(Pt)
他のRectと重なっているか bool b = R1.Overlaps(R2)
他のRectを完全に含むか bool b = R1.Contains(R2)
全方向に大きく(小さく)する R.Inflate(X,Y)
R.Inflate(l,t,r,b)
中心点を得る Pt = R.CenterPoint()

FireMonkeyのイベント等で使われるTRectF/TPointFでもおおむね同様の関数が使える。(一部名前が違ったりするようだが、精査していない)