Now that Microsoft tries to get us to use our displays to their full potential (read: maximum resolution), we as developers have to know how to write programs that are aware of different DPI settings. Most posts and articles I read about this show nice APIs for Win32 and MFC developers and a short remark that building DPI aware applications comes for free with WPF. I don’t believe this. Let me show you why.
I made two images with one of my favorite paint tools: Paint.NET
While drawing these images I switched off any anti aliasing and smoothing so when we see any aliasing, smoothing or smearing it will not be the image.
Next up: a WPF application.
1: <Window x:Class="HighDPITest.MainWindow"
4: Title="High DPI Test" Height="400" Width="400">
11: <Rectangle Grid.Row="0" Grid.Column="0"
12: Width="250" Height="50"
13: Stroke="Black" StrokeThickness="1"/>
14: <TextBlock Grid.Row="0" Grid.Column="0"
15: HorizontalAlignment="Center" VerticalAlignment="Center"
16: Text="250x50" />
17: <Image Grid.Row="1" Grid.Column="0"
18: Source="Picture1.png" Stretch="None"/>
19: <Image Grid.Row="2" Grid.Column="0"
20: Source="Picture2.png" Stretch="None"/>
As you can see WPF takes the DPI setting of the image into consideration. The 96 DPI image is displayed exactly the way it was drawn. It matches perfectly on my 96 DPI desktop as each dot of the image is mapped exactly on a pixel of the desktop. The second image is smaller and is a bit blurry. It is blurred because WPF skips some dots in the image data; it only needs 96 dots per inch whereas the image contains 120 dots per inch. So when WPF determines the width of the image it calculates 250 / 120 = 2.08 inch ( x 96 = 200 pixels on screen) as opposed to the first image: 250 / 96 = 2.60 inch ( x 96 = 250 pixels on screen)
Let’s change my desktop’s DPI to 120 DPI and see what happens:
First let me explain that this screenshot has been manipulated by me. When you make a screenshot on a 120 DPI screen you end up with a bigger image because there are now 120 dots per inch instead of 96 so in this post I resized the image to make it as big as the first screenshot. The original screenshot can be seen by clicking on the image. (Are you still with me? 😉 )
So what changed? Well it is quite obvious, this time the 120 DPI image is crisp and the 96 DPI image is blurred. The blurring is caused by WPF making up pixels for the desktop because it needs more dots to fill the image on screen than are available in the image data.
Imagine a graphics designer making a logo for you to use in your application and let’s assume he (or she) creates a bitmap image. How would you use the logo in your WPF application?
My guess is that the designer would not like WPF to skip pixels or invent extra pixels but at the same time you would want your users to be able to switch their display’s DPI. You have a couple of options:
- Have the designer create the logo at several DPI levels and load the image that matches the display’s DPI. To retrieve the current DPI (yes, a display might have a horizontal DPI that differs from the vertical DPI (to make thing even worse)):
1: void MainWindowOnLoaded(object sender, RoutedEventArgs e)
3: Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
4: double horizontalDPI = m.M11 * 96.0;
5: double verticalDPI = m.M22 * 96.0;
The good is that the image will look crisp and it will have the right size. Unfortunately you do not know at what level the user will set the display DPI so you might need to draw quite a few images. Draw at least the most obvious ones: 96, 120, 144 DPI and while you’re at it do a 300 DPI for printing too.
- Write a special control that always renders an image one dot per pixel. Like this. The good is that it will be pixel perfect but its size will be dependent on the display’s DPI: higher DPI –> smaller image and thus the exact opposite of what the user wanted. Users up their DPI to get bigger text and images. This might also be an option for situations where you need pixel perfect images and do not want any anti aliasing or stretching. (Imagine your doctor examining your MRI Scan looking for a tiny blockade in an artery and he can’t be sure whether that pixel is showing a barrier in your brain or it has been interpolated by WPF)
- Use vector drawings. They scale quite nicely (don’t overdo it) but you will not always be able to do this because some images look more like photographs and you can’t easily redraw a photograph in vector format.
The browser is another bump in the road. This article gives a nice overview and even explains a bit about Silverlight and Flash.
In WPF it is quite easy to write a DPI aware application as long as you are sticking to vectors (controls and images) but when you need to display bitmaps, you need to plan ahead and do some extra thinking.