Let’s talk about graphics in WPF. We would talk about the WPF graphics architecture followed by vector graphics, bitmaps and videos, WPF resolution independence and effects provided by WPF.
It might seem interesting to you WPF is built on DirectX 3D rendering engine for all its accelerated 3D rendering pipeline and 2D bitmap rendering. This enables WPF to take the advantage of the modern graphics card engines to a large extent through the Media Integration Layer (MIL). MIL being the unmanaged part helps to make use of the directx to huge extent. Also it helps the main .net (WPF) to communicate with the directx.
You should note here that the MIL unmanaged API is not public at the moment but its presence effects the public .NET API. These capabilities allow the user to make use of the graphic capabilities of the modern computers hardware. Now it becomes import for you learn these capabilities so that you could get the best performance out of the WPF graphics.
One of the major advantages of WPF is its scalability to support higher resolution for higher DPI screens. WPF provides this facility by providing transform capability to all the elements of the Visual Tree. For example if we apply scale transform to an element in the visual tree then the transform will apply to the node and all its children. This is all taken care by the WPF engine internally so that the developer does not have to worry about all that. You do not need to know anything about the GDI32 or GDI+ to get the work done.
There are several other types of transforms as well that can be applied to WPF elements. These include rotate, translation, scaling and shearing. It does not support perspective transform. If you want the perspective transform then you need to use the WPF 3D features. Now if you would use the rotate transform you would notice that the control is not actually rotated but WPF creates a rectangular bound around the control and use the perpetual effect on win32 to show this transform.
As we would see in the image below we have applied a rotate transform onto the combobox.
The transforms that WPF offers are the ones that we can use to affect the 3X3 transform matrix. These transforms are translate, rotate, skew and scale. To use the perspective transform we need to use the WPF 3D features.
Composition and Integration
Another feature in WPF is to integrate images and media to a WPF application. You can have the video content inside any WPF content control including a button. These kinds of things are really difficult to do in previous version of windows applications. In Win32 overlapping controls are not completely support but in WPF you could have overlapping controls and play around with the opacity of each control. The reason WPF can support such graphics is that WPF has much simpler rendering model. WPF follows the painter algorithm to paint the window. So painting a WPF window is like a painting a picture that means the things you paint later will appear on the surface above the ones painted before.
Procedural vs. Declarative
Declarative style is when you tell the computer what you want and let the computer decide on how to achieve that. The best example to see is SQL, we just tell SQL what results we would like and SQL fetches it for us. But this not quite how the user interface programming works, we need to specify some kind of method that we need to specify which will be called when the element is ready to be displayed. As we can see the code below that we have tapped into the code for OnRender method.
Now let’s use this CustomRender element of ours.
Now before we run this application let’s add a trace to the OnRender method and see how many times WPF makes call to OnRender method. What this trace will do is it will print a message onto the debug window whenever this method is hit.
Let’s fill in the additional details.
We will notice that OnRender method is called only once. Let’s see if OnRender is called when we transform the element. Add the following code to the code behind file of the RectangleEllipse.xaml.
Now when we run the application and get a scale transform on mouse down, we will see that the OnRender method is still not called. So let’s understand what’s happening here. When OnRender is called the drawingcontext will remember what we asked it to render i.e. a rectangle and an ellipse. It will pass this information to the MIL to perform the actual render. What WPF does is prepares the message with the drawing context of needs to be rendered to the MIL and then MIL renders graphics on its own. So we tell WPF to scale the element what WPF does is passes this message with the details that need to be changed onto the MIL to scale the graphic which was passed to the MIL earlier. And MIL uses the copy of the drawing context to rebuild the new image with the transform. The MIL retains the copy of each drawing associated with each node of the Visual tree.
But this is not the way we generally send the instruction to WPF. Mostly we use the declarative syntax and allow WPF the way it wants to display the graphics. We use XAML here instead of C#. The difference between declarative and procedural programming is that in declarative programming we just define the outcome whereas in Procedural we define the steps we need to follow in order to achieve the desired outcome.
It’s difficult to work with procedural approach and is more error prone. We can achieve the same using declarative syntax in C#.
Primitives vs. Shapes
In the last example we saw a difference between the procedural and declarative code. In the procedural code we typed Rect whereas in the declarative code we typed Rectangle. This actually represents the difference between the presentation core and the presentation framework. The Presentation Core provides a direct wrapper on top of the MIL. This means that the type Presentation Core uses are fairly close to the types MIL works with. These are generally the simple low level primitive types which do not have a lot of information. We cannot exactly identify Rect on the screen as the same rectangle might be used at 20 different places on the screen.
The types we use in the declarative code are much higher level. It will handle events and we can identify the presence of the rectangle exactly on the screen. The higer level types are derived from the Shape base class and have a much deeper set of services. Also these cost more in terms of resources as a rectangle is a much heavier object than rect struct.
The reason for this is that WPF has a split between the Core and Framework. So if we do not need the framework services then we do not need to pay the cost for it and work out with the core type. So the reason for using core type is
- To increase the performance by reducing the resources required by the object.
- To use the types to which the framework has not provided any wrapper to like the brushes.
- Do not need high level types.
But most of the time the higher level types are the first and the best choice.
Brushes are the core elements and do not have framework wrapper form them.
We have three types of brushes
- SolidColorBrush – It is the brush which will uniformly paint the surface of an element. We can also add the transparency effect in this brush. To see this action let’s open the WPF project in Blend and add a couple of rectangles to the canvas overlapping each other. Now give different backgrounds to both of them and set the alpha value of the rectangle above as 50% and we would see something like below.
- LinearGradientBrush – Now let’s add another rectangle and select GradientBrush in the Editor.
Now let’s click the gradient bar to add new stops to add the colors for the gradient brush.
We can also select the brush transform tool to select the direction of gradient.
Now let’s have a look at the xaml for this.
- RadialGradientBrush – Now when we selected the gradient brush the default was LinearGradientBrush but we can also select a RadialGradientBrush.
WPF represents shapes as geometry objects. Geometry is an abstract base class and the most powerful geometry is path Geometry. This defines shapes as a series of figures whose outlines are defined as a series of LineSegment. Let’s a geometry is action. When add the following code to the xaml we do not see a shape but some cryptic text. I will tell you what happened here. Geomerty is path of core API and so they are not allowed in a UI tree directly. We need to pass the geometry to a drawing object or shapes. So whats happened here is that UserControl has figured out that PathGeometry is not a UI element so it has called the ToString on the geometry and displayed the result. SO render this geometry we need to put it into a suitable shape.
Let’s put this geometry into a path and we will be able to see our geometry.
So all the shapes work in a way that they override the OnRender method under the hood and pass the information to the MIL for display. We can tweak the numbers to change the shape. Now let’s see how the multi figure PathGeometry will be displayed. Let’s add a second figure which will remain inside the first figure. We will see something like below.
So we see a hole in the object which is actually not a hole but WPF does this. When the figures overlap they cancel each other out. WPF counts the number of figures at any point and if the number is odd it fills in the shape and if the number is even WPF leaves it unfilled.
The other types of geometries are
Another geometry class is GeometryGroup. It helps us combine multiple geometries into single geometry where each geometry has its own place. Let’s see this on action.
When we create a path using the Pen tool in expression blend we will not see anything like PathGeometry in the xaml produced. We will something like shown below. The Data starts with M which means move to this point start the path. Then we have C which is and has four co-ordinates for itself. We keep moving further this way and at the end we will see z which means the end.
We can use thin inside any PathGeometry or the data property of the Path. When we represent the geometry as a string it is not represented as a PathGeometry but a StreamGeometry. It does not need to contain the Figures objects.
As we saw in the previous section that we had to put the geometry inside a path so that it is rendered properly. But as you might know that Path is a framework element and there are cost involved with its rendering. We not necessarily need a framework element to display our element. Instead we can use WPF drawing structure to display geometries. Drawing is a collection of drawing objects. We can wrap all the drawing objects that MIL can display in a DrawingGroup and using this we can paint geometries, bitmaps, text and videos. The MIL retains the visual representation of all the elements all all nodes of the visual tree so that it can display updates as well and the representation it uses is drawing. So we are talking to MIL in its own language. So this makes drawing the efficient representation in WPF graphics. We do not need to write code to create these drawing as it can be done in XAML. Like geometry dwaring is just a description and does not know how to render itself. So we need to put it inside a DrawingVisual to render it. Let’s see this code. DrawingGroup is at the root of a drawing. DrawingGroup acts as a container for a series of DrawingObjects. We need add this DrawingGroup to the source of an image drawing.
Instead of setting this ImageSource of the image we can set GridBackground of the Grid to DrawingBrush will get the similar effect. This is really good because if we are a lot of framework (above 5000) then we can see a significant drop in the performance. Drawing elements are really useful as they reduce the framework element count and improving the performance of the WPF application. But have to pay cost for using the drawing as from the event handling perspective the whole drawing seems to be one element. Drawings behave like bitmap but these are vector.
The drawing brushes that we saw are an example of Composite drawing brushes. They itself contain brushes so we can nested inside another brush and so on. WPF offers another brush named VisualBrush. So rather than using a drawing we can use any VisualTree or Sub Tree as a Visual Brush. Let’ see this in action. We have the fill of the grid to a visual brush. The contents of the grid are a brush but the contents are not interactive.
All the composite brushes ImageBrush, DrawingBrush and VisualBrush share a base class called a TileBrush. This base class provides properties as below
- Stretch – This property determines how the content would scale on render. It has values like Fill, None,Uniform,UniformToFill.
- Viewbox – The Viewbox specifies the source area of interest in an image. So if want only a part if the image we can use as shown below. We can also specify the ViewboxUnits as RelativeToBouindingBox or Absolute pixels. So a Viewbox defines the source material to be used as a brush material. It defaults to relative units.
- Viewport – Viewport defines as to where the source material is projected onto the target. It also defaults to relative units.
WPF supports transforms on any part of the visual tree. The transforms supported by Visual Tree are:
We can use them individually or combine them in a transform group. There are two ways in which we can apply transform to a UI element.
- RenderTransform – This will affect only on the element which it is applied and WPF makes no attempt to reform the layout to accommodate the change in the element.
- LayoutTransform – This will affect the whole visual tree to accommodate the change in the user interface.
Transform is also a core type and we can apply transform to a drawing or geometry. Also we can apply transform to the brushes as well.
Clipping and OpacityMask
These are the effect that can be applied to any element in the visual tree. We can use geometry to clip any element or we can apply an opacity mask. When we clip an element then anything which is not inside the geometry becomes invisible. WPF anti aliases the border to provide a smooth look. So anything in a clip is clipped with sharp abrupt edge whereas while using the opacity mask we modulate the opacity. Let’s see this in action.
But this clipping comes with a cost. When we apply clip to a control then WPF renders the entire element into an off screen buffer and then renders the clip result into the main screen. So each element we apply clip to needs an additional buffer.
The same rendering as clip applies to opacity mask as well. It could be even worse as it might end up switching from Hardware acceleration to Software acceleration which might be catastrophic.
WPF also offers a few Bitmap processing effects as well and these can be applied to any element in the visual tree.
We can apply multiple effects by using BitmapEffectGroup. In the image below we see a couple of these effects.
In this section we will have a quick look at the services provided by the animation system for dynamic objects. As we can see in the image below we need to specify the trigger which will tell when to start the animation. Here we have an event trigger which will start the animation when this Rectangle.Loaded event occurs. Then we have defined that BeginStoryboard will occur when the specified event is triggered. We can also pause or stop an animation. A storyboard is an orchestration of the steps that the animation will go through. For the demo purpose I have included only one step here. We have also specified the property the animation will change. So the animation type should be corresponding to the specified property. Like below the width property is of type double so we have used DoubleAnimation. We have specified the initial and final values. Also it has the time for the animation will run.
WPF allows us to incorporate 3d content into our WPF application. The Viewport3D element hosts a 3D model into our WPF application. As the layout only knows about the 2D content so for the layout Viewport is just a rectangle that displays some content. As see in the image below we need to include a 3D camera and a 3D Model will have at least one light and a shape. Generally we will export this 3D model from a 3D design tool. But we can the code below to the xaml file and see how it would look.
And this how it will appear
The code for this post can be found here.
Any questions, comments and feedback are most welcome.