От темы в журнале к проектной практике
Соответствующие страницы услуг и технологий к статье
Кто эксплуатирует VCL‑приложения на современных Windows‑клиентах, рано или поздно сталкивается с одним и тем же симптомом: иконки при 125%/150%/200% выглядят размытыми, зазубренными или получают серую окантовку. Именно здесь тема VCL High-DPI Icons становится практической: не потому, что High‑DPI ново, а потому, что проблемы обычно проявляются в повседневной эксплуатации — на терминальных серверах, при переключении ноутбука в док или когда на разных мониторах используются разные DPI.
Основная проблема почти никогда не в том, что «PNG повреждён», а в конвейере обработки: откуда берётся иконка (ресурс, файл, SVG, шрифт), в каком размере она предоставляется, как попадает в TImageList и кто когда и как её масштабирует. В VCL сходятся несколько понятий, которые нужно различать: DPI-Awareness (масштабирует ли Windows приложение или приложение само), Per-Monitor-DPI (каждый монитор может быть другим) и ImageList-Strategie (хранить несколько разрешений или растеризовать во время выполнения).
В этой статье намеренно не обсуждаются дебаты по UI‑дизайну, а рассматривается чистый, надёжный в эксплуатации подход: масштабировать иконки во время выполнения, но так, чтобы они не превращались в пиксельную кашу, чтобы альфа‑каналы оставались целыми и чтобы смена DPI работала без мерцания или неправильных размеров изображений. Также описаны подводные камни, советы по отладке и честная оценка, когда дополнительные усилия оправданы.
Почему появляется «пиксельная каша»: понимание цепочки масштабирования в VCL
Наиболее частая причина нечётких иконок — единоразовое уменьшение/увеличение (Down-/Upscaling) в неподходящий момент. Классический сценарий в Legacy‑VCL:
- Приложение предоставляет иконки только в 16×16 или 24×24.
- Windows или VCL масштабирует до 20×20 / 32×32 / 48×48.
- Алгоритм масштабирования использует интерполяцию, которая для фотографий приемлема, но для пиксельной графики размывает края.
- Кроме того, прозрачность (Alpha) сжимается в логику маски или конвертируется несколько раз.
Особенно неприятно, когда происходят несколько последовательных масштабирований: например, когда ImageList уже предоставляет масштабированное Bitmap, а Windows из‑за DPI‑Unaware или системного DPI масштабирует его ещё раз. Результат: двойное размазывание.
Второй, в проектах недооцениваемый момент — момент масштабирования. При Per‑Monitor‑DPI (PMv2, то есть Per‑Monitor‑DPI‑Awareness v2) эффективный DPI может изменяться, когда окно перемещается между мониторами или когда клиент удалённого рабочего стола динамически подстраивает DPI. Если при этом TImageList или кэш не перестраиваются корректно, внезапно можно увидеть иконки неправильного размера или с неправильной сеткой.
TImageList при High‑DPI: типичные ловушки в реальных приложениях
Die TImageList ist historisch für kleine Bitmaps gedacht, mit festen Maßen, Indizes und relativ starrer Speicherlogik. Unter High-DPI ergeben sich daraus praxisnahe Fallen:
1) Fest verdrahtete Width/Height
Viele VCL-Formulare setzen ImageList.Width/Height zur Designzeit und lassen es dabei. Bei 150% möchte Windows aber z.B. aus 16×16 eher 24×24 machen. Wenn die Liste auf 16×16 bleibt, wird entweder geclippt oder an anderer Stelle skaliert — beides unschön.
2) PNG-Alpha und Maskenlogik
Je nach Delphi-Version und VCL-Controls landet man schnell in einem Mischbetrieb: PNGs mit Alpha werden intern teils als 32-bit Bitmap gehalten, teils als Mask+Color. Sobald du mehrfach konvertierst (PNG → Bitmap → ImageList → Draw), entstehen graue Halos oder harte Kanten. Der Effekt ist häufig backgroundsensitiv: Auf dunkler Toolbar sieht es schlimmer aus als auf einem hellen Panel.
3) DPI-Wechsel zur Laufzeit: Caches, Handles, OwnerDraw
Einige Controls cachen die Bilddarstellung oder übernehmen ImageList-Handles zu einem Zeitpunkt, wo die DPI noch nicht final ist. Besonders bei Toolbars, TreeViews/ListViews und OwnerDraw-Szenarien sieht man nach DPI-Wechseln sporadisch falsche Bildgrößen oder leere Icons, bis ein Repaint oder ein RecreateWnd passiert.
4) Terminalserver und Remote-Desktop als Realitätstest
Wenn die App über RDP genutzt wird, sind DPI-Wechsel und Session-Reconnects nicht exotisch. Genau dort fliegt dir eine unrobuste ImageList-Strategie um die Ohren: Der Benutzer sieht nach dem Reconnect verwaschene Icons oder falsch skalierte Symbolleisten, obwohl lokal alles ok war.
Sauberer Ansatz: Mehrere Auflösungen bereitstellen statt brutales Upscaling
Die wichtigste Entscheidung ist konzeptionell: Willst du Icons zur Laufzeit aus einer einzigen Basisgrafik hochskalieren (z.B. 16×16 → 32×32), oder stellst du mehrere native Auflösungen bereit und wählst je nach DPI die passende?
In der Praxis gewinnt fast immer „mehrere Auflösungen“. Upscaling kann man machen, wenn die Quelle vektorbasiert ist (SVG, Icon-Font) oder wenn du wirklich nur moderate Faktoren brauchst. Sobald du aus einem kleinen Rasterbild stark hochskalierst, verlierst du Kantenqualität, und das sieht man auf modernen Displays sofort.
In der VCL‑Welt sind für diesen Ansatz heute zwei Bausteine relevant:
- TImageCollection: Container für Bilder in mehreren Größen/Varianten.
- TVirtualImageList: erzeugt daraus zur Laufzeit eine ImageList in der aktuell benötigten Größe und reagiert auf DPI‑Änderungen.
Das nimmt dir nicht alle Probleme ab, aber es verschiebt das Problem an die richtige Stelle: Du definierst Bildquellen sauber, und die Skalierung/Selektion passiert konsistent.
VCL High-DPI Icons zur Laufzeit skalieren: Wann es sinnvoll ist (und wann nicht)
Es gibt legitime Gründe, Icons zur Laufzeit zu skalieren:
- Du lädst Icons dynamisch (z.B. aus einem Plugin‑Ordner, kundenspezifische Branding‑Pakete, Konfigurationspakete).
- Du willst eine einheitliche Pipeline für verschiedene Quellen (ICO, PNG, SVG) und möchtest nicht alle Varianten zur Build‑Zeit binden.
- Du erzeugst Icons programmgesteuert (Status‑Badges, Overlays, zusammengesetzte Symbole).
Не имеет смысла выполнять масштабирование во время выполнения, если у тебя по сути только классические иконки панели инструментов из фиксированного набора. Тогда самый малозатратный в поддержке путь: аккуратно поставлять несколько разрешений и позволять VCL выбирать нужное.
Если ты делаешь масштабирование во время выполнения, то с чёткими правилами:
- Никогда не масштабировать повторно уже масштабированное битмап-изображение. Всегда исходи из мастер-источника (в идеале — векторный или высокоразрешающий).
- Кэшировать по целевому размеру и DPI, иначе ты будешь масштабировать при каждом Paint заново — это нагружает CPU и может вызывать рывки.
- Сохранять альфа: минимизировать конверсии, использовать 32-bit RGBA, не растеризовать фон.
Прагматичная архитектура: конвейер иконок как отдельный компонент
В крупных приложениях имеет смысл не рассыпать логику повсюду, а построить небольшую конвейерную цепочку. Это не обязательно фреймворк — скорее чётко выделенная зона ответственности:
- Icon-Quelle: Откуда приходят мастер-ассеты (ресурсы, файлы, база данных, API)?
- Rasterizer/Scaler: Как из мастера получается целевой размер (интерполяция, при необходимости SVG-рендер)?
- Cache: Ключ (Icon-ID, целевые пиксели, DPI, Theme) и жизненный цикл (инвалидировать при смене DPI, Theme, пакета).
- Consumer-Adapter: Как результат попадает в VCL-структуры (TImageList/TVirtualImageList, OwnerDraw, PaintBox)?
Преимущество: ты можешь воспроизводимо дебажить DPI-баги в одном месте, вместо того чтобы искать «где ещё масштабируется» в 40 формах.
Корректная обработка смены DPI: события, пересоздание, перерисовка
Под Windows смены DPI — это собственный жизненный цикл. В VCL в зависимости от версии и DPI-Awareness есть несколько событий/механизмов, но базовый принцип остаётся:
- Если DPI окна меняется, то битмап-ресурсы, которые должны быть пиксельно точными, нужно заново предоставить.
- Если ты динамически заполняешь ImageLists, то простого «Invalidate» часто недостаточно — нужен Rebuild изображений в новой целевой размерности.
Практичный паттерн: при изменении DPI (например, Form-Scale/смена монитора) инвалидируй кэш иконок для этого DPI и перестрой затронутые ImageLists. Важно не масштабировать в Paint-событиях, а делать это в контролируемом блоке обновления (Toolbar.BeginUpdate/EndUpdate, выключить ListView-Redraw, затем включить). Так ты избежишь мерцания и незавершённых состояний UI.
Почему TImageList так часто получается нечетким: интерполяция, округление DPI, края
Когда вы поймёте, что проблема не в самих DPI, а в интерполяции плюс округлении, многие эффекты становятся объяснимыми:
- Округление DPI: 125% ist kein sauberer Verdoppler. Aus 16 px werden 20 px (16 * 1,25). Aus 24 px werden 30 px. Das sind krumme Zahlen, die Pixelkanten erschweren.
- Фильтры ресемплинга: Bilinear/Bicubic macht Kanten weicher. Das ist für Fotos ok, für Icons oft nicht.
- Субпиксельные эффекты: Windows kann je nach Renderpfad Subpixel-Antialiasing nutzen oder nicht. Bei Icons willst du kontrollierte Kanten e4a0b7 und mf6glichst keine mehrfachen Filterstufen.
Wenn du Raster-Icons hast, ist es in vielen Teams gängige Praxis, pro Zielgröße eigene PNGs zu liefern (16/20/24/32/40/48). Klingt nach viel, ist aber oft weniger Aufwand als jahrelang „warum sieht das auf diesem Monitor komisch aus“ zu debuggen.
Debugging: Pixelmatsch reproduzierbar machen statt nach Bauchgefühl zu fixen
High-DPI-Bugs wirken oft zufällig. Mit ein paar Checks werden sie deterministisch:
1) DPI und ImageList-Größen zur Laufzeit loggen
Logge bei Start und bei DPI-Wechsel: CurrentPPI des Forms, Screen.PixelsPerInch (Achtung: das kann System-DPI sein), sowie ImageList.Width/Height der betroffenen Listen. Wenn du nach einem Monitorwechsel noch 16 siehst, obwohl du 32 erwartest, ist die Ursache klar: Rebuild fehlt oder kommt zu spät.
2) Icons sichtbar vergrößern
Ein schneller Test ist, die Toolbar-Icons temporär auf 48 px zu setzen. Schlechte Skalierung springt dann sofort ins Auge. Gute Pipelines bleiben auch bei 48 px scharf, weil sie aus einer passenden Quelle rastern.
3) Theme- und Hintergrundwechsel testen
Halos am Rand sind oft Alpha-/Premultiply-Probleme. Teste auf hell/dunkel und auf Flächen mit Farbverlauf. Wenn die Kante je nach Hintergrund anders aussieht, stimmt die Transparenzbehandlung nicht.
4) Remote Desktop / Monitorwechsel als Testskript
Erstelle ein kurzes Testskript für die QS: App starten auf Monitor A (100%), Fenster auf Monitor B (150%), zurück, dann RDP reconnect. Wenn das stabil ist, sind viele Kundenprobleme bereits erschlagen.
Migration in bestehenden Anwendungen: Schrittweise statt Big Bang
In gewachsenen Delphi-VCL-Anwendungen steckt die Icon-Logik oft an vielen Stellen: Menüs, Toolbars, ActionLists, TreeViews, Statusanzeigen. Ein Big-Bang-Umbau bringt Risiko. Bewährt hat sich ein schrittweises Vorgehen:
- Inventur: Welche ImageLists gibt es? Welche Controls nutzen sie? Welche Größen werden erwartet?
- Priorisieren: Zuerst die prominentesten Flächen (Haupttoolbar, Navigation, Kontextmenüs).
- Einheitliche Quelle: Icons zentralisieren (ImageCollection oder eigener Loader), statt pro Form Einzeldateien zu laden.
- DPI-Wechsel testen: Ab dem ersten umgebauten Modul konsequent DPI-Wechsel-Tests fahren.
Важно: если вы параллельно эксплуатируете старую и новую Pipeline, задокументируйте чёткие правила. Иначе получится смешанная среда, в которой одни иконки выглядят резкими, а другие заметно нечеткими.
Производительность и память: масштабирование во время выполнения без побочных эффектов
Масштабирование требует CPU и памяти. В бизнес‑ПО это редко заметно в простое, но при перемещении окна на новый монитор или при запуске с большим количеством форм может появляться подтормаживание. Три практических ориентиры:
- Ограничивать размеры кэша: не храните все промежуточные состояния бесконечно. Если вам нужны только 100% и 150%, кэшируйте только их.
- Lazy Build: растрируйте иконки лишь тогда, когда экран действительно их требует. При больших меню это экономит время старта.
- Batch-Rebuild: при смене DPI не триггерьте перестройку для каждого контрола отдельно. Центральный Rebuild предотвращает избыточное масштабирование.
Если вы работаете с TVirtualImageList, многое из этого уже заложено в концепцию, но всё равно нужно следить, чтобы не накладывать дополнительную собственную логику масштабирования сверху.
Fallback-Strategien: Was tun, wenn nicht alle Icon-Größen vorliegen?
На практике не всегда доступны все ресурсы во всех размерах. Тогда нужна чёткая стратегия отката, чтобы избежать случайных результатов:
- Prefer Downscale: лучше уменьшать с 64 px до 32 px, чем увеличивать с 16 px до 32 px.
- Definiere Stufen: определите, какие целевые размеры вы действительно поддерживаете (например, 16/20/24/32/40/48) и аккуратно сопоставляйте DPI с этими ступенями.
- Transparenz testen: при откатах особенно обращайте внимание на альфа‑канал — именно там появляются ореолы.
Типичная ошибка — просто брать ближайший размер. Это приводит к тому, что воспринимаемая резкость варьируется в зависимости от иконки. Лучше иметь жёсткий, задокументированный план маппинга.
Когда усилия действительно оправданы?
Есть три ясных индикатора, что стоит реализовать аккуратную High‑DPI‑конвейер для иконок:
- Ваши пользователи работают с разными мониторами (ноутбук + внешний) или активно используют RDP.
- Приложение долговечно и поддерживается годами — восприятие интерфейса является частью принятия продукта.
- Вы всё равно планируете шаги по модернизации (повышение DPI‑awareness, замена контролов, переработка макета тулбара).
Если же приложение запускается только на фиксированной киосковой системе с одинаковым разрешением, можно свести тему к минимуму: поставьте подходящий размер иконок, корректно настройте DPI‑awareness — и всё.
Fazit: High-DPI ist kein kosmetisches Detail, sondern eine Rendering-Entscheidung
Нечёткие иконки в VCL редко являются изолированной ошибкой — это признак недостаточно аккуратной цепочки из источников, масштабирования и кэширования. Наиболее надёжный путь — предоставить иконки в нескольких разрешениях и доставлять их через центральную конвейерную логику (например, ImageCollection/VirtualImageList или собственный слой иконок) консистентно. Масштабирование во время выполнения имеет смысл, когда у вас есть динамические источники или составные символы, но только при наличии мастер‑источника, кэша, основанного на DPI, и чётких правил Rebuild.
Если у вас есть конкретные симптомы (смена DPI портит иконки, ореолы по краям, неверные размеры после RDP), имеет смысл целенаправленно изолировать проблему и рассматривать её как небольшой архитектурный компонент, а не навешивать обходные решения для каждой формы. Если вам нужна поддержка при отладке или поэтапной модернизации, вы найдёте подходящую точку входа здесь: ссылка для связи с Net-Base Software GmbH.
Для этой темы также важны Timagelist High Dpi и Delphi Vcl Dpi-Awareness. В статье эти аспекты понятно разъясняются и показано, на что обращать внимание в повседневной работе.
Следующий шаг
Если из темы становится реальный проект, архитектуру, существующее состояние и эксплуатацию следует рассматривать совместно на ранней стадии.
Мы поддерживаем не только при отдельных вопросах, но и тогда, когда из фрагментов исходного кода, унаследованных проблем или идей портала должен сформироваться надёжный корпоративный проект.
- Текущее состояние, целевое состояние и технические риски оцениваются совместно.
- REST, доступ к данным, порталы и развертывание не переносятся на более поздние этапы.
- Вы заранее видите, какой путь экономически и операционно жизнеспособен.