Fra magasinets tema til projektpraksis
Passende service- og tekniske sider til artiklen
Den, der kører VCL-applikationer på moderne Windows-klienter, støder før eller siden på det samme symptom: ikoner fremstår ved 125 %/150 %/200 % som udglattede, opsplejsede eller får en grå kant. Netop her bliver emnet VCL High-DPI-ikoner praktisk relevant: Ikke fordi High-DPI er nyt, men fordi problemerne som regel først viser sig i daglig brug — på terminalservere, ved docking/undocking af bærbar eller så snart forskellige DPI anvendes pr. skærm.
Kerneaspektet er næsten aldrig, at et PNG er korrupt, men pipeline: Hvor kommer ikonet fra (resource, fil, SVG, font), i hvilken størrelse leveres det, hvordan lander det i TImageList og hvem skalerer hvornår hvordan. I VCL samles flere begreber, som man må adskille: DPI-Awareness (om Windows skalerer appen eller appen skalerer sig selv), Per-Monitor-DPI (hver skærm må være forskellig) og ImageList-strategi (holde flere opløsninger eller rasterisere ved kørselstid).
I dette indlæg handler det bevidst ikke om UI-design-debatter, men om en ren, driftssikker tilgang: at skalere ikoner ved kørselstid, men sådan at de ikke bliver til pixelmos, at alpha-kanaler forbliver intakte, og at DPI-skift fungerer uden flimrende eller forkerte billedstørrelser. Dertil kommer fælder, debugging-tips og en ærlig vurdering af, hvornår den ekstra indsats er berettiget.
Hvorfor pixelmos opstår: Forstå skaleringkæden i VCL
Den hyppigste årsag til uskarpe ikoner er en enkelt ned-/opskalering på et forkert tidspunkt. Klassisk forløb i legacy-VCL:
- Applikationen leverer kun ikoner i 16×16 eller 24×24.
- Windows eller VCL skalerer til 20×20 / 32×32 / 48×48.
- Skaleringen anvender en interpolation, der er ok til fotos, men som udtværer kanter i pixelgrafik.
- Derudover presses transparens (alpha) ind i en maskelogik eller konverteres flere gange.
Særligt problematisk bliver det, når flere skaleringstrin sker efter hinanden: for eksempel når ImageList allerede leverer et skaleret bitmap, og Windows på grund af DPI-unawareness eller system-DPI skalerer det igen. Resultat: dobbelt udjævning.
Et andet ofte undervurderet punkt i projekter er tidspunktet for skalering. Ved Per-Monitor-DPI (eller PMv2, altså Per-Monitor-DPI-Awareness v2) kan den effektive DPI ændre sig, når et vindue flytter skærm, eller når en Remote-Desktop-klient dynamisk justerer DPI. Hvis en TImageList eller en cache ikke bliver genopbygget korrekt, får man pludselig ikoner i forkert størrelse eller med forkert raster.
TImageList under High-DPI: typiske faldgruber i reelle applikationer
TImageList er historisk beregnet til små bitmaps med faste mål, indekser og en relativt stiv hukommelseslogik. Under High-DPI opstår der praktiske faldgruber:
1) Fastkodede Width/Height
Mange VCL-formularer sætter ImageList.Width/Height i design-tid og lader det blive. Ved 150% vil Windows f.eks. gerne gøre 16 til 24. Hvis listen forbliver på 16, bliver billedet enten klippet eller skaleret et andet sted — begge dele ser dårligt ud.
2) PNG-alpha og maskelogik
Afhængig af Delphi-versionen og VCL-kontroller ender man hurtigt i et blandingsscenario: PNG’er med alpha gemmes internt delvist som 32-bit bitmap, delvist som maske+farve. Så snart du konverterer flere gange (PNG -> Bitmap -> ImageList -> Draw), opstår grå halos eller hårde kanter. Effekten er ofte baggrundsafhængig: På en mørk værktøjslinje ser det værre ud end på et lyst panel.
3) DPI-skift under kørsel: Caches, Handles, OwnerDraw
Nogle kontroller cacher billedfremstillingen eller overtager ImageList-handles på et tidspunkt, hvor DPI endnu ikke er endelig. Især ved Toolbars, TreeViews/ListViews og OwnerDraw-scenarier ser man efter DPI-skift sporadisk forkerte billedstørrelser eller tomme ikoner, indtil et Repaint eller et RecreateWnd sker.
4) Terminalserver og Remote-Desktop som realitetstest
Hvis applikationen bruges over RDP, er DPI-skift og session-reconnects ikke usædvanlige. Netop dér slår en urobust ImageList-strategi fejl: Brugeren ser efter reconnect udvaskede ikoner eller forkert skalerede værktøjslinjer, selvom det lokalt så fint ud.
Ren tilgang: Flere opløsninger til rådighed i stedet for brutalt opskalering
Den vigtigste beslutning er konceptuel: Vil du skalerer ikoner ved kørsel fra et enkelt basisbillede (f.eks. 16 -> 32), eller tilbyder du flere native opløsninger og vælger den passende efter DPI?
I praksis vinder næsten altid „flere opløsninger“. Opskalering kan bruges, hvis kilden er vektorbasseret (SVG, icon-font) eller hvis du kun behøver moderate faktorer. Så snart du opskalerer meget fra et lille rasterbillede, mister du kantkvalitet, og det ses tydeligt på moderne skærme.
I VCL-verdenen er til denne tilgang i dag to byggesten relevante:
- TImageCollection: Container for billeder i flere størrelser/varianter.
- TVirtualImageList: genererer derfra ved kørsel en ImageList i den aktuelt nødvendige størrelse og reagerer på DPI-ændringer.
Det løser ikke alle problemer, men flytter ansvaret til det rette lag: Du definerer billedkilderne entydigt, og udvælgelse/skalering sker konsistent.
Skalering af VCL High-DPI-ikoner ved kørsel: Hvornår det giver mening (og hvornår ikke)
Der er legitime grunde til at skalere ikoner ved kørsel:
- Du indlæser ikoner dynamisk (f.eks. fra en plugin-mappe, kunde-specifikke branding-pakker eller konfigurationspakker).
- Du ønsker en ensartet pipeline for forskellige kilder (ICO, PNG, SVG) og vil ikke binde alle varianter ved build-tid.
- Du genererer ikoner programmatisk (status-badges, overlays, sammensatte symboler).
Runtime-skalering giver ikke mening, hvis du i realiteten kun har klassiske toolbar-ikoner fra et fast sæt. Den mindst vedligeholdelseskrævende løsning er: levere flere opløsninger og lade VCL vælge.
Hvis du bruger runtime-skalering, så med klare regler:
- Aldrig skalere et allerede skaleret bitmap på ny. Start altid fra en masterkilde (ideelt vektorbaseret eller højtopløst).
- Cache per målstørrelse og DPI, ellers skalerer du ved hvert paint på ny — det koster CPU og kan give hak i UI.
- Bevar alpha: Minimer konverteringer, brug 32-bit RGBA, og undgå at rasterisere baggrunden.
Pragmatisk arkitektur: Icon-Pipeline som egen komponent
I større applikationer er det værd at undgå at sprede emnet overalt og i stedet bygge en lille pipeline. Det behøver ikke være et framework — snarere et klart ansvarsområde:
- Icon-Quelle: Hvor kommer master-assets fra (ressourcer, filer, database, API)?
- Rasterizer/Scaler: Hvordan genereres målstørrelser fra masterkilden (interpolation, evt. SVG-render)?
- Cache: Nøgle (Icon-ID, målpixel, DPI, Theme) og livscyklus (invalidér ved DPI-skift, Theme-skift, pakkeskift).
- Consumer-Adapter: Hvordan kommer resultatet ind i VCL-strukturer (TImageList/TVirtualImageList, OwnerDraw, PaintBox)?
Fordel: Du kan reproducere DPI-bugs ét sted og debugge dér i stedet for at lede i 40 Forms efter, hvor der skaleres igen.
DPI-skift korrekt håndteret: Ereignisse, Rebuild, Repaint
Under Windows er DPI-skift en egen livscyklus. I VCL findes der, afhængigt af version og DPI-awareness, flere hændelser/mekanismer, men grundprincippet er det samme:
- Hvis vinduets DPI ændrer sig, skal billedbaserede ressourcer, der skal være pixelnøjagtige, genskabes.
- Hvis du fylder ImageLists dynamisk, er et blot Invalidate ofte ikke nok — du har brug for et Rebuild af billederne i den nye målstørrelse.
Et praktisk mønster er: Ved DPI-ændring (f.eks. Form-Scale/monitor-skift) invalidér du ikon-cachen for denne DPI og genopbygger de berørte ImageLists. Vigtigt er, at du ikke skalerer i Paint-events, men i en kontrolleret opdateringsblok (Toolbar.BeginUpdate/EndUpdate, ListView-Redraw slukket, og tænd igen). Så undgår du flimmer og ufuldstændige UI-tilstande.
Hvorfor TImageList så ofte bliver sløret: Interpolation, DPI-runding, kanter
Når du først har forstået, at problemet ikke er DPI’en, men interpolation plus afrunding, kan mange effekter forklares:
- DPI-afrunding: 125% er ikke en ren fordobling. 16 px bliver 20 px (16 * 1,25). 24 px bliver 30 px. Det giver ujævne tal, som gør pixelkanter vanskelige.
- Resampling-filter: Bilinear/Bicubic gør kanter blødere. Det er OK for fotos, men ofte ikke for ikoner.
- Subpixel-effekter: Windows kan alt efter rendersti bruge subpixel-antialiasing eller ej. For ikoner ønsker du kontrollerede kanter — og helst ingen flere filtertrin.
Hvis du har rasterikoner, er det i mange teams almindelig praksis at levere separate PNG’er per målstørrelse (16/20/24/32/40/48). Det lyder af meget, men er ofte mindre arbejde end at bruge år på at debugge ‚hvorfor ser det mærkeligt ud på denne skærm‘.
Debugging: Gør pixelgrød reproducerbar i stedet for at fixe efter mavefornemmelse
High-DPI-fejl virker ofte tilfældigt. Med et par checks bliver de deterministiske:
1) DPI og ImageList-størrelser logges ved kørsel
Log ved opstart og ved DPI-ændring: CurrentPPI for formen, Screen.PixelsPerInch (bemærk: det kan være system-DPI), samt ImageList.Width/Height for de berørte lister. Hvis du efter et monitorskift stadig ser 16 px, selvom du forventer 32, er årsagen klar: et rebuild mangler eller kommer for sent.
2) Gør ikoner synligt større
En hurtig test er midlertidigt at sætte toolbar-ikonerne til 48 px. Dårlig skalering bliver straks synlig. Gode pipelines forbliver skarpe ved 48 px, fordi de rasteriseres fra en passende kilde.
3) Test tema- og baggrundsskift
Halos i kanten er ofte alpha-/premultiply-problemer. Test på lyst/mørkt og på områder med farvegradient. Hvis kanten ser forskellig ud afhængigt af baggrunden, er transparensbehandlingen forkert.
4) Remote Desktop / monitorskift som testskript
Lav et kort testskript til QA: start appen på monitor A (100%), flyt vinduet til monitor B (150%), tilbage, og så RDP-reconnect. Hvis det er stabilt, er mange kundesager allerede løst.
Migration i eksisterende applikationer: Trinvis fremgang i stedet for Big Bang
I eksisterende Delphi-VCL-applikationer er ikonlogikken ofte spredt over mange steder: menuer, toolbars, ActionLists, TreeViews, statusindikatorer. En Big-Bang-ombygning medfører risiko. Et trinvis forløb har vist sig effektivt:
- Gennemgang: Hvilke ImageLists findes? Hvilke kontroller bruger dem? Hvilke størrelser forventes?
- Prioriter: Start med de mest fremtrædende områder (hovedtoolbar, navigation, kontekstmenuer).
- Ensartet kilde: Centraliser ikoner (ImageCollection eller egen loader) i stedet for at loade enkeltfiler per form.
- Test DPI-ændringer: Fra det første ombyggede modul kør konsekvent DPI-ændringstests.
Vigtigt: Hvis du kører gammel og ny pipeline parallelt, dokumentér klare regler. Ellers opstår et blandet landskab, hvor nogle ikoner er skarpe, og andre tydeligt fremstår slørede.
Performance og hukommelse: Runtime-skalering uden bivirkninger
Skalering koster CPU og hukommelse. I forretningssoftware bemærkes det sjældent i tomgang, men når vinduet trækkes til en ny skærm eller ved opstart med mange forms kan det hakke. Tre praktiske retteskinner:
- Begræns cache-størrelse: Gem ikke alle mellemstadier for evigt. Hvis du kun har brug for 100% og 150%, cache kun disse.
- Lazy Build: Rasteriser ikoner først, når skærmen virkelig har brug for dem. Ved store menuer sparer det opstartstid.
- Batch-Rebuild: Ved DPI-skift trig ikke hvert kontrolelement enkeltvis. Et centralt rebuild forhindrer redundant skalering.
Hvis du arbejder med TVirtualImageList, er meget af dette allerede indbygget som koncept, men du skal stadig være opmærksom på ikke at bygge ekstra egen skalering ovenpå.
Fallback-strategier: Hvad gør man, hvis ikke alle ikonstørrelser foreligger?
I realiteten har du ikke altid alle assets i alle størrelser. Du har brug for en klar fallback-strategi for at undgå tilfældige resultater:
- Foretræk nedskalering: Hellere nedskalere fra 64 px til 32 px end opskalere fra 16 px til 32 px.
- Definér trin: Fastlæg hvilke målstørrelser du reelt understøtter (f.eks. 16/20/24/32/40/48) og map DPI korrekt hertil.
- Test transparens: Ved fallbacks vær særlig opmærksom på alpha — der opstår ofte halos.
En typisk fejl er blot at tage den nærmeste størrelse. Det får den oplevede skarphed til at variere fra ikon til ikon. Bedre er en fast, dokumenteret mapping-plan.
Hvornår er indsatsen virkelig berettiget?
Der er tre klare indikatorer på, at en ordentlig High-DPI-ikon-pipeline er berettiget:
- Dine brugere arbejder med blandede skærme (bærbar + ekstern) eller meget via RDP.
- Applikationen er langtidsholdbar og vedligeholdes over år — UI-opfattelsen er en del af accepten.
- Du har under alle omstændigheder moderniseringsopgaver foran dig (hæv DPI-awareness, skift controls, revider toolbar-layout).
Hvis appen derimod kun kører på et fast kiosk-system med identisk opløsning, kan emnet holdes minimalt: lever én passende ikonstørrelse, sæt DPI-awareness korrekt, og så er det ofte tilstrækkeligt.
Konklusion: High-DPI er ikke et kosmetisk detalje, men en renderingbeslutning
Uskarpe ikoner er i VCL sjældent en isoleret fejl; det er et tegn på en usikker kæde af kilder, skalering og caching. Den mest robuste tilgang er at levere ikoner i flere opløsninger og distribuere dem konsekvent gennem en central pipeline (f.eks. ImageCollection/VirtualImageList eller et eget ikonlag). Runtime-skalering giver mening, når du har dynamiske kilder eller sammensatte symboler — men kun i kombination med en master-kilde, DPI-baseret cache og klare rebuild-regler.
Hvis du har konkrete symptomer (DPI-skift ødelægger ikoner, halos i kanten, forkerte størrelser efter RDP), er det værd at isolere problemet og behandle det som en lille arkitekturkomponent i stedet for at stable workarounds per form. Hvis du har brug for støtte til debugging eller en trinvis modernisering, finder du her det passende udgangspunkt: Kontakt zur Net-Base Software GmbH.
Til dette emne er Timagelist High Dpi og Delphi Vcl Dpi-Awareness også vigtige. Artiklen sætter disse aspekter i en forståelig kontekst og viser, hvad der betyder noget i praksis.
Næste trin
Når emnet bliver til et reelt projekt, bør arkitektur, eksisterende systemer og drift tidligt vurderes samlet.
Vi støtter ikke kun ved enkeltspørsmål, men også når kildekodeudsnit, legacy-komponenter eller portalidéer skal udvikles til et robust virksomhedsprojekt.
- Eksisterende tilstand, målbillede og tekniske risici vurderes samlet.
- REST, dataadgang, portaler og udrulning bliver ikke udskudt som efterfølgende opgaver.
- De ser tidligt, hvilken vej der er økonomisk og driftsmæssigt bæredygtig.