文字列をフォームファイル(DFM)形式に変換する[C++Builder]

「えーと●●●って選択肢のあるRadioGroupはどのフォームだったっけ」とか探したくなることがたまにある。
C++Builderでは(Delphiも)フォーム情報がテキスト形式(DFM)で保存されているので、ソースのあるフォルダでgrepかければ一発、のはずなのだが、英数・記号以外の文字は数値変換されているので、見つからないことがままある。

たとえば変な例だが「叱るButtonAtype」みたいなCaptionはdfmファイルに
Caption = #21489#12427'Button'#65313'type'
と保存される。(”A”も全角なので数値変換されている)

たぶん
・半角英数・記号はそのまま(シングルクオーテーション囲み)
・それ以外は文字コード番号の10進数表記(個々の先頭に”#”)
・さらにサロゲートペアは分割して変換(例の「叱(U+20B9F)」は「#21489#12427」)
というルールっぽい。

というわけで、変換関数を作った。
以下、コード。

[boost]サイト上のシリアライズデータをダウンロードせずにデシリアライズする[C++Builder]

boost::serializatioでシリアライズしたデータがweb上(例えば自分のサイト)にあり、PC上のプログラムから利用したい場合、一時的にダウンロードしてデシリアライズすれば簡単なのだが、後で一時ファイルを削除する手間が生じる。

以下、ダウンロードしないまま利用する方法をメモ。

手順は以下の3ステップ。
(1)UrlからデータをTStreamに読み込む
(2)TStreamからバイト列(char配列)に書き込む
(3)バイト列からデシリアライズ(boost::iostreamsを使う)

C++Builder10.2.3 (Boost1.55.0) / Windows10(22H2)で確認。

C++でIPアドレスをソートする

さっと検索した範囲だとサンプルが見つからず、単純だけど自分で作ったのでメモ。例によって力技。数値変換せずに文字列比較だけ(ユニコード前提)。

データは、他の自サイトに最近1ヵ月ほどでアクセスのあったGooglebotやGoogleの他サービスのIPアドレス。(ちなみにいずれも全リストが公開されている。Googlebot / 他サービス使用IP。参照した記事はこちら )

比較関数

まず比較関数を用意する。とりあえず、よく使うstd::wstring版。(不正な文字列とかの対策は足りないかも)

(降順ソートは比較を逆にするだけ。)

テストコード

出力結果

64.233.172.180
66.102.8.152
66.102.8.158
66.249.64.152
66.249.66.86
66.249.68.1
66.249.68.5
66.249.68.6
66.249.68.9
66.249.68.13
66.249.68.17
66.249.68.20
66.249.68.21
66.249.68.24
66.249.68.25
66.249.68.28
66.249.68.30
66.249.72.239
66.249.72.241
66.249.72.243
66.249.74.16
66.249.79.20
66.249.79.133
66.249.79.135
66.249.79.137
66.249.79.139
66.249.79.141
66.249.79.143
66.249.79.145
66.249.79.147
66.249.79.153
66.249.82.16
66.249.82.18
66.249.82.20
66.249.82.26
66.249.82.28
66.249.82.30
66.249.82.176
66.249.82.178
66.249.82.180
66.249.82.186
66.249.82.188
66.249.82.190
66.249.83.17
66.249.83.19
66.249.83.21
66.249.88.40
66.249.88.42
66.249.88.44
66.249.88.45
66.249.88.46
66.249.88.47
72.14.199.68
72.14.199.70
72.14.199.72
72.14.199.73
72.14.199.76
72.14.199.77
72.14.199.79
72.14.199.80
72.14.201.152
72.14.201.153
74.125.150.1
74.125.150.19
74.125.150.20
74.125.150.23
74.125.150.24
74.125.150.25
74.125.150.27
74.125.150.28
74.125.150.31
74.125.212.235
74.125.212.237
74.125.212.239
74.125.212.241
74.125.212.243
74.125.214.16
74.125.214.18
74.125.214.20
74.125.214.26
107.178.234.23
107.178.234.85
107.178.234.88
107.178.234.92
107.178.234.144
107.178.234.147
107.178.234.155
107.178.234.156
107.178.234.158
130.211.54.158
193.186.4.134
193.186.4.152
193.186.4.153

汎用版

これだけだと芸がないので、stringとかu16stringとか一通りの文字列型に対応する汎用版。
関数テンプレートの特殊化ってこういう使い方でいいのかよく分かってないが。
区切り文字の取得以外は、前のコードとほぼ同じ。

呼び出しはこんな感じ。

区切りの文字定数取得は、c++14以降だとbasic_string_viewを使ってもう少しすっきり出来るらしい。

C++Builder10.2.3 / Windows10(22H2)で確認。

画像のフェードアウトいろいろ[VCL]

C++Builderで、表示した画像をじわじわ消していくのはFireMonkeyならほぼコードいらずで可能だが、VCLだと自前で処理する必要がある。いろんな方法を試してみたので暫定まとめ。使いどころはよく分からないが。

0.事前準備

TFormにTImageを置く。
ちらつき防止のためFormのDoubleBufferedプロパティをtrueにしておく。

1.ランダムにPixel操作(力業)

画像がぽつぽつと白くなっていく効果。
画像のPixel分の数値配列をシャッフルして、順番に白くしていくだけのシンプルな力業だが、実際にやってみると一手間かけないと自然な効果が出ない。

基本形

まず基本形。
手順は単純で
 ①画素数分の数値配列を作ってシャッフル
 ②シャッフルした数値のPixelをループで白くする
だけ。

多めに変化

上記のコードだと、PCの性能や画像サイズにもよるがかなり遅い。複数のPixelを白くするよう改良してみる。

実際にやると目の錯覚(たぶん)でだんだん遅くなっているように感じる。

変化量を増やしていく

白くするPixel数がどんどん増えるように改良。

だいぶ自然な感じになった。

Bitmap以外の場合

ここまで使ったImage->CanvasはBitmap以外はアクセスできない。
他の画像形式はBitmapに代入して行うのが安直だけど手軽。

まとめコード

以上のまとめコード。

2.色を薄くしていく

画像が段々薄くなるパターン。
RGBをいじるだけ。ScanLineを使えるので、ランダムなピクセル操作よりは高速。

かなりスムースになる。

3.アルファチャンネルを使う

TBitmapのアルファチャンネルを設定し、だんだん薄くしていく手もある。
(画像タイプみて変換する処理は省略。TPNGImageだと別の方法があるらしい)

実行結果。

4.Drawメソッドの第4引数を使う(お手軽)

Canvas->Drawメソッドで、第4引数の不透明度(Opacity)を指定する方法。
これはかなり手軽。TImageの画像がどんな形式でも、変換する必要はない。

C++Builder10.2.3 / Windows10(21H2)で確認。

TImageに表示されている画像の種類を知る[VCL]

C++Builder(Delphi)のTImageにはJpegやPng、ビットマップなど多様な画像が読み込めるが、あとから画像種類を知りたい場合
 Image->Picture->Graphic->ClassName()
で画像のVCLクラス名が取れる。今さら知ったのでメモ。

C++Builder10.2.3 / Windows10(21H2)で確認。

TStringListからvector / TStringDynArrayからvector[C++Builder]

C++Builder(およびDelphi)でよく使われるTStringListとかDynamicArrayを、std::vectorに変換したい場合がよくある。
この場合、いちいちループを回さなくても、STLライクなイテレータ操作が使える、ということを今さら公式ヘルプで知ったのでメモ。

TStringListからvector

TStringListからstd::vectorは以下のように一発で変換可能。

既存のvectorにはassignで代入。

スマートポインタでも問題ない。

文字列リストの基底クラスであるTStringsも同様。
おなじみのフォント一覧を取得するケース。

ソートされた結果を最初から得たい場合は、std::setをいきなり初期化してもいい。

 

DynamicArrayからvector

Delphi動的配列型のDynamicArrayも似たような代入が可能。

IOUtilsのTDirectory::GetFilesはディレクトリ内の全ファイルを一発で取れる便利な関数だが、この返値TStringDynArrayDynamicArray<String>のtypedefなので、同様にvector等に変換できる。

 

以上、C++Builder10.2.3 / Windows10(21H2)で確認。

vectorの重複要素をソートせずに削除する[c++]

vectorの重複要素の削除は、まずソートしてから std::unique / erase という定番手法があるが、現在の要素の順番を崩さないまま行いたい場合もある。

こうしたケース用に、何か定番の方法があるのかと調べたら、こちらのサイトを見ただけでも実にいろいろなやり方があるらしい。

とりあえず、要素の大きさとか重複率とか気にせず、速度や効率にこだわらなければ、結局 std::set を使うベタな方法が個人的には一番分かりやすかった。

なおstd::setのメンバ関数countは1か0を返すだけ。std::findを使うよりシンプルに書ける。

テンプレート関数を作っておくと、どんな型を突っ込んだvectorでもだいたい使えるので楽かもしれない。(というか自分用に作った)

ついでに std::remove_if と c++11以降のラムダ式も使ったバージョン。std::deque版。

これは、setのinsert関数の返値を使うともっとシンプルに書ける、とこちらのサイトで知った。

setのメンバ関数 insert は、引数1つのバージョンだと std::pair<iterator,bool>を返す。挿入に成功するとsecondにはtrueが入る。なるほど便利。

   
C++Builder10.2.3で確認。

[Vcl]AcrobatDCを利用したPDF印刷が失敗する対策

Adobe Acrobatはクラウド版(Acrobat DC)になってから使っていなかったのだが、アプリケーションの印刷ダイアログで選択して使用すると、PDF作成に失敗するようになっていた。

以下は、四角形を1つ描くだけの単純なテストプログラム。

PrintDialogでAdobe PDFを選択すると、下記のダイアログが出たまま止まる。

Microsoft Print to PDF および JUST PDF4 だと問題なくPDFが作成された。

Acrobat DCは最近のアップデートで、どうやら「文書名」が空のままだとダメになったらしい。
というわけで、以下のようにTitleを適当に付加すると正常にPDFが作成された。

C++Builder10.2.3/Windows10(64bit/バージョン21H2)で確認。

Spotify APIでアーティストの全楽曲を取得する

Pythonの勉強がてらSpotify APIをいじっていて、アーティストの全楽曲を取得するのにちょっと苦労した。自分の知識の整理もかねて方法をメモ。

0.基本と準備

Spotifyのidとalbumとtrack

Spotifyでは、アーティスト、楽曲、アルバム、プレイリストなどは全て固有のidで管理されている。ブラウザでアクセスした際の
 https://………../playlist/xxxxxxxxxxxxxxxx?…..
 https://…………/album/xxxxxsxxxxxxxxxx?……
のxxxxxの部分がid。

楽曲(track)を取得する際に、基本的に把握しておきたい(と思う)ポイント。

  • trackはすべて”album”に属する。albumはいわゆる「アルバム」だけでなく、シングルも含む(配信シングルも収録trackひとつだけのalbumに属する)
  • 複数のalbumに収録された同一曲は、それぞれ別trackとして管理される
  • albumデータからはtrack情報が取れるが、trackデータから逆引きは出来ない

Spityfy APIを使う準備

Spotify APIを使うためのid取得や認証方法とかはネットに山ほど情報があるので省略。Spotifyの無料ユーザーでもAPIは使える。
以下、お約束のコード。

以下、出力例は、延べ1400曲以上らしい松田聖子(artist_id:3E5NLQpQbd0eJ18XO9zC0h)で示している。

1.アルバム/シングルから全楽曲を取得

基本的な手順は
・artist idからartist_albumsAPIでalbumのid一覧を取得
・各albumのidからalbum_tracksAPIでtrack(楽曲)データの一覧を取得
・各trackデータからタイトルを取得
となる。

アルバムから全楽曲

いわゆるアルバムから全曲名を取り出すには、まずartist_albums APIのalbum_typeにalbumを指定する。
なおalbum_typeに指定できるのは以下の4種類

  • album
  • single
  • appears on
  • compilation

(‘appears on’と’compilation’については後述)

以下、コード。

出力結果サンプル(id:3E5NLQpQbd0eJ18XO9zC0h)

シングルから全楽曲

artist_albums APIのalbum_typeに’single’を指定するだけで、あとはアルバムと変わらない。
カップリング曲も含めたtrackが取得できる。
以下、両方対応のための関数を利用したパターン。

出力結果サンプル(id:3E5NLQpQbd0eJ18XO9zC0h)

アルバム・シングルの全曲

上記で取った楽曲名を合わせるだけ。

出力結果サンプル(id:3E5NLQpQbd0eJ18XO9zC0h)

2.他アーティスト作品への参加曲や各種コンピ収録曲

album_typeに’appears_on’を指定すると、アルバム・シングル以外にアーティストが参加した、他アーティスト作品(album)のデータが取れる。(英語の解説)
実際に確認した範囲では
 (1)複数アーティストが参加した、いわゆるコンピ
 (2)特定テーマによるベストアルバム的なもの
 (3)他アーティストの作品参加
の少なくとも3種類が取れる模様。(1)の場合、albumのartist名は「ヴァリアス・アーティスト(Various artist)」になるようだ。

ともかく、このalbumには、対象アーティストが参加していないtrackも含まれている(ことが多いはず)ので、各trackのartist_idをチェックする必要がある。

以下、シンプルにtrack名を取得するコード。ハイライト部分がアルバムやシングルの処理に追加したコード。

出力結果サンプル(id:3E5NLQpQbd0eJ18XO9zC0h)

3.「コンピレーション」楽曲も取得

album_typeに’compilation’を指定すると「コンピレーション」albumのデータが取れる。これは上記3種類で取れるalbumには含まれないようだ。
Spotifyで言うcompilationとは、「The Greatest Hits of xxxx」みたいな、アーティストの代表曲をまとめたものを指すらしい(英語の解説)。2.と3.で取得した以外の楽曲は取れないと思う(compilationだけ配信していれば別だが)。
ともかく、trackの取得はアルバムやシングルと同じ。

4.全楽曲を一気に取得し csv出力

artist_albums APIのalbum_type指定を省略すると、上記4種類のalbumデータ全部が取れる。
これで、2.のappears on と同じ方式で絞り込めば「全ての楽曲名」が取得できる(はず)。

出力結果サンプル(id:3E5NLQpQbd0eJ18XO9zC0h)

ただ、例えば同じ曲に、artist単独によるものと、他artistとのコラボとの両方がある場合、この単純な方式だと漏れてしまう。

なので、
 ・複数アーティスト名義(コラボなど)は楽曲名にアーティスト名を付加
さらに
 ・楽曲タイトルと、それぞれの収録アルバム・シングル名すべて
 ・楽曲を人気順に並べ替える(複数album収録曲は最も人気が高いものを採用)
 ・まとめたデータを最後にcsv出力
してみる。
以下、コード。artist名を取るためのartistAPIと、楽曲の人気(popularity)データを取るためのtrackAPIを新たに使っている。

出力結果サンプル(id:3E5NLQpQbd0eJ18XO9zC0h)

単純に曲名でリストアップした場合に比べて、楽曲名がひとつ増えている。(同名の曲で、単独名義のものと、本人含む複数名義のものがあった)。

出力したcsvはこんな感じ(一部、2022年2月中旬取得分)。

なおalbumデータ内のalbum_typeは、artist_album APIで指定したalbum_typeとは一致しないので注意が必要。API指定のalbum_typeに対応するのはalbum_groupの方になっている。ややこしい。

また、上記の複数artistの確認部分で、少々まだるっこしい処理になっているのは、API利用回数を少しでも減らすため。短時間で大量にAPIを呼び出すと制限に引っかかる(公式の英語解説)。実際に試行錯誤する際には、取得データを適宜、データ保存して、それを呼び出すようにした方がいいようだ。

以上、Python ver3.10.2で確認。

[c++]文字列から1文字ずつ、サロゲートペアと異体字セレクタを考慮しつつ取り出す

Unicode文字列から「1文字ずつ」を取り出す場合、異体字セレクタやサロゲートペアを考慮に入れる必要がある。
以下、日本語環境での話(モンゴル文字の異体字セレクタ等は未対応)。

1.std::wstring(2バイト環境)の場合

以前の記事で書いたとおり、異体字セレクタは文字の末尾に4byte分の情報が付加される。(C++Builderで異体字セレクタを使う)

このため、wchar_tが2byteの場合、wstringの「1文字」が取り得るバイト数は以下の4通りになる。

「1文字」の4パターン バイト数
基本文字(基本多言語面) 2byte
サロゲートペア 4byte
基本文字の異体字セレクタ付 6byte
サロゲートペアの異体字セレクタ付 8byte

以下、文字列から、1文字ずつをstd::vectorに格納するメソッド。

(wchar_tが16bit/unsignedであることを前提にしたコード)

2.std::string(utf-8)の場合

utf-8文字列をstd::stringに突っ込んで使う場合もあるので(自分自身はやらないが)、ちょっと調べた。 以下のような方法で取り出せる(たぶん)。

utf-8文字は、先頭バイトを見ると1文字分のバイト数が分かるので、サロゲートペア判定は特に必要ない。 だが、そこで取れるバイト数には異体字セレクタ情報は反映されないので、自前で処理する必要がある。

文字に付加される異体字セレクタは計4byte分で、漢字用のIVSでは以下の範囲を取るようだ。

バイト順 値の範囲
0xF3 (固定)
0xA0 (固定)
0x84~0x87
0x80~0xBF
(0x80~0xAF)

4バイト目は、3バイト目が0x87の時に限り最大で0xAF(たぶん)。

以下、utf-8文字列の格納されたstd::stringから1文字ずつ取り出すメソッド。
なお、IVS最初の0xF3は、単体でも1byteで文字を表現するので、まず2byte分を判定する必要がある。

3.サンプルプログラム

C++Builderによるサンプルプログラム。

TFormにTEditとTMemoをひとつずつ、TButtonを2つ貼り付ける。
TEditとTMemoのフォントは游明朝、サイズを40にしておく。 TEditに以下の文字列を入れておく。

文字列の漢字部分は、最初の「辻」から順に
・基本文字
・異体字セレクタ
・基本文字
・サロゲートペア
・サロゲートペア
・サロゲートペアの異体字セレクタ付
英字は小文字のaが全角、大文字のAは半角。

3-1.std::wstringを使った例

Button1Clickを次のように実装。

結果:

3-2.std::stringを使った例

C++BuilderにはUTF8String型があるので、変換にはそれを使っている。
Button2Clickを以下のように実装。

結果:
 
以上、とりあえずのテストでは上手くいっているが、何か見落とし等があるかも。