[WPF] カラーホイール

#概要
カラーピッカーによくあるようなHSV色空間の色相を表現できる丸いやつです。
`DrawColorWheel`から`ImageSource`として返されます。ただ、各ピクセルに角度ごとの色を埋めているだけですが、`size = 20`くらいでも小さく表示すれば綺麗になると思います。描画に関してもっと良い方法はあると思いますが、知らないので教えていただきたいです。
なお、楕円には対応していません。

#必要なもの

```C#:ColorWheel.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace #NAMESPACE#
{
    public class ColorWheel : Control
    {
        static ColorWheel()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorWheel), new FrameworkPropertyMetadata(typeof(ColorWheel)));
        }

        #region == Radius ==

        public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(ColorWheel), new PropertyMetadata(0d, (d, e) =>
        {
            if (d is ColorWheel colorWheel)
            {
                double radius = (double)e.NewValue;
                double diameter = radius * 2;
                colorWheel.Width = diameter;
                colorWheel.Height = diameter;
                colorWheel.Clip = new EllipseGeometry(new Point(radius, radius), radius, radius);
            }
        }));
        public double Radius { get => (double)GetValue(RadiusProperty); set => SetValue(RadiusProperty, value); }

        #endregion
        #region == Quality ==

        public int Quality { get => (int)GetValue(QualityProperty); set => SetValue(QualityProperty, value); }
        public static readonly DependencyProperty QualityProperty = DependencyProperty.Register("Quality", typeof(int), typeof(ColorWheel), new PropertyMetadata(0, (d, e) =>
        {
            if (d is ColorWheel colorWheel)
            {
                colorWheel.Source = DrawColorWheel(colorWheel.Quality);
            }
        }));

        #endregion

        #region == Source ==

        private static readonly DependencyPropertyKey SourcePropertyKey = DependencyProperty.RegisterReadOnly("Source", typeof(ImageSource), typeof(ColorWheel), new PropertyMetadata());
        public static readonly DependencyProperty SourceProperty = SourcePropertyKey.DependencyProperty;
        public ImageSource Source { get => (ImageSource)GetValue(SourceProperty); private set => SetValue(SourcePropertyKey, value); }

        #endregion

        private static readonly double TwicePI = 2 * Math.PI;

        private static ImageSource DrawColorWheel(int radius)
        {
            int size = radius * 2;
            WriteableBitmap writeableBitmap = new WriteableBitmap(size, size, 96, 96, PixelFormats.Bgr24, null);

            int length = (int)(TwicePI * size);
            Color[] colors = new Color[length];
            for (int i = 0; i < length; i++)
            {
                colors[i] = GetColorFromHSV((double)i / length, 1, 1);
            }

            byte[] pixels = new byte[size * size * 3];
            for (int y = 0; y < size; y++)
            {
                int disY = y - radius;
                for (int x = 0; x < size; x++)
                {
                    int index = (size * y + x) * 3;
                    Color color = colors[(int)((Math.Atan2(disY, x - radius) + Math.PI) / TwicePI * length) - 1];
                    pixels[index] = color.B;
                    pixels[index + 1] = color.G;
                    pixels[index + 2] = color.R;
                }
            }

            writeableBitmap.WritePixels(new Int32Rect(0, 0, size, size), pixels, size * 3, 0, 0);
            return writeableBitmap;
        }

        private static Color GetColorFromHSV(double h, double s, double v)
        {
            h = h < 1 ? h : 0;
            double max = v;
            double range = s * max;
            double min = max - range;
            double[] rgb = new double[3];
            int hd = (int)(h * 6) % 6;
            int hdd = (int)(h * 3) % 3;
            rgb[hdd] = max;
            rgb[((hd + 1) / 2 + 1) % 3] = min;
            rgb[(5 - hd) % 3] = (hd % 2 == 0 ? -1 : 1) * (h * 6 - hdd * 2 - 1) * range + min;
            return Color.FromRgb((byte)(rgb[0] * 255), (byte)(rgb[1] * 255), (byte)(rgb[2] * 255));
        }
    }
}
```

```XML:Themes/Generic.xaml
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:#NAMESPACE#">
    
    <Style TargetType="{x:Type local:ColorWheel}">
        <Setter Property="Radius" Value="50"/>
        <Setter Property="Quality" Value="20"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ColorWheel}">
                    <Grid>
                        <Image Source="{TemplateBinding Source}" Stretch="UniformToFill"/>
                        <Rectangle>
                            <Rectangle.Fill>
                                <RadialGradientBrush>
                                    <GradientStop Color="White"/>
                                    <GradientStop Color="Transparent" Offset="1"/>
                                </RadialGradientBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
```

#使用例
```XML:MainWindow.xaml
<Window x:Class="#NAMESPACE#.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:#NAMESPACE#"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:ColorWheel/>
    </Grid>
</Window>
```