diff --git a/src/Avalonia.Controls/ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs b/src/Avalonia.Controls/ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
new file mode 100644
index 0000000000..e95c63da33
--- /dev/null
+++ b/src/Avalonia.Controls/ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
@@ -0,0 +1,205 @@
+// This source file is adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml)
+//
+// Licensed to The Avalonia Project under the MIT License.
+
+using Avalonia.Media;
+
+namespace Avalonia.Controls.Primitives
+{
+ ///
+ public partial class ColorSpectrum
+ {
+ ///
+ /// Gets or sets the currently selected color in the RGB color model.
+ ///
+ ///
+ /// For control authors use instead to avoid loss
+ /// of precision and color drifting.
+ ///
+ public Color Color
+ {
+ get => GetValue(ColorProperty);
+ set => SetValue(ColorProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ColorProperty =
+ AvaloniaProperty.Register(
+ nameof(Color),
+ Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
+
+ ///
+ /// Gets or sets the two HSV color channels displayed by the spectrum.
+ ///
+ ///
+ /// Internally, the uses the HSV color model.
+ ///
+ public ColorSpectrumChannels Channels
+ {
+ get => GetValue(ChannelsProperty);
+ set => SetValue(ChannelsProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ChannelsProperty =
+ AvaloniaProperty.Register(
+ nameof(Channels),
+ ColorSpectrumChannels.HueSaturation);
+
+ ///
+ /// Gets or sets the currently selected color in the HSV color model.
+ ///
+ ///
+ /// This should be used in all cases instead of the property.
+ /// Internally, the uses the HSV color model and using
+ /// this property will avoid loss of precision and color drifting.
+ ///
+ public HsvColor HsvColor
+ {
+ get => GetValue(HsvColorProperty);
+ set => SetValue(HsvColorProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty HsvColorProperty =
+ AvaloniaProperty.Register(nameof(HsvColor), new HsvColor(1, 0, 0, 1));
+
+ ///
+ /// Gets or sets the maximum value of the Hue channel in the range from 0..359.
+ /// This property must be greater than .
+ ///
+ ///
+ /// Internally, the uses the HSV color model.
+ ///
+ public int MaxHue
+ {
+ get => GetValue(MaxHueProperty);
+ set => SetValue(MaxHueProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MaxHueProperty =
+ AvaloniaProperty.Register(nameof(MaxHue), 359);
+
+ ///
+ /// Gets or sets the maximum value of the Saturation channel in the range from 0..100.
+ /// This property must be greater than .
+ ///
+ ///
+ /// Internally, the uses the HSV color model.
+ ///
+ public int MaxSaturation
+ {
+ get => GetValue(MaxSaturationProperty);
+ set => SetValue(MaxSaturationProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MaxSaturationProperty =
+ AvaloniaProperty.Register(nameof(MaxSaturation), 100);
+
+ ///
+ /// Gets or sets the maximum value of the Value channel in the range from 0..100.
+ /// This property must be greater than .
+ ///
+ ///
+ /// Internally, the uses the HSV color model.
+ ///
+ public int MaxValue
+ {
+ get => GetValue(MaxValueProperty);
+ set => SetValue(MaxValueProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MaxValueProperty =
+ AvaloniaProperty.Register(nameof(MaxValue), 100);
+
+ ///
+ /// Gets or sets the minimum value of the Hue channel in the range from 0..359.
+ /// This property must be less than .
+ ///
+ ///
+ /// Internally, the uses the HSV color model.
+ ///
+ public int MinHue
+ {
+ get => GetValue(MinHueProperty);
+ set => SetValue(MinHueProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MinHueProperty =
+ AvaloniaProperty.Register(nameof(MinHue), 0);
+
+ ///
+ /// Gets or sets the minimum value of the Saturation channel in the range from 0..100.
+ /// This property must be less than .
+ ///
+ ///
+ /// Internally, the uses the HSV color model.
+ ///
+ public int MinSaturation
+ {
+ get => GetValue(MinSaturationProperty);
+ set => SetValue(MinSaturationProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MinSaturationProperty =
+ AvaloniaProperty.Register(nameof(MinSaturation), 0);
+
+ ///
+ /// Gets or sets the minimum value of the Value channel in the range from 0..100.
+ /// This property must be less than .
+ ///
+ ///
+ /// Internally, the uses the HSV color model.
+ ///
+ public int MinValue
+ {
+ get => GetValue(MinValueProperty);
+ set => SetValue(MinValueProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MinValueProperty =
+ AvaloniaProperty.Register(nameof(MinValue), 0);
+
+ ///
+ /// Gets or sets the displayed shape of the spectrum.
+ ///
+ public ColorSpectrumShape Shape
+ {
+ get => GetValue(ShapeProperty);
+ set => SetValue(ShapeProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ShapeProperty =
+ AvaloniaProperty.Register(
+ nameof(Shape),
+ ColorSpectrumShape.Box);
+ }
+}
diff --git a/src/Avalonia.Controls/ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls/ColorPicker/ColorSpectrum/ColorSpectrum.cs
new file mode 100644
index 0000000000..e4096bcfda
--- /dev/null
+++ b/src/Avalonia.Controls/ColorPicker/ColorSpectrum/ColorSpectrum.cs
@@ -0,0 +1,1615 @@
+// This source file is adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml)
+//
+// Licensed to The Avalonia Project under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Controls.Shapes;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Threading;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls.Primitives
+{
+ ///
+ /// A two dimensional spectrum for color selection.
+ ///
+ public partial class ColorSpectrum : TemplatedControl
+ {
+ ///
+ /// Event for when the selected color changes within the spectrum.
+ ///
+ public event EventHandler? ColorChanged;
+
+ private bool _updatingColor = false;
+ private bool _updatingHsvColor = false;
+ private bool _isPointerOver = false;
+ private bool _isPointerPressed = false;
+ private bool _shouldShowLargeSelection = false;
+ private List _hsvValues = new List();
+
+ private IDisposable? _layoutRootDisposable;
+
+ // XAML template parts
+ private Grid? _layoutRoot;
+ private Grid? _sizingGrid;
+
+ private Rectangle? _spectrumRectangle;
+ private Ellipse? _spectrumEllipse;
+ private Rectangle? _spectrumOverlayRectangle;
+ private Ellipse? _spectrumOverlayEllipse;
+
+ private Canvas? _inputTarget;
+ private Panel? _selectionEllipsePanel;
+
+ private ToolTip? _colorNameToolTip;
+
+ // Put the spectrum images in a bitmap, which is then given to an ImageBrush.
+ private WriteableBitmap? _hueRedBitmap;
+ private WriteableBitmap? _hueYellowBitmap;
+ private WriteableBitmap? _hueGreenBitmap;
+ private WriteableBitmap? _hueCyanBitmap;
+ private WriteableBitmap? _hueBlueBitmap;
+ private WriteableBitmap? _huePurpleBitmap;
+
+ private WriteableBitmap? _saturationMinimumBitmap;
+ private WriteableBitmap? _saturationMaximumBitmap;
+
+ private WriteableBitmap? _valueBitmap;
+
+ // Fields used by UpdateEllipse() to ensure that it's using the data
+ // associated with the last call to CreateBitmapsAndColorMap(),
+ // in order to function properly while the asynchronous bitmap creation
+ // is in progress.
+ private ColorSpectrumShape _shapeFromLastBitmapCreation = ColorSpectrumShape.Box;
+ private ColorSpectrumChannels _componentsFromLastBitmapCreation = ColorSpectrumChannels.HueSaturation;
+ private double _imageWidthFromLastBitmapCreation = 0.0;
+ private double _imageHeightFromLastBitmapCreation = 0.0;
+ private int _minHueFromLastBitmapCreation = 0;
+ private int _maxHueFromLastBitmapCreation = 0;
+ private int _minSaturationFromLastBitmapCreation = 0;
+ private int _maxSaturationFromLastBitmapCreation = 0;
+ private int _minValueFromLastBitmapCreation = 0;
+ private int _maxValueFromLastBitmapCreation = 0;
+
+ private Color _oldColor = Color.FromArgb(255, 255, 255, 255);
+ private HsvColor _oldHsvColor = HsvColor.FromAhsv(0.0f, 0.0f, 1.0f, 1.0f);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorSpectrum()
+ {
+ _shapeFromLastBitmapCreation = Shape;
+ _componentsFromLastBitmapCreation = Channels;
+ _imageWidthFromLastBitmapCreation = 0;
+ _imageHeightFromLastBitmapCreation = 0;
+ _minHueFromLastBitmapCreation = MinHue;
+ _maxHueFromLastBitmapCreation = MaxHue;
+ _minSaturationFromLastBitmapCreation = MinSaturation;
+ _maxSaturationFromLastBitmapCreation = MaxSaturation;
+ _minValueFromLastBitmapCreation = MinValue;
+ _maxValueFromLastBitmapCreation = MaxValue;
+ }
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ UnregisterEvents();
+
+ _layoutRoot = e.NameScope.Find("LayoutRoot");
+ _sizingGrid = e.NameScope.Find("SizingGrid");
+ _spectrumRectangle = e.NameScope.Find("SpectrumRectangle");
+ _spectrumEllipse = e.NameScope.Find("SpectrumEllipse");
+ _spectrumOverlayRectangle = e.NameScope.Find("SpectrumOverlayRectangle");
+ _spectrumOverlayEllipse = e.NameScope.Find("SpectrumOverlayEllipse");
+ _inputTarget = e.NameScope.Find