Net-Base Magazine

18.08.2026

VCL High-DPI: Scale icons at runtime and avoid pixelation in TImageList

High-DPI in the VCL is not just a checkbox but a sequence of ImageList setup, DPI change events and clean rendering. This hands-on article shows how to scale icons at runtime, avoid artifacts and reproducibly debug common TImageList pitfalls.

18.08.2026

From magazine topic to project implementation

Relevant service and technical pages for this post

Anyone running VCL applications on modern Windows clients sooner or later encounters the same symptom: icons look blurred, frayed, or acquire a gray border at 125%/150%/200%. This is precisely where the topic of VCL High-DPI Icons becomes practical: not because High-DPI is new, but because the issues usually only appear in day-to-day operations — on Terminal Servers, when docking or undocking notebooks, or whenever monitors use different DPIs.

The core issue is almost never „the PNG is broken“, but the pipeline: where the icon comes from (resource, file, SVG, font), in which size it is provided, how it ends up in the TImageList, and who scales it, when and how. In the VCL several concepts converge that must be kept apart: DPI awareness (whether Windows scales the app or the app scales itself), per-monitor DPI (each monitor may be different) and image list strategy (keeping multiple resolutions or rasterizing at runtime).

This article intentionally does not engage in UI design debates, but focuses on a clean, operationally robust approach: scaling icons at runtime in a way that avoids pixel mush, preserves alpha channels, and handles DPI changes without flicker or incorrect image sizes. It also covers pitfalls, debugging tips, and a candid assessment of when the extra effort is justified.

Why pixel mush occurs: understanding the scaling chain in the VCL

Grafik zur Icon-Skalierung: Master-Icon wird in mehrere Zielgrf6dfen gerastert, doppelte Skalierung ffchrt zu Unsche4rfe
A single scaling from a master source is controllable — double resampling steps quickly lead to visible blur.

The most common cause for blurry icons is a single down/upscaling at the wrong time. Typical sequence in legacy VCL:

  • The application supplies icons only in 16×16 or 24×24.
  • Windows or the VCL scales them to 20×20 / 32×32 / 48×48.
  • The scaler uses an interpolation that is OK for photos but blurs edges in pixel graphics.
  • Additionally, transparency (alpha) gets squeezed into a mask logic or converted multiple times.

It becomes particularly nasty when multiple scalings occur in sequence: for example when the ImageList already provides a scaled bitmap and Windows — because of DPI-unawareness or system DPI — scales it again. Result: double blurring.

A second point, often underestimated in projects, is the timing of scaling. With per-monitor DPI (PMv2, i.e. Per-Monitor-DPI-Awareness v2) the effective DPI can change when a window moves between monitors or when a Remote Desktop client adjusts DPI dynamically. If a TImageList or cache is not rebuilt correctly, icons suddenly appear at the wrong size or with the wrong raster.

TImageList under High-DPI: typical pitfalls in real applications

The TImageList was historically designed for small bitmaps, with fixed dimensions, indices and a relatively rigid storage logic. Under High-DPI this leads to practical pitfalls:

1) Hard-wired Width/Height

Many VCL forms set ImageList.Width/Height at design time and leave it at that. At 150% Windows may want, for example, to make 16×16 rather 24×24. If the list remains at 16×16, images will either be clipped or scaled elsewhere — both unpleasant.

2) PNG alpha and mask logic

Depending on the Delphi version and the VCL controls, you easily end up in a mixed mode: PNGs with alpha are internally sometimes kept as 32-bit bitmaps, sometimes as mask+color. As soon as you convert multiple times (PNG -> Bitmap -> ImageList -> Draw), gray halos or hard edges appear. The effect is often background-sensitive: it looks worse on a dark toolbar than on a light panel.

3) DPI changes at runtime: caches, handles, OwnerDraw

Some controls cache the image rendering or take ImageList handles at a point where the DPI is not yet final. Especially with toolbars, TreeViews/ListViews and OwnerDraw scenarios you can see sporadically incorrect image sizes or empty icons after DPI changes, until a Repaint or a RecreateWnd occurs.

4) Terminal Server and Remote Desktop as a reality test

If the app is used over RDP, DPI changes and session reconnects are not exotic. This is exactly where an unrobust ImageList strategy fails: after a reconnect the user sees blurred icons or incorrectly scaled toolbars, even though everything was fine locally.

Clean approach: provide multiple resolutions instead of brute-force upscaling

The most important decision is conceptual: Do you want to upscale icons at runtime from a single base graphic (e.g. 16×16 -> 32×32), or do you provide multiple native resolutions and select the appropriate one depending on the DPI?

In practice, multiple resolutions almost always win. Upscaling is acceptable when the source is vector-based (SVG, icon font) or when you only need moderate factors. As soon as you strongly upscale a small raster image, you lose edge quality, and you can see that immediately on modern displays.

In the VCL world two components are relevant for this approach today:

  • TImageCollection: container for images in multiple sizes/variants.
  • TVirtualImageList: generates at runtime an ImageList in the currently required size and reacts to DPI changes.

That does not solve every problem, but it moves the responsibility to the right place: you define image sources clearly, and scaling/selection happens consistently.

Scaling VCL High-DPI icons at runtime: when it makes sense (and when it doesn’t)

There are legitimate reasons to scale icons at runtime:

  • You load icons dynamically (e.g. from a plugin folder, customer-specific branding packages, configuration packages).
  • You want a unified pipeline for different sources (ICO, PNG, SVG) and do not want to bind all variants at build time.
  • You generate icons programmatically (status badges, overlays, composite symbols).

Runtime scaling is not appropriate if you actually only have classic toolbar icons from a fixed set. The least maintenance-intensive approach is to provide multiple resolutions cleanly and let the VCL choose.

If you do runtime scaling, do it with clear rules:

  • Never scale again from an already scaled bitmap. Always start from a master source (ideally vector-based or high-resolution).
  • Cache per target size and DPI, otherwise you will rescale on every paint — that costs CPU and can cause stuttering.
  • Preserve alpha: minimize conversions, use 32-bit RGBA, do not rasterize the background.

Pragmatic architecture: icon pipeline as a separate component

In larger applications it pays off not to scatter the topic everywhere, but to build a small pipeline. This doesn’t have to be a framework — rather a clear area of responsibility:

  • Icon source: Where do the master assets come from (resources, files, database, API)?
  • Rasterizer/Scaler: How is the master converted into the target size (interpolation, possibly SVG render)?
  • Cache: Key (Icon-ID, target pixels, DPI, theme) and lifecycle (invalidate on DPI change, theme change, package change).
  • Consumer adapter: How does the result get into VCL structures (TImageList/TVirtualImageList, OwnerDraw, PaintBox)?

Advantage: You can reproduce DPI bugs in one place instead of searching through 40 forms for where it’s being scaled again.

Handling DPI changes correctly: events, rebuild, repaint

Notebook with two monitors of different DPI, switching windows shows different icon sizes in a desktop app
Per-monitor DPI is the reality test: on monitor switch the icon pipeline must re-rasterize, not just redraw.

Under Windows DPI changes are their own lifecycle. In the VCL there are, depending on version and DPI-awareness, several events/mechanisms, but the basic principle remains:

  • If the window’s DPI changes, bitmap-based resources that need to be pixel-accurate must be re-provided.
  • If you populate ImageLists dynamically, a mere Invalidate is often not enough — you need a rebuild of the images in the new target size.

A practical pattern is: on DPI change (e.g. form-scale/monitor switch) invalidate the icon cache for that DPI and rebuild the affected ImageLists. It is important to not scale inside paint events, but in a controlled update block (Toolbar.BeginUpdate/EndUpdate, disable ListView redraw, then re-enable). This avoids flicker and partial UI states.

Why TImageList so often becomes blurry: interpolation, DPI rounding, edges

Once you understand that it is not DPI that is the problem, but interpolation plus rounding, many effects can be explained:

  • DPI rounding: 125% is not a clean doubling. 16 px becomes 20 px (16 * 1.25). 24 px becomes 30 px. Those are awkward numbers that make pixel edges harder.
  • Resampling filter: bilinear/bicubic softens edges. That is fine for photos, often not for icons.
  • Subpixel effects: Windows can use subpixel antialiasing depending on the render path or not. For icons you want controlled edges — and preferably no multiple filter stages.

If you have raster icons, it is common practice in many teams to provide separate PNGs per target size (16/20/24/32/40/48). Sounds like a lot, but is often less effort than spending years debugging „why does this look strange on this monitor.“

Debugging: make pixel mush reproducible instead of fixing by gut feeling

Workstation with debug notes for icon scaling and background tests for the alpha edge
Good DPI debugging routines test sizes, backgrounds and rebuild timings — not only the first screenshot.

High-DPI bugs often appear random. With a few checks they become deterministic:

1) Log DPI and ImageList sizes at runtime

Log at startup and on DPI changes: the form’s CurrentPPI, Screen.PixelsPerInch (note: this can be the system DPI), and the ImageList.Width/Height of the affected lists. If after a monitor switch you still see 16 px when you expect 32 px, the cause is clear: a rebuild is missing or happens too late.

2) Make icons visibly larger

A quick test is to temporarily set toolbar icons to 48 px. Poor scaling then becomes obvious. Good pipelines remain sharp even at 48 px because they rasterize from an appropriate source.

3) Test theme and background changes

Halos at the edge are often alpha/premultiply issues. Test on light/dark themes and on areas with gradients. If the edge looks different depending on the background, the transparency handling is incorrect.

4) Remote Desktop / monitor switch as a test script

Create a short QA test script: start the app on Monitor A (100%), move the window to Monitor B (150%), back, then reconnect RDP. If that sequence is stable, many customer problems are already eliminated.

Migration in existing applications: incremental instead of Big Bang

In mature Delphi-VCL applications the icon logic is often spread across many places: menus, toolbars, ActionLists, TreeViews, status displays. A Big-Bang rewrite brings risk. A stepwise approach has proven effective:

  • Inventory: Which ImageLists exist? Which controls use them? Which sizes are expected?
  • Prioritize: Start with the most prominent areas (main toolbar, navigation, context menus).
  • Unified source: Centralize icons (ImageCollection or a dedicated loader) instead of loading individual files per form.
  • Test DPI switches: From the first migrated module onwards, run DPI-change tests consistently.

Important: If you run old and new pipelines in parallel, document clear rules. Otherwise you end up with a mixed landscape where some icons are sharp and others appear visibly blurry.

Performance and memory: runtime scaling without side effects

Scaling costs CPU and memory. In business software this rarely shows up at idle, but when moving a window to a new monitor or at startup with many forms it can stutter. Three practical guardrails:

  • Limit cache sizes: Don’t keep every intermediate step forever. If you only need 100% and 150%, cache only those.
  • Lazy Build: Rasterize icons only when the screen actually needs them. For large menus this saves startup time.
  • Batch-Rebuild: On DPI change don’t trigger each control individually. A central rebuild prevents redundant scaling.

If you work with TVirtualImageList, much of this is already part of the concept — but you still must take care not to add your own extra scaling on top.

Fallback strategies: what to do when not all icon sizes are available?

In reality you don’t always have every asset in every size. You therefore need a clear fallback strategy to avoid random results:

  • Prefer downscale: Better to downscale from 64 px to 32 px than upscale from 16 px to 32 px.
  • Define tiers: Specify which target sizes you actually support (e.g. 16/20/24/32/40/48) and map DPI cleanly to them.
  • Test transparency: Pay special attention to alpha in fallbacks — halos appear exactly there.

A common mistake is to pick any next size. That causes perceived sharpness to vary between icons. A stricter, documented mapping plan is better.

When is the effort worthwhile?

There are three clear indicators that a clean high‑DPI icon pipeline is worth the investment:

  • Your users work with mixed monitors (laptop + external) or frequently use RDP.
  • The application is long‑lived and maintained over years — UI perception is part of acceptance.
  • You already have modernization steps planned (increase DPI awareness, replace controls, revise toolbar layout).

If the app runs only on a fixed kiosk system with an identical resolution, you can keep this minimal: provide an appropriate icon size, set DPI awareness correctly, done.

Conclusion: High‑DPI is not a cosmetic detail but a rendering decision

Blurry icons in the VCL are rarely an isolated bug; they indicate an impure chain of sources, scaling and caching. The most robust approach is to provide icons in multiple resolutions and deliver them consistently via a central pipeline (e.g. ImageCollection/VirtualImageList or your own icon layer). Runtime scaling makes sense when you have dynamic sources or composite symbols — but only with a master source, a DPI‑based cache and clear rebuild rules.

If you have concrete symptoms (DPI switches breaking icons, halos at the edges, wrong sizes after RDP), it pays to isolate the issue and treat it as a small architectural building block, instead of piling workarounds per form. If you need support for debugging or a staged modernization, you can start here: Kontakt zur Net-Base Software GmbH.

For this topic, Timagelist High Dpi and Delphi Vcl Dpi-Awareness are also important. The article places these aspects into a clear context and shows what matters in day-to-day operation.

Discuss a project or modernization initiative with Net-Base.

Next step

When the topic becomes an actual project, architecture, existing systems and operations should be considered together from the outset.

We support not only with individual issues, but also when source snippets, legacy topics, or portal ideas are to be turned into a robust enterprise project.

  • Current state, target state and technical risks are assessed jointly.
  • REST, data access, portals and rollout are not deferred to a later stage as secondary consequences.
  • You can see early on which path is economically and operationally viable.

Share post

Share this post directly

LinkedIn, X, XING, Facebook, WhatsApp and e-mail are available immediately. For Instagram we will prepare the link and short text directly.

Email

Instagram opens in a new tab. The link and short text are copied to the clipboard beforehand.