I need to create a multi-step gradient along a circular path, as demonstrated in the following image:
Does anyone have any ideas on how this could be accomplished in XAML rather than code? Would it be possible to use the existing gradient brushes or composite them somehow to achieve this effect?
You can get a cross-radial effect by using a non-affine transformation such as a perspective transform. I used the ideas in this article by Charles Petzold:
- Non-Affine Transforms in 2D?
to create a XAML-only annular region with a cross-radial gradient. Here is the markup:
<Canvas x:Name="LayoutRoot">
<Canvas.Resources>
<x:Array x:Key="sampleData" Type="sys:Object">
<x:Array Type="sys:Object">
<sys:Double>0</sys:Double>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Red" Offset="0"/>
<GradientStop Color="Yellow" Offset="0.5"/>
<GradientStop Color="Blue" Offset="1"/>
</LinearGradientBrush>
</x:Array>
<x:Array Type="sys:Object">
<sys:Double>90</sys:Double>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Blue" Offset="0"/>
<GradientStop Color="Green" Offset="0.5"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</x:Array>
<x:Array Type="sys:Object">
<sys:Double>180</sys:Double>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Red" Offset="0"/>
<GradientStop Color="Yellow" Offset="0.5"/>
<GradientStop Color="Blue" Offset="1"/>
</LinearGradientBrush>
</x:Array>
<x:Array Type="sys:Object">
<sys:Double>270</sys:Double>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Blue" Offset="0"/>
<GradientStop Color="Green" Offset="0.5"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</x:Array>
</x:Array>
</Canvas.Resources>
<ItemsControl ItemsSource="{StaticResource sampleData}">
<ItemsControl.OpacityMask>
<RadialGradientBrush>
<GradientStop Color="Transparent" Offset="0.95"/>
<GradientStop Color="White" Offset="0.949"/>
<GradientStop Color="White" Offset="0.501"/>
<GradientStop Color="Transparent" Offset="0.5"/>
</RadialGradientBrush>
</ItemsControl.OpacityMask>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ItemsPresenter/>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Width="1" Height="1">
<Canvas.RenderTransform>
<RotateTransform Angle="{Binding [0]}" CenterX="124" CenterY="124"/>
</Canvas.RenderTransform>
<Viewport3D Width="250" Height="250">
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions="0 0 0, 0 1 0, 1 0 0, 1 1 0" TextureCoordinates="0 1, 0 0, 1 1, 1 0" TriangleIndices="0 2 1, 2 3 1"/>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial Brush="{Binding [1]}"/>
</GeometryModel3D.Material>
<GeometryModel3D.Transform>
<MatrixTransform3D Matrix="0.002,0,0,0,-0.499,-0.498,0,-0.998,0,0,1,0,0.499,0.5,0,1"/>
</GeometryModel3D.Transform>
</GeometryModel3D>
<AmbientLight Color="White" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
<Viewport3D.Camera>
<OrthographicCamera Position="0.5 0.5 1" LookDirection="0 0 -1" UpDirection="0 1 0" Width="1"/>
</Viewport3D.Camera>
</Viewport3D>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
and here is the visual result:
The effect uses a data source collection with items that have two properties, an angle and a brush. It draw four quadrants (up, right, down and left) using a different brush for each quadrant. Then the whole thing is clipped to the annular region with an opacity mask.
In GDI+/Winforms you can use the PathGradientBrush to do this:
http://www.bobpowell.net/pgb.htm
http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.pathgradientbrush.aspx
Unfortunately there is no support for a PathGradientBrush in WPF but a few people have asked for it here:
http://dotnet.uservoice.com/forums/40583-wpf-feature-suggestions/suggestions/480949-add-a-pathgradientbrush-like-in-winforms-
(might be worth casting your vote too!)
Because of the lack of support you cannot do it directly in XAML, you could however use GDI+ code to create an image and then use the image in your XAML. This might give you better performance than using a non-affine transformation.
Take a look at Shazzam You could write a pixelshader that renders this gradient.
I think that in the long run this will be easier than combining linear gradients. An other option is to simply draw a bitmap and use it.