Pixel Snapping in WPF Applications
The WPF graphics system uses device-independent units to enable resolution and device independence. Each device independent pixel automatically scales with the system's dots per inch (dpi) setting. This provides WPF applications proper scaling for different dpi settings and makes the application automatically dpi-aware.
However, this dpi independence can create irregular edge rendering due to anti-aliasing. These artifacts, commonly seen as blurry, or semi-transparent, edges can occur when the location of an edge falls in the middle of a device pixel rather than between device pixels. To address this issue, WPF provides a way for object edges in a visual tree to snap, or become fixed, to device pixels through pixel snapping, eliminating the semi-transparent edges produced by anti-aliasing.
Pixel snapping is a means to suppress these visual artifacts by applying small offsets to the geometry of the visual to align the geometry to device pixels.
This topic contains the following sections.
- Pixel Snapping for Anti-aliased Rendering
- Guidelines
- Bitmap Images
- Related Topics
Pixel Snapping for Anti-aliased Rendering
Sharp Lines
Without pixel snapping, anti-aliased rendered lines may appear semi-transparent if the edge does not fall between device pixels. The following illustration shows the output of a single pixel width anti-aliased line that falls in the middle of a device pixel (left) and a single pixel width line that falls between device pixels (right).
Anti-aliased line rendering.
With pixel snapping, anti-aliased lines snap, or become fixed, to the device pixels and appear sharp, eliminating semi-transparent line rendering. The following example demonstrates the affect that the SnapsToDevicePixels property has on a single pixel line. Slowly resizing the window shows the visual artifacts on an unsnapped line (left), and the fixed size of the snapped line (right) as their position change.
<Page x:Class="PixelSnapping.Lines"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Lines" Name="linesPage"
>
<StackPanel Width="150" Margin="7" Orientation="Horizontal">
<!-- Single pixel line with pixel snapping turned OFF.-->
<Rectangle SnapsToDevicePixels="False"
Width="45.5" Margin="10" Height="1" Fill="Red"/>
<!-- Single pixel line with pixel snapping turned ON.-->
<Rectangle SnapsToDevicePixels="True"
Width="45.5" Margin="10" Height="1" Fill="Red"/>
</StackPanel>
<!-- Background Grid -->
<Page.Background>
<DrawingBrush Viewport="0,0,10,10" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="White">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,1,1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z " Brush="#CCCCFF" />
<GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="#CCCCFF" />
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Page.Background>
</Page>
Note: |
---|
SnapsToDevicePixels only affects elements that run through the layout pass. Guidelines can be set on a Drawing using the DrawingGroup object's GuidelineSet property. To manually set the guidelines of a Visual, create new guidelines using the VisualYSnappingGuidelines and VisualXSnappingGuidelines properties. |
Pixel snapping only sharpens horizontal and vertical lines and does not affect diagonal lines.
Adjoining Objects
Anti-aliasing can also cause visual artifacts when the edges between objects abut and the adjoining edge is not exactly aligned between a row or a column of device pixels. The anti-aliased scene may soften the edge using the underlying background color producing a seeping effect in which the edge between each object appears transparent in color. The following illustration demonstrates this seeping effect.
Abutting objects with seeping effect.
With pixel snapping enabled, abutting edges are snapped to the device pixels to remove the seeping effect. The following example demonstrates the affect of the SnapsToDevicePixels property on abutting objects. Slowly resizing the window demonstrates the seeping issue on the unsnapped rectangles (left) while the snapped rectangles (right) stay together with no visual irregularities.
<Page x:Class="PixelSnapping.Seeping"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Seeping"
>
<StackPanel Orientation="Horizontal" Height="100">
<Border
SnapsToDevicePixels="False"
Margin="10" BorderThickness="1" BorderBrush="Black" Height="80" Background="White">
<StackPanel Height="100.0">
<Rectangle Width="20" Height="20" Fill="Red"/>
<Rectangle Width="20" Height="20" Fill="Red"/>
<Rectangle Width="20" Height="20" Fill="Red"/>
<Rectangle Width="20" Height="20" Fill="Red"/>
</StackPanel>
</Border>
<Border
SnapsToDevicePixels="True"
Margin="10" BorderThickness="1" BorderBrush="Black" Height="80" Background="White">
<StackPanel Height="100.0">
<Rectangle Width="20" Height="20" Fill="Red"/>
<Rectangle Width="20" Height="20" Fill="Red"/>
<Rectangle Width="20" Height="20" Fill="Red"/>
<Rectangle Width="20" Height="20" Fill="Red"/>
</StackPanel>
</Border>
</StackPanel>
<!-- Background Grid -->
<Page.Background>
<DrawingBrush Viewport="0,0,10,10" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="White">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,1,1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z " Brush="#CCCCFF" />
<GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="#CCCCFF" />
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Page.Background>
</Page>
Note that the snapped rectangles do not explicitly set a value for the SnapsToDevicePixels property. The property only needs to be set to true on the root to enable the behavior on all child elements.
Guidelines
At its core, pixel snapping is controlled by guidelines. Guidelines assist in adjusting geometries to a device pixel grid. In most cases, pixel snapping using the SnapsToDevicePixels property will provide the desired outcome. However, this property isn't always available especially when using Drawing objects or dealing directly with a DrawingContext so guidelines must be set to achieve the desired sharpness pixel snapping provides.
To set guidelines on Drawing and DrawingContext objects, the GuidelineSet class is used. This class allows you to create horizontal and vertical guidelines that can be applied to a DrawingGroup or be pushed onto a DrawingContext for subsequent drawing commands. The guidelines instruct the drawing which lines should be snapped to a device pixel. For a detailed example of GuidelineSet usage, see How to: Apply a GuidelineSet to a Drawing.
Guidelines can also be set at the Visual level by changing the horizontal and vertical guideline collections. These are accessible through the VisualYSnappingGuidelines and VisualXSnappingGuidelines properties.
Bitmap Images
Because of the dpi independent nature of WPF, bitmap based UI can have undesired presentation results. Anti-aliased scenes can blur an image due to fractional-pixel alignment issues. This is particularly true for images that contain high frequency changes such as single pixel lines with contrasting elements adjacent to them (such as alternating black and white lines). The following illustration demonstrates the image quality differences of an aligned image (left) and an image offset to not align with device pixels (right).
Image alignment to device pixels.
A common scenario for images in UI is to center an image representing an icon within another object. Because icons are typically small images with high frequency changes, adjustments to the application's layout may need to be made to avoid the visual artifacts produced by anti-aliasing.
To properly center an image, the container should have an even width, height if the image's pixel width, height are even. If the image has an odd pixel width, height, the containing element should also have an odd width, height.
The following example creates a two Border objects that contain an Image. The top border has an even width and height to match that of the image. The bottom border has an odd width and height.
The following illustration shows the output from the example and the affect the container size has on image.
<Page x:Class="PixelSnapping.Images"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Images"
>
<StackPanel>
<!-- Image has a pixel dimension of 144x96. -->
<!-- Because the image has a even width and height,
an even border width and height allows the image to proper center.
-->
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="100">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="sharpness.png" Stretch="None"/>
</Border>
<!-- Image has a pixel dimension of 144x96. -->
<!-- Because the image has a even width and height,
an odd border width and height causes the image to soften.
-->
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Width="201" Height="101">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="sharpness.png" Stretch="None"/>
</Border>
</StackPanel>
<!-- Grid Background -->
<Page.Background>
<DrawingBrush Viewport="0,0,10,10" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="White">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,1,1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z " Brush="#CCCCFF" />
<GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="#CCCCFF" />
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Page.Background>
</Page>
Unfortunately, simply adjusting the container object's size does not guarantee device pixel alignment. The entire layout of an application can have an affect on the alignment of the image. The display dots per inch (dpi) also affects image alignment. In the example above, the image alignment will only work if the display is set to 96 dots per inch (dpi). At any other setting, the layout needs to be adjusted to accommodate the display settings. High frequency images should be avoided whenever possible in Windows Presentation Foundation (WPF) applications.
See Also
Reference
Image
VisualXSnappingGuidelines
VisualYSnappingGuidelines