WPF 实现颜色选择器控件
控件名:ColorPicker
作 者:WPFDevelopersOrg - 黄佳 | 驚鏵
原文链接[1]:https://github.com/WPFDevelopersOrg/WPFDevelopers
框架使用 .NET4 至 .NET6
;Visual Studio 2022
;
1)新增 ColorPicker.cs
代码如下:
包含一个Slider(用于选择色调)、一个Canvas(用于选择饱和度和亮度)、一个Thumb(拖动选择饱和度和亮度的指示器)和一个Button(用于切换颜色类型)。
通过使用ColorPicker控件,用户可以选择一个颜色,并且可以通过绑定SelectedColor属性来获取所选颜色。这个属性是一个依赖属性,支持双向绑定,并且当颜色发生改变时会触发SelectedColorChanged事件。
ColorType属性用于指定颜色类型,可以选择RGB、HSL或HEX三种类型之一。当用户点击Button时,可以循环切换颜色类型。
在控件的模板中,通过TemplatePart特性标记了四个重要的子元素:HueSlider、Canvas、Thumb和Button。在OnApplyTemplate方法中,根据模板找到这些子元素,并注册相应的事件处理程序。
控件通过使用颜色转换工具类ColorUtil实现颜色的转换和计算。当用户在Canvas上点击或拖动Thumb时,会计算得到相应的HSB(色调、饱和度、亮度)值,并将其设置为依赖属性HSB的值。然后根据HSB的值计算出对应的颜色,并更新SelectedColor属性的值。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using WPFDevelopers.Utilities;
namespace WPFDevelopers.Controls
{
[TemplatePart(Name = HueSliderColorTemplateName, Type = typeof(Slider))]
[TemplatePart(Name = CanvasTemplateName, Type = typeof(Canvas))]
[TemplatePart(Name = ThumbTemplateName, Type = typeof(Thumb))]
[TemplatePart(Name = ButtonTemplateName, Type = typeof(Button))]
public class ColorPicker : Control
{
private const string HueSliderColorTemplateName = "PART_HueSlider";
private const string CanvasTemplateName = "PART_Canvas";
private const string ThumbTemplateName = "PART_Thumb";
private const string ButtonTemplateName = "PART_Button";
private static readonly DependencyPropertyKey HueColorPropertyKey =
DependencyProperty.RegisterReadOnly("HueColor", typeof(Color), typeof(ColorPicker),
new PropertyMetadata(Colors.Red));
public static readonly DependencyProperty HueColorProperty = HueColorPropertyKey.DependencyProperty;
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register("SelectedColor", typeof(Color), typeof(ColorPicker),
new FrameworkPropertyMetadata(Colors.Red, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedColorChanged));
private static readonly DependencyPropertyKey HSBPropertyKey =
DependencyProperty.RegisterReadOnly("HSB", typeof(HSB), typeof(ColorPicker),
new PropertyMetadata(new HSB()));
public static readonly DependencyProperty HSBHProperty = HSBPropertyKey.DependencyProperty;
public static readonly DependencyProperty ColorTypeProperty =
DependencyProperty.Register("ColorType", typeof(ColorTypeEnum), typeof(ColorPicker),
new PropertyMetadata(ColorTypeEnum.RGB));
private Button _button;
private Canvas _canvas;
private Slider _hueSliderColor;
private bool _isInnerUpdateSelectedColor;
private Thumb _thumb;
private ColorTypeEnum[] colorTypeEnums;
private int currentGridStateIndex;
static ColorPicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker),
new FrameworkPropertyMetadata(typeof(ColorPicker)));
}
public Color HueColor => (Color) GetValue(HueColorProperty);
public Color SelectedColor
{
get => (Color) GetValue(SelectedColorProperty);
set => SetValue(SelectedColorProperty, value);
}
public HSB HSB => (HSB) GetValue(HSBHProperty);
public ColorTypeEnum ColorType
{
get => (ColorTypeEnum) GetValue(ColorTypeProperty);
set => SetValue(ColorTypeProperty, value);
}
private static void OnSelectedColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = d as ColorPicker;
if (ctrl._isInnerUpdateSelectedColor)
{
ctrl._isInnerUpdateSelectedColor = false;
return;
}
var color = (Color) e.NewValue;
double h = 0, s = 0, b = 0;
ColorUtil.HsbFromColor(color, ref h, ref s, ref b);
var hsb = new HSB {H = h, S = s, B = b};
ctrl.SetValue(HueColorPropertyKey, ColorUtil.ColorFromHsb(hsb.H, 1, 1));
ctrl.SetValue(HSBPropertyKey, hsb);
Canvas.SetLeft(ctrl._thumb, s * ctrl._canvas.ActualWidth - ctrl._thumb.ActualWidth / 2);
Canvas.SetTop(ctrl._thumb, (1 - b) * ctrl._canvas.ActualHeight - ctrl._thumb.ActualHeight / 2);
ctrl._hueSliderColor.Value = 1 - h;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (_hueSliderColor != )
_hueSliderColor.ValueChanged -= HueSliderColor_OnValueChanged;
_canvas = GetTemplateChild(CanvasTemplateName) as Canvas;
if (_canvas != )
{
_canvas.Loaded += Canvas_Loaded;
_canvas.MouseUp += Canvas_MouseUp;
}
_thumb = GetTemplateChild(ThumbTemplateName) as Thumb;
if (_thumb != )
_thumb.DragDelta += Thumb_DragDelta;
_hueSliderColor = GetTemplateChild(HueSliderColorTemplateName) as Slider;
if (_hueSliderColor != )
_hueSliderColor.ValueChanged += HueSliderColor_OnValueChanged;
_button = GetTemplateChild(ButtonTemplateName) as Button;
currentGridStateIndex = 0;
colorTypeEnums = (ColorTypeEnum[]) Enum.GetValues(typeof(ColorTypeEnum));
if (_button != )
_button.Click += Button_Click;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
currentGridStateIndex = (currentGridStateIndex + 1) % colorTypeEnums.Length;
ColorType = colorTypeEnums[currentGridStateIndex];
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
var canvasPosition = e.GetPosition(_canvas);
GetHSB(canvasPosition);
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
var canvasPosition = e.GetPosition(_canvas);
GetHSB(canvasPosition);
}
private void GetHSB(Point point, bool isMove = true)
{
var newLeft = point.X - _thumb.ActualWidth / 2;
var newTop = point.Y - _thumb.ActualHeight / 2;
var thumbW = _thumb.ActualWidth / 2;
var thumbH = _thumb.ActualHeight / 2;
var canvasRight = _canvas.ActualWidth - thumbW;
var canvasBottom = _canvas.ActualHeight - thumbH;
if (newLeft < -thumbW)
newLeft = -thumbW;
else if (newLeft > canvasRight)
newLeft = canvasRight;
if (newTop < -thumbH)
newTop = -thumbH;
else if (newTop > canvasBottom)
newTop = canvasBottom;
if (isMove)
{
Canvas.SetLeft(_thumb, newLeft);
Canvas.SetTop(_thumb, newTop);
}
var hsb = new HSB
{
H = HSB.H, S = (newLeft + thumbW) / _canvas.ActualWidth,
B = 1 - (newTop + thumbH) / _canvas.ActualHeight
};
SetValue(HSBPropertyKey, hsb);
var currentColor = ColorUtil.ColorFromAhsb(1, HSB.H, HSB.S, HSB.B);
if (SelectedColor != currentColor)
{
_isInnerUpdateSelectedColor = true;
SelectedColor = currentColor;
}
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
var point = Mouse.GetPosition(_canvas);
GetHSB(point);
}
private void Canvas_Loaded(object sender, RoutedEventArgs e)
{
var width = (int) _canvas.ActualWidth;
var height = (int) _canvas.ActualHeight;
var point = new Point(width - _thumb.ActualWidth / 2, -_thumb.ActualHeight / 2);
Canvas.SetLeft(_thumb, point.X);
Canvas.SetTop(_thumb, point.Y);
var hsb = new HSB {H = _hueSliderColor.Value, S = HSB.S, B = HSB.B};
SetValue(HSBPropertyKey, hsb);
}
private void HueSliderColor_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (DoubleUtil.AreClose(HSB.H, e.NewValue))
return;
var hsb = new HSB {H = 1 - e.NewValue, S = HSB.S, B = HSB.B};
SetValue(HSBPropertyKey, hsb);
SetValue(HueColorPropertyKey, ColorUtil.ColorFromHsb(HSB.H, 1, 1));
var newLeft = Canvas.GetLeft(_thumb);
var newTop = Canvas.GetTop(_thumb);
var point = new Point(newLeft, newTop);
GetHSB(point, false);
}
}
public enum ColorTypeEnum
{
RGB,
HSL,
HEX
}
}
2)新增 ColorPicker.xaml
代码如下:
在Canvas的背景中使用了DrawingBrush和DrawingGroup来创建渐变和几何形状。 创建了一个线性渐变刷(LinearGradientBrush),渐变的起点是(0,0),终点是(1,0)。其中的GradientStop标签指定了两个渐变停止点,一个偏移量为0,颜色为白色,另一个偏移量为1,颜色绑定到了HueColor属性。 接下来,同样方式创建了另一个线性渐变刷,渐变的起点是(0,0),终点是(0,1)。GradientStop指定了两个渐变停止点,一个偏移量为0,颜色为透明("#00000000"),另一个偏移量为1,颜色为黑色。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WPFDevelopers.Controls"
xmlns:convert="clr-namespace:WPFDevelopers.Converts"
xmlns:helpers="clr-namespace:WPFDevelopers.Helpers"
xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Basic/ControlBasic.xaml" />
</ResourceDictionary.MergedDictionaries>
<convert:ColorToBrushConverter x:Key="WD.ColorToBrushConverter" />
<convert:ColorToRedConverter x:Key="WD.ColorToRedConverter" />
<convert:ColorToGreenConverter x:Key="WD.ColorToGreenConverter" />
<convert:ColorToBlueConverter x:Key="WD.ColorToBlueConverter" />
<convert:ColorTypeToVisibilityConverter x:Key="WD.ColorTypeToVisibilityConverter" />
<convert:ColorToStringConverter x:Key="WD.ColorToStringConverter" />
<convert:HToColorConverter x:Key="WD.HToColorConverter" />
<convert:SToColorConverter x:Key="WD.SToColorConverter" />
<convert:LToColorConverter x:Key="WD.LToColorConverter" />
<LinearGradientBrush x:Key="WD.ColorPickerRainbowBrush" po:Freeze="True">
<GradientStop Color="#FF0000" />
<GradientStop Offset="0.167" Color="#FF00FF" />
<GradientStop Offset="0.334" Color="#0000FF" />
<GradientStop Offset="0.501" Color="#00FFFF" />
<GradientStop Offset="0.668" Color="#00FF00" />
<GradientStop Offset="0.835" Color="#FFFF00" />
<GradientStop Offset="1" Color="#FF0000" />
</LinearGradientBrush>
<ControlTemplate x:Key="WD.ColorPickerSliderThumbTemplate" TargetType="Thumb">
<Border
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Background="Transparent"
BorderBrush="White"
BorderThickness="3"
CornerRadius="{Binding ActualWidth, RelativeSource={RelativeSource Self}, Converter={StaticResource WD.HalfValueConverter}}" />
</ControlTemplate>
<Style x:Key="WD.ColorPickerSliderRepeatButtonBaseStyle" TargetType="RepeatButton">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Focusable" Value="false" />
<Setter Property="IsTabStop" Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Rectangle
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="{TemplateBinding Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="WD.ColorPickerSlider" TargetType="{x:Type Slider}">
<Setter Property="Background" Value="{StaticResource WD.ColorPickerRainbowBrush}" />
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false" />
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="Height" Value="15" />
<Setter Property="Margin" Value="4,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
<controls:SmallPanel>
<Border
MaxWidth="{TemplateBinding MaxWidth}"
Margin="{TemplateBinding Margin}"
Background="{TemplateBinding Background}"
CornerRadius="2" />
<Track x:Name="PART_Track" Orientation="{TemplateBinding Orientation}">
<Track.DecreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource WD.ColorPickerSliderRepeatButtonBaseStyle}" />
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource WD.ColorPickerSliderRepeatButtonBaseStyle}" />
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb
x:Name="Thumb"
Width="15"
Height="15"
Focusable="False"
OverridesDefaultStyle="True"
Template="{StaticResource WD.ColorPickerSliderThumbTemplate}">
<Thumb.Effect>
<DropShadowEffect Opacity=".6" ShadowDepth="0" />
</Thumb.Effect>
</Thumb>
</Track.Thumb>
</Track>
</controls:SmallPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="WD.ColorPicker"
BasedOn="{StaticResource WD.ControlBasicStyle}"
TargetType="{x:Type controls:ColorPicker}">
<Setter Property="Width" Value="260" />
<Setter Property="Height" Value="200" />
<Setter Property="Margin" Value="2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource WD.BaseSolidColorBrush}" />
<Setter Property="Background" Value="{DynamicResource WD.BackgroundSolidColorBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:ColorPicker}">
<Border
Margin="{TemplateBinding Margin}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Canvas
x:Name="PART_Canvas"
Margin="1,1,1,0"
ClipToBounds="True">
<Canvas.Background>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing>
<GeometryDrawing.Brush>
<LinearGradientBrush EndPoint="1,0">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset="1" Color="{Binding HueColor, RelativeSource={RelativeSource TemplatedParent}}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,5,5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Brush>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Offset="0" Color="#00000000" />
<GradientStop Offset="1" Color="{StaticResource WD.BlackColor}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,5,5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Canvas.Background>
<Thumb
x:Name="PART_Thumb"
Width="15"
Height="15"
Background="Transparent"
BorderBrush="White"
BorderThickness="3">
<Thumb.Template>
<ControlTemplate TargetType="{x:Type Thumb}">
<controls:SmallPanel>
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{Binding ActualWidth, RelativeSource={RelativeSource Self}, Converter={StaticResource WD.HalfValueConverter}}"
SnapsToDevicePixels="True">
<Border.Effect>
<DropShadowEffect Opacity=".6" ShadowDepth="0" />
</Border.Effect>
</Border>
</controls:SmallPanel>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
<Grid Grid.Row="1" Margin="6,5,6,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Ellipse
Width="25"
Height="25"
Margin="0,0,4,0"
Fill="{TemplateBinding SelectedColor,
Converter={StaticResource WD.ColorToBrushConverter}}">
<Ellipse.Effect>
<DropShadowEffect Opacity=".6" ShadowDepth="0" />
</Ellipse.Effect>
</Ellipse>
<Slider
Name="PART_HueSlider"
Grid.Column="1"
Width="Auto"
IsMoveToPointEnabled="True"
LargeChange="0.01"
Maximum="1"
SmallChange="0.01"
Style="{StaticResource WD.ColorPickerSlider}"
Value="1" />
</Grid>
<Grid
Grid.Row="2"
Margin="4"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="4,0" />
</Style>
<Style
x:Key="WD.TextBoxColorPicker"
BasedOn="{StaticResource WD.DefaultTextBox}"
TargetType="{x:Type TextBox}">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="Width" Value="50" />
</Style>
<Style BasedOn="{StaticResource WD.NumericBox}" TargetType="{x:Type controls:NumericBox}">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="Width" Value="50" />
<Setter Property="UpDownButtonsWidth" Value="0" />
<Setter Property="Maximum" Value="255" />
<Setter Property="Minimum" Value="0" />
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{DynamicResource WD.PrimaryTextSolidColorBrush}" />
<Setter Property="FontSize" Value="10" />
</Style>
</Grid.Resources>
<Button
Name="PART_Button"
Grid.Column="0"
Width="30"
Height="30"
Margin="0,0,4,0"
helpers:ElementHelper.IsRound="True"
Style="{StaticResource WD.NormalButton}">
<controls:PathIcon Kind="UnfoldMore" />
</Button>
<UniformGrid
Grid.Column="1"
Rows="1"
Visibility="{Binding ColorType, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorTypeToVisibilityConverter}, ConverterParameter={x:Static controls:ColorTypeEnum.RGB}}">
<StackPanel>
<controls:NumericBox Value="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToRedConverter}}" />
<TextBlock Text="R" />
</StackPanel>
<StackPanel>
<controls:NumericBox Value="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToGreenConverter}}" />
<TextBlock Text="G" />
</StackPanel>
<StackPanel>
<controls:NumericBox Value="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToBlueConverter}}" />
<TextBlock Text="B" />
</StackPanel>
</UniformGrid>
<UniformGrid
Grid.Column="1"
Rows="1"
Visibility="{Binding ColorType, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorTypeToVisibilityConverter}, ConverterParameter={x:Static controls:ColorTypeEnum.HSL}}">
<StackPanel>
<TextBox
helpers:TextBoxHelper.AllowOnlyNumericInput="True"
helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
helpers:TextBoxHelper.SelectAllOnClick="True"
Style="{StaticResource WD.TextBoxColorPicker}"
Text="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.HToColorConverter}}" />
<TextBlock Text="H" />
</StackPanel>
<StackPanel Grid.Column="1">
<TextBox
helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
helpers:TextBoxHelper.SelectAllOnClick="True"
Style="{StaticResource WD.TextBoxColorPicker}"
Text="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.SToColorConverter}}" />
<TextBlock Text="S" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBox
helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
helpers:TextBoxHelper.SelectAllOnClick="True"
Style="{StaticResource WD.TextBoxColorPicker}"
Text="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.LToColorConverter}}" />
<TextBlock Text="L" />
</StackPanel>
</UniformGrid>
<StackPanel
Grid.Column="1"
Margin="12,0"
Visibility="{Binding ColorType, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorTypeToVisibilityConverter}, ConverterParameter={x:Static controls:ColorTypeEnum.HEX}}">
<TextBox
helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
helpers:TextBoxHelper.SelectAllOnClick="True"
MaxLength="9"
Text="{Binding SelectedColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToStringConverter}}"
TextAlignment="Center" />
<TextBlock Text="HEX" />
</StackPanel>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource WD.ColorPicker}" TargetType="{x:Type controls:ColorPicker}" />
</ResourceDictionary>
3)新增颜色转换工具类 ColorUtil.cs
代码如下:
包含了将 HSL 颜色空间转换为 RGB 颜色空间以及将 RGB 颜色空间转换为 HSL 颜色空间的方法。 ConvertHSLToColor 方法用于将 HSL 颜色转换为 RGB 颜色。HSLToRgb 方法实现了将 HSL 颜色转换为 RGB 颜色的算法,而 SetColor 方法用于在计算过程中设置颜色值。 RgbToHSL 方法用于将 RGB 颜色转换为 HSL 颜色。HsbFromColor 方法则实现了从 RGB 颜色获取 HSB(色相、饱和度、亮度)的算法,并修改了传入的引用类型参数。ColorFromAhsb 和 ColorFromHsb 方法分别用于根据 AHSB(透明度、色相、饱和度、亮度)和 HSB 值创建颜色对象。
using System;
using System.Windows.Media;
using WPFDevelopers.Controls;
namespace WPFDevelopers.Utilities
{
public static class ColorUtil
{
public static Color ConvertHSLToColor(Color color, double h = double.NaN, double sl = double.NaN,
double l = double.NaN)
{
var hsl = RgbToHSL(color);
if (!double.IsNaN(h))
hsl.H = h;
if (!double.IsNaN(sl))
hsl.S = sl;
if (!double.IsNaN(l))
hsl.L = l;
var rgb = HSLToRgb(hsl);
return rgb;
}
private static Color HSLToRgb(HSL hslColor)
{
var rgbColor = new Color();
if (hslColor.S == 0)
{
rgbColor.R = (byte) (hslColor.L * 255);
rgbColor.G = (byte) (hslColor.L * 255);
rgbColor.B = (byte) (hslColor.L * 255);
rgbColor.A = (byte) (hslColor.A * 255);
return rgbColor;
}
double t1;
if (hslColor.L < 0.5)
t1 = hslColor.L * (1.0 + hslColor.S);
else
t1 = hslColor.L + hslColor.S - hslColor.L * hslColor.S;
var t2 = 2.0 * hslColor.L - t1;
var h = hslColor.H / 360;
var tR = h + 1.0 / 3.0;
var r = SetColor(t1, t2, tR);
var tG = h;
var g = SetColor(t1, t2, tG);
var tB = h - 1.0 / 3.0;
var b = SetColor(t1, t2, tB);
rgbColor.R = (byte) (r * 255);
rgbColor.G = (byte) (g * 255);
rgbColor.B = (byte) (b * 255);
rgbColor.A = (byte) (hslColor.A * 255);
return rgbColor;
}
private static double SetColor(double t1, double t2, double t3)
{
if (t3 < 0) t3 += 1.0;
if (t3 > 1) t3 -= 1.0;
double color;
if (6.0 * t3 < 1)
color = t2 + (t1 - t2) * 6.0 * t3;
else if (2.0 * t3 < 1)
color = t1;
else if (3.0 * t3 < 2)
color = t2 + (t1 - t2) * (2.0 / 3.0 - t3) * 6.0;
else
color = t2;
return color;
}
public static HSL RgbToHSL(Color rgbColor)
{
var hslColor = new HSL();
var r = (double) rgbColor.R / 255;
var g = (double) rgbColor.G / 255;
var b = (double) rgbColor.B / 255;
var a = (double) rgbColor.A / 255;
var min = Math.Min(r, Math.Min(g, b));
var max = Math.Max(r, Math.Max(g, b));
var delta = max - min;
if (max == min)
{
hslColor.H = 0;
hslColor.S = 0;
hslColor.L = max;
return hslColor;
}
hslColor.L = (min + max) / 2;
if (hslColor.L < 0.5)
hslColor.S = delta / (max + min);
else
hslColor.S = delta / (2.0 - max - min);
if (r == max) hslColor.H = (g - b) / delta;
if (g == max) hslColor.H = 2.0 + (b - r) / delta;
if (b == max) hslColor.H = 4.0 + (r - g) / delta;
hslColor.H *= 60;
if (hslColor.H < 0) hslColor.H += 360;
hslColor.A = a;
return hslColor;
}
public static void HsbFromColor(Color C, ref double H, ref double S, ref double B)
{
var r = C.R / 255d;
var g = C.G / 255d;
var b = C.B / 255d;
var max = Math.Max(Math.Max(r, g), b);
var min = Math.Min(Math.Min(r, g), b);
var delta = max - min;
var hue = 0d;
var saturation = DoubleUtil.GreaterThan(max, 0) ? delta / max : 0.0;
var brightness = max;
if (!DoubleUtil.IsZero(delta))
{
if (DoubleUtil.AreClose(r, max))
hue = (g - b) / delta;
else if (DoubleUtil.AreClose(g, max))
hue = 2 + (b - r) / delta;
else if (DoubleUtil.AreClose(b, max))
hue = 4 + (r - g) / delta;
hue = hue * 60;
if (DoubleUtil.LessThan(hue, 0d))
hue += 360;
}
H = hue / 360d;
S = saturation;
B = brightness;
}
public static Color ColorFromAhsb(double a, double h, double s, double b)
{
var r = ColorFromHsb(h, s, b);
r.A = (byte) Math.Round(a * 255d);
return r;
}
public static Color ColorFromHsb(double H, double S, double B)
{
double red = 0.0, green = 0.0, blue = 0.0;
if (DoubleUtil.IsZero(S))
{
red = green = blue = B;
}
else
{
var h = DoubleUtil.IsOne(H) ? 0d : H * 6.0;
var i = (int) Math.Floor(h);
var f = h - i;
var r = B * (1.0 - S);
var s = B * (1.0 - S * f);
var t = B * (1.0 - S * (1.0 - f));
switch (i)
{
case 0:
red = B;
green = t;
blue = r;
break;
case 1:
red = s;
green = B;
blue = r;
break;
case 2:
red = r;
green = B;
blue = t;
break;
case 3:
red = r;
green = s;
blue = B;
break;
case 4:
red = t;
green = r;
blue = B;
break;
case 5:
red = B;
green = r;
blue = s;
break;
}
}
return Color.FromRgb((byte) Math.Round(red * 255.0), (byte) Math.Round(green * 255.0),
(byte) Math.Round(blue * 255.0));
}
}
}
4)示例
代码如下:
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
<wd:ColorPicker />
参考资料
原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers