16
votes

I need to create a multi-step gradient along a circular path, as demonstrated in the following image:

Wheel Gradient

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?

3
Why the color choices? It's not consistent with any pitch-color mapping I've seen before, and synesthesics tend to perceive this quite differently anyway. You also don't appear to match the Color Piano Project exactly. - Cody Gray
The color choices are arbitrary and chosen for aesthetic reasons only. There is no correlation to pitch. - Charlie

3 Answers

17
votes

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:

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:

enter image description here

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.

1
votes

In GDI+/Winforms you can use the PathGradientBrush to do this:

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.

0
votes

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.