雑誌のテーマからプロジェクト実践へ
該当記事に関連するサービス・技術ページ
VCLアプリケーションを最新の Windows-クライアントで運用していると、遅かれ早かれ同じ症状に遭遇します: アイコンが125%/150%/200%でぼやけたり、ほつれたように見えたり、灰色の縁が付いたりします。まさにここでVCL High-DPI Iconsの問題が実務的になります。High-DPI自体が新しいからではなく、問題は通常、ターミナルサーバー、ノートPCのドック切替、あるいはモニターごとにDPIが異なる状況で日常的に顕在化するからです。
核心はほとんどの場合「PNGが壊れている」ことではなくパイプラインです: アイコンはどこから来るのか(リソース、ファイル、SVG、フォント)、どのサイズで提供されるのか、どのようにしてTImageListに渡されるのか、そして誰がいつどのようにスケーリングするのか。VCLでは区別すべき用語が複数絡みます: DPI-Awareness(Windowsがアプリをスケールするのか、アプリ自身がスケールするのか)、Per-Monitor-DPI(各モニターが個別でよい)、およびImageList-Strategie(複数解像度を保持するのか、ランタイムでラスター化するのか)です。
この記事では意図的にUIデザインの議論は扱わず、運用に耐える明確なアプローチを示します: 実行時にアイコンをスケールするが、ピクセルが潰れてしまわないようにし、アルファチャンネルを保持し、DPI切替時にちらつきや誤った画像サイズが発生しないようにする方法です。さらに落とし穴、デバッグのヒント、そして追加工数の見合いについての現実的な評価を付記します。
なぜピクセルが潰れるのか: VCLにおけるスケーリングチェーンを理解する
ぼやけたアイコンの最も一般的な原因は、誤ったタイミングでの一度きりのダウン/アップスケーリングです。レガシーVCLでの典型的な流れは次の通りです:
- アプリケーションがアイコンを16×16または24×24だけで提供する。
- WindowsまたはVCLが20×20 / 32×32 / 48×48にスケールする。
- スケーラは写真には許容できる補間を使用するが、ピクセルグラフィックのエッジをぼかしてしまう。
- さらに透明度(アルファ)がマスク論理に押し込まれたり、複数回変換されたりする。
特に厄介なのはスケールが重ねて発生する場合です: 例えばImageListが既にスケール済みのビットマップを返し、WindowsがDPI-UnawareやシステムDPIのためにさらに上書きスケールすると、結果は二重のぼかしになります。
プロジェクトで過小評価されがちなもう一つの点はスケーリングのタイミングです。Per-Monitor-DPI(PMv2、すなわちPer-Monitor-DPI-Awareness v2)では、ウィンドウがモニターを移動したりリモートデスクトップクライアントがDPIを動的に調整したりすると実効DPIが変わり得ます。その際にTImageListやキャッシュが正しく再構築されないと、突然アイコンが誤ったサイズや誤ったラスタで表示されます。
High-DPI環境のTImageList: 実運用での典型的な落とし穴
TImageListは歴史的に小さなビットマップ用に設計されており、固定のサイズ、インデックス、比較的堅いメモリ管理ロジックを前提としています。High-DPI環境では、実務上の落とし穴が生じます:
1) Fest verdrahtete Width/Height
多くの VCL フォームはデザイン時に ImageList.Width/Height を設定したままにします。しかし 150% では Windows が例えば 16×16 を 24×24 にしたいことがあります。リストが 16×16 のままだと、描画側でクリップされるか別箇所でスケーリングされてしまい、どちらも望ましくありません。
2) PNG-Alpha und Maskenlogik
状況により Delphi-Version と VCL コントロールの組合せによっては混在した取り扱いになることが多く、PNG のアルファは内部で一部が 32-bit Bitmap として保持される場合もあれば、Mask+Color として扱われる場合もあります。PNG → Bitmap → ImageList → Draw といった複数回の変換を行うと、灰色のハローや硬いエッジが発生します。この現象は背景依存で、暗いツールバー上では明るいパネル上よりも目立ちます。
3) DPI-Wechsel zur Laufzeit: Caches, Handles, OwnerDraw
一部のコントロールは画像表示をキャッシュしたり、DPI が確定していない時点で ImageList のハンドルを取得したりします。特にツールバー、TreeViews/ListViews、OwnerDraw のシナリオでは、DPI 変更後に再描画や RecreateWnd が行われるまで、断続的に誤った画像サイズや空のアイコンが表示されることがあります。
4) Terminalserver und Remote-Desktop als Realitätstest
アプリが RDP 経由で利用される場合、DPI の変更やセッションのリコネクトは珍しくありません。まさにその環境で、不安定な ImageList 戦略が破綻します。ユーザーはリコネクト後にぼやけたアイコンや誤ったスケーリングのツールバーを目にすることがあり、ローカル環境では問題がなかったのに見える、という現象が起きます。
Sauberer Ansatz: Mehrere Auflösungen bereitstellen statt brutales Upscaling
最も重要な決定は概念的なものです:実行時に単一の基準画像からアイコンを拡大する(例: 16×16 → 32×32)か、複数のネイティブ解像度を用意して DPI に応じて適切なものを選択するか、どちらを採るかです。
実務ではほとんどの場合、複数解像度を用意する方が優れています。ソースがベクターベース(SVG、Icon-Font)であるか、あるいは本当に中程度の拡大率しか必要としない場合にはアップスケーリングも選択肢になります。しかし小さなラスタ画像を大きく拡大するとエッジの品質が失われ、現代のディスプレイではその劣化が即座に見て取れます。
VCL の世界では、このアプローチに関して現在以下の二つの要素が重要です:
- TImageCollection: 複数のサイズ/バリアントの画像を格納するコンテナ。
- TVirtualImageList: そこから実行時に現在必要なサイズの ImageList を生成し、DPI 変更に対応する。
これで全ての問題が解決するわけではありませんが、問題を適切な位置に移動させます:画像ソースを明確に定義し、スケーリングと選択が一貫して行われるようになります。
VCL High-DPI Icons zur Laufzeit skalieren: Wann es sinnvoll ist (und wann nicht)
アイコンを実行時にスケーリングすることには正当な理由があります:
- プラグインフォルダ、顧客固有のブランディングパッケージ、設定パッケージなどからアイコンを動的に読み込む場合。
- ICO、PNG、SVG といった様々なソースを統一されたパイプラインで扱いたくて、すべてのバリアントをビルド時に束縛したくない場合。
- プログラムでアイコンを生成する場合(ステータスバッジ、オーバーレイ、合成シンボルなど)。
固定セットのクラシックなツールバーアイコンしかない場合に、ランタイムでのスケーリングは合理的ではない。その場合、最も保守負担が少ない方法は、複数の解像度をきちんと用意してVCLに選ばせることだ。
ランタイムスケーリングを行う場合は、明確なルールを設けること:
- 決して既にスケーリングされたビットマップをさらにスケールしてはいけない。常にマスターソースから始める(理想的にはベクターまたは高解像度)。
- ターゲットサイズとDPIごとのキャッシュ、さもないとペイントごとに再スケールしてしまい、CPU負荷が増え、描画がカクつく。
- アルファを保持:変換を最小限にし、32-bit RGBAに設定し、背景で合成(ラスタライズ)してしまわないこと。
Pragmatische Architektur: Icon-Pipeline als eigener Baustein
大規模なアプリケーションでは、この問題をあちこちに散らさず、小さなパイプラインを構築する価値がある。フレームワークである必要はなく、むしろ明確な責任領域として設計するのが良い:
- アイコンのソース:マスターアセットはどこから来るか(リソース、ファイル、データベース、API)?
- Rasterizer/Scaler:マスターからターゲットサイズをどのように生成するか(補間、必要に応じてSVGレンダリング)?
- キャッシュ:キー(Icon-ID、ターゲットピクセル、DPI、Theme)とライフサイクル(DPI変更、テーマ変更、パッケージ変更時の無効化)。
- Consumer-Adapter:結果をVCL構造(TImageList/TVirtualImageList、OwnerDraw、PaintBox)にどう取り込むか?
利点:DPI関連のバグを一箇所で再現してデバッグできるため、40個のフォームを巡って「どこで再スケールされているか」を探す必要がなくなる。
DPI-Wechsel korrekt behandeln: Ereignisse, Rebuild, Repaint
Unter Windows sind DPI-Wechsel ein eigener Lebenszyklus. In der VCL gibt es je nach Version und DPI-Awareness mehrere Ereignisse/Mechanismen, aber das Grundprinzip bleibt:
- Wenn sich die DPI des Fensters ändert, müssen bildbasierte Ressourcen, die pixelgenau sein sollen, neu bereitgestellt werden.
- Wenn du ImageLists dynamisch füllst, reicht ein bloßes Invalidate oft nicht — du brauchst ein 再構築 der Images in der neuen Zielgröße.
Ein praxistaugliches Muster ist: Auf DPI-Änderung (z. B. Form-Scale/Monitorwechsel) invalidierst du den Icon-Cache für diese DPI und baust die betroffenen ImageLists neu auf. Wichtig ist, dabei nicht in Paint-Events zu skalieren, sondern in einem kontrollierten Update-Block (Toolbar.BeginUpdate/EndUpdate, ListView-Redraw aus, dann wieder an). So vermeidest du Flackern und halbfertige UI-Zustände.
Warum TImageList so oft unscharf wird: Interpolation, DPI-Rundung, Kanten
Wenn du einmal verstanden hast, dass nicht DPI das Problem ist, sondern Interpolation plus Rundung, lassen sich viele Effekte erklären:
- DPIの丸め: 125%はクリーンな倍数ではありません。16 pxは20 pxになります(16 * 1,25)。24 pxは30 pxになります。これらは端数の出る値で、ピクセルのエッジ処理を難しくします.
- リサンプリングフィルタ: Bilinear/Bicubicはエッジをなだらかにします。写真には通常問題ありませんが、アイコンにはしばしば不適切です.
- サブピクセル効果: Windowsはレンダーパスによってサブピクセルアンチエイリアスを使うことも使わないこともあります。アイコンでは制御されたエッジが望ましく、可能な限り多段階のフィルタは避けたいところです.
ラスターアイコンを使っている場合、多くのチームではターゲットサイズごとに個別のPNG(16/20/24/32/40/48)を用意するのが一般的な運用です。一見手間に思えますが、「なぜこのモニタで妙に見えるのか」を何年もデバッグし続けるよりは手間が少ないことが多いです。
デバッグ: 直感で直すのではなく、ピクセルの崩れを再現可能にする
High-DPIのバグはしばしば偶発的に見えますが、いくつかのチェックで決定論的に扱えます:
1) 実行時にDPIとImageListのサイズをログする
起動時とDPI変更時にログを取ってください: フォームのCurrentPPI、Screen.PixelsPerInch(注意: これはシステムDPIを返すことがあります)、および影響を受けるリストのImageList.Width/Height。モニタ切り替え後にまだ16が表示され、32を期待しているなら原因は明白です: リビルドが行われていないか、遅れている可能性があります。
2) アイコンを目に見えて拡大する
手早いテストとして、ツールバーアイコンを一時的に48 pxに設定してみてください。スケーリングの不備は即座に目に付きます。適切なパイプラインは48 pxでもシャープに表示されます — 適切なソースからラスター化されているためです。
3) テーマと背景の切り替えをテストする
エッジ周りのハローは多くの場合アルファ/プレマルチプライ問題です。明背景/暗背景やグラデーションのある面でテストしてください。背景によって縁の見え方が変わるなら、透過処理が間違っています。
4) リモートデスクトップ/モニタ切替をテストスクリプトにする
QA用に短いテストスクリプトを作成します: モニタAでアプリ起動(100%)、ウィンドウをモニタBへ移動(150%)、元に戻す、さらにRDPを再接続する。これが安定していれば、多くの顧客問題は既に潰せています。
既存アプリケーションの移行: Big Bangではなく段階的に
成長してきた Delphi-VCL アプリケーションでは、アイコンロジックがメニュー、ツールバー、ActionList、TreeView、ステータス表示など多くの箇所に分散していることが多いです。Big-Bangで一度に作り替えるのはリスクが高いので、段階的なアプローチが有効でした:
- 棚卸: どのImageListがあるか?どのコントロールがそれを使っているか?どのサイズが期待されているか?
- 優先順位付け: まずは最も目立つ領域(メインツールバー、ナビゲーション、コンテキストメニュー)から着手する。
- 一元化されたソース: アイコンを中央で管理する(ImageCollectionや専用ローダー) — フォームごとに個別ファイルを読み込むのは避ける。
- DPI切替テスト: 最初に改修したモジュールから一貫してDPI切替テストを実行する。
重要: 旧パイプラインと新パイプラインを並行運用する場合は、明確なルールを文書化してください。さもなければ、あるアイコンはシャープで、別のアイコンは明らかにぼやけて見える混在した状態が発生します。
パフォーマンスとメモリ: ランタイムスケーリングによる副作用を抑える
スケーリングはCPUとメモリを消費します。業務用ソフトウェアではアイドル時には目立たないことが多いですが、ウィンドウを新しいモニタに移動したときやフォームが多い状態での起動時に引っかかりが出ることがあります。実践的な指針を三つ挙げます:
- キャッシュサイズを制限する: すべての中間段階を永遠に保持しない。100%と150%だけが必要なら、それらだけをキャッシュする。
- Lazy Build: 画面が実際に必要とするときにのみアイコンをラスタライズする。大きなメニューでは起動時間を節約できる。
- Batch-Rebuild: DPI変更時に各コントロールを個別にトリガーしない。中央でのリビルドが冗長なスケーリングを防ぐ。
もしTVirtualImageListを使用しているなら、その多くは概念として既に組み込まれていますが、それでも自前のスケーリングを重ねてしまわないよう注意が必要です。
フォールバック戦略: すべてのアイコンサイズが揃っていない場合にどうするか?
実際にはすべてのサイズでアセットが揃っているとは限りません。その場合、望ましくない結果を避けるために明確なフォールバック戦略が必要です:
- Prefer Downscale: 64pxから32pxにダウンスケールする方が、16pxを32pxにアップスケールするより望ましい。
- 段階を定義する: 実際にサポートするターゲットサイズ(例: 16/20/24/32/40/48)を決め、DPIをそれに正しくマッピングする。
- 透過をテストする: フォールバック時はアルファチャンネルに特に注意する。ちょうどその部分にハローが発生しやすい。
典型的な誤りは、なんとなく次に近いサイズを使ってしまうことです。これによりアイコンごとに見た目のシャープさが変わってしまいます。より良いのは、厳格に文書化されたマッピング計画です。
どの時点でこの手間を掛ける価値があるか?
クリーンなHigh-DPIアイコンパイプラインに投資する価値がある明確な指標が三つあります:
- ユーザーがノートPCと外部ディスプレイを併用するような混在モニタ環境、あるいはRDPを多用している。
- アプリケーションが長寿命で数年にわたり保守される見込みがあり、UIの見え方が受容性の一部となる。
- 既にモダナイズの計画があり(DPI-Awarenessを高める、コントロールを置き換える、ツールバーのレイアウトを見直す等)、その過程で取り組む予定がある。
対照的に、アプリが同一解像度の固定キオスク端末だけで動作する場合は、対策を最小限に留められます。適切なアイコンサイズを用意し、DPI-Awarenessを正しく設定すれば十分です。
結論: High-DPIは単なる見た目の問題ではなく、レンダリングに関わる設計判断である
VCLにおけるぼやけたアイコンは、単独の不具合であることは稀で、ソース、スケーリング、キャッシュのチェーンに問題があることを示しています。最も堅牢な方法は、複数の解像度でアイコンを用意し、それらを中央のパイプライン(例: ImageCollection/VirtualImageList または専用のアイコンレイヤ)を通して一貫して配信することです。ランタイムでのスケーリングは、動的なソースや合成されたシンボルがある場合に有効ですが、マスターソース、DPI基準のキャッシュ、明確なリビルドルールがある場合に限ります。
もし具体的な症状(DPI変更でアイコンが壊れる、縁にハローが出る、RDP後にサイズが間違う等)があるなら、フォームごとにワークアラウンドを積み重ねるのではなく、その課題を切り分けて小さなアーキテクチャ部品として扱う価値があります。デバッグや段階的なモダナイズの支援が必要な場合は、こちらが適切な入口です:Net-Base Software GmbHへのお問い合わせ。
このテーマでは Timagelist High Dpi と Delphi の Vcl DPI 対応も重要です。本稿はこれらの側面を分かりやすく整理し、日常運用で重要となる点を示します。
次のステップ
テーマが実際のプロジェクトになる場合、アーキテクチャ、既存資産、運用は早期に一体として検討する必要があります。
私たちは単なる個別の問い合わせへの対応にとどまらず、ソースの断片やレガシー課題、ポータルの構想が堅牢な企業向けプロジェクトへと成長する段階まで支援します。
- 既存環境、目標像、技術的リスクを一体として評価します。
- REST、データアクセス、ポータル、およびロールアウトは後回しにされません。
- 早い段階で、どの選択肢が経済的かつ運用上実行可能であるかを見極められます。