Merge pull request #7555 from TobiasSekan/MoreColorsTakeTwo

[ColorPicker] Add HSB, HSI, HWB and NCol color representation
This commit is contained in:
Clint Rutkas 2020-11-09 15:16:32 -08:00 committed by GitHub
commit 2d61b69ff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 589 additions and 228 deletions

View File

@ -295,6 +295,7 @@ codeofconduct
codereview
COINIT
Colorbrush
colorconv
colorpicker
colorpickerref
COLORREF
@ -903,6 +904,8 @@ hresult
hrgn
HRSRC
HSCROLL
hsb
hsi
hsl
hstring
hsv
@ -913,6 +916,7 @@ html
htt
http
hu
hwb
HWINEVENTHOOK
hwnd
HWNDFIRST
@ -1087,6 +1091,7 @@ IPrincipal
IProgram
IPublic
IQuery
IRead
IReflect
IRegistered
IRegistration
@ -1439,6 +1444,7 @@ NCMBUTTONDOWN
NCMBUTTONUP
NCMOUSELEAVE
NCMOUSEMOVE
NCol
NCPAINT
NCRBUTTONDBLCLK
NCRBUTTONDOWN

View File

@ -10,6 +10,11 @@ data:[a-zA-Z=;,/0-9+-]+
"Lorem[^"]+?\."
TestCase\("[^"]+"
# Test line with hexadecimal colors
\[DataRow\("[0-9A-F]{6}", \d{3}, \d{3}, \d{3}\)\]
\[DataRow\("[0-9A-F]{6}", \d{3}.\d{1}, \d{3}.\d{1}, \d{3}.\d{1}\)\]
\[DataRow\("[0-9A-F]{6}", "[BCGMRY]\d\d?", \d{3}, \d{3}\)\]
# Windows paths
\\native
\\notifications

View File

@ -4,37 +4,10 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public enum ColorRepresentationType
{
/// <summary>
/// Color presentation as hexadecimal color value without the alpha-value (e.g. #0055FF)
/// </summary>
HEX = 0,
/// <summary>
/// Color presentation as RGB color value (red[0..255], green[0..255], blue[0..255])
/// </summary>
RGB = 1,
/// <summary>
/// Color presentation as CMYK color value (cyan[0%..100%], magenta[0%..100%], yellow[0%..100%], black key[0%..100%])
/// </summary>
CMYK = 2,
/// <summary>
/// Color presentation as HSL color value (hue[0°..360°], saturation[0..100%], lightness[0%..100%])
/// </summary>
HSL = 3,
/// <summary>
/// Color presentation as HSV color value (hue[0°..360°], saturation[0%..100%], value[0%..100%])
/// </summary>
HSV = 4,
}
public class ColorPickerProperties
{
public ColorPickerProperties()

View File

@ -40,14 +40,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
public string GetModuleName()
{
return Name;
}
=> Name;
// This can be utilized in the future if the settings.json file is to be modified/deleted.
public bool UpgradeSettingsConfiguration()
{
return false;
}
=> false;
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.PowerToys.Settings.UI.Library.Enumerations
{
// NOTE: don't change the order (numbers) of the enumeration entires
/// <summary>
/// The type of the color representation
/// </summary>
public enum ColorRepresentationType
{
/// <summary>
/// Color presentation as hexadecimal color value without the alpha-value (e.g. #0055FF)
/// </summary>
HEX = 0,
/// <summary>
/// Color presentation as RGB color value (red[0..255], green[0..255], blue[0..255])
/// </summary>
RGB = 1,
/// <summary>
/// Color presentation as CMYK color value (cyan[0%..100%], magenta[0%..100%], yellow[0%..100%], black key[0%..100%])
/// </summary>
CMYK = 2,
/// <summary>
/// Color presentation as HSL color value (hue[0°..360°], saturation[0..100%], lightness[0%..100%])
/// </summary>
HSL = 3,
/// <summary>
/// Color presentation as HSV color value (hue[0°..360°], saturation[0%..100%], value[0%..100%])
/// </summary>
HSV = 4,
/// <summary>
/// Color presentation as HSB color value (hue[0°..360°], saturation[0%..100%], brightness[0%..100%])
/// </summary>
HSB = 5,
/// <summary>
/// Color presentation as HSI color value (hue[0°..360°], saturation[0%..100%], intensity[0%..100%])
/// </summary>
HSI = 6,
/// <summary>
/// Color presentation as HWB color value (hue[0°..360°], whiteness[0%..100%], blackness[0%..100%])
/// </summary>
HWB = 7,
/// <summary>
/// Color presentation as natural color (hue, whiteness[0%..100%], blackness[0%..100%])
/// </summary>
NCol = 8,
}
}

View File

@ -3,8 +3,10 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
@ -16,7 +18,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private readonly ISettingsUtils _settingsUtils;
private ColorPickerSettings _colorPickerSettings;
private readonly ColorPickerSettings _colorPickerSettings;
private bool _isEnabled;
@ -30,6 +32,19 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
throw new ArgumentNullException(nameof(settingsRepository));
}
SelectableColorRepresentations = new Dictionary<ColorRepresentationType, string>
{
{ ColorRepresentationType.CMYK, "CMYK - cmyk(100%, 50%, 75%, 0%)" },
{ ColorRepresentationType.HEX, "HEX - #FFAA00" },
{ ColorRepresentationType.HSB, "HSB - hsb(100, 50%, 75%)" },
{ ColorRepresentationType.HSI, "HSI - hsi(100, 50%, 75%)" },
{ ColorRepresentationType.HSL, "HSL - hsl(100, 50%, 75%)" },
{ ColorRepresentationType.HSV, "HSV - hsv(100, 50%, 75%)" },
{ ColorRepresentationType.HWB, "HWB - hwb(100, 50%, 75%)" },
{ ColorRepresentationType.NCol, "NCol - R10, 50%, 75%" },
{ ColorRepresentationType.RGB, "RGB - rgb(100, 50, 75)" },
};
GeneralSettingsConfig = settingsRepository.SettingsConfig;
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
@ -48,13 +63,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
SendConfigMSG = ipcMSGCallBackFunc;
}
/// <summary>
/// Gets a list with all selectable <see cref="ColorRepresentationType"/>s
/// </summary>
public IReadOnlyDictionary<ColorRepresentationType, string> SelectableColorRepresentations { get; }
public bool IsEnabled
{
get
{
return _isEnabled;
}
get => _isEnabled;
set
{
if (_isEnabled != value)
@ -64,7 +80,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
// Set the status of ColorPicker in the general settings
GeneralSettingsConfig.Enabled.ColorPicker = value;
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
}
@ -73,11 +89,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
public bool ChangeCursor
{
get
{
return _colorPickerSettings.Properties.ChangeCursor;
}
get => _colorPickerSettings.Properties.ChangeCursor;
set
{
if (_colorPickerSettings.Properties.ChangeCursor != value)
@ -91,11 +103,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
public HotkeySettings ActivationShortcut
{
get
{
return _colorPickerSettings.Properties.ActivationShortcut;
}
get => _colorPickerSettings.Properties.ActivationShortcut;
set
{
if (_colorPickerSettings.Properties.ActivationShortcut != value)
@ -107,19 +115,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
}
}
public int CopiedColorRepresentationIndex
public ColorRepresentationType SelectedColorRepresentationValue
{
get
{
return (int)_colorPickerSettings.Properties.CopiedColorRepresentation;
}
get => _colorPickerSettings.Properties.CopiedColorRepresentation;
set
{
if (_colorPickerSettings.Properties.CopiedColorRepresentation != (ColorRepresentationType)value)
if (_colorPickerSettings.Properties.CopiedColorRepresentation != value)
{
_colorPickerSettings.Properties.CopiedColorRepresentation = (ColorRepresentationType)value;
OnPropertyChanged(nameof(CopiedColorRepresentationIndex));
_colorPickerSettings.Properties.CopiedColorRepresentation = value;
OnPropertyChanged(nameof(SelectedColorRepresentationValue));
NotifySettingsChanged();
}
}

View File

@ -13,6 +13,95 @@
AutomationProperties.LandmarkType="Main">
<Grid RowSpacing="{StaticResource DefaultRowSpacing}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel x:Name="ColorPickerView" Orientation="Vertical">
<ToggleSwitch x:Uid="ColorPicker_EnableColorPicker" IsOn="{Binding IsEnabled, Mode=TwoWay}" />
<Custom:HotkeySettingsControl x:Uid="ColorPicker_ActivationShortcut"
MinWidth="240"
Margin="{StaticResource MediumTopMargin}"
HorizontalAlignment="Left"
Enabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"
HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}"
Keys="Win, Ctrl, Alt, Shift" />
<TextBlock x:Uid="ShortcutGuide_Appearance_Behavior"
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"
Style="{StaticResource SettingsGroupTitleStyle}" />
<ComboBox x:Name="ColorPicker_ComboBox"
x:Uid="ColorPicker_CopiedColorRepresentation"
MinWidth="240"
Margin="{StaticResource SmallTopMargin}"
HorizontalAlignment="Left"
DisplayMemberPath="Value"
IsEnabled="{Binding IsEnabled}"
ItemsSource="{Binding SelectableColorRepresentations}"
Loaded="ColorPicker_ComboBox_Loaded"
SelectedValue="{Binding SelectedColorRepresentationValue, Mode=TwoWay}"
SelectedValuePath="Key" />
<!--
Disabling this until we have a safer way to reset cursor as
we can hit a state where the cursor doesn't reset
<CheckBox x:Uid="ColorPicker_ChangeCursor"
IsChecked="{Binding ChangeCursor, Mode=TwoWay}"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{Binding IsEnabled}"/>
-->
</StackPanel>
<RelativePanel x:Name="SidePanel"
Grid.Column="1"
Width="{StaticResource SidePanelWidth}"
HorizontalAlignment="Left">
<StackPanel x:Name="DescriptionPanel">
<TextBlock x:Name="AboutTitle"
x:Uid="About_ColorPicker"
Grid.ColumnSpan="2"
Margin="{StaticResource XSmallBottomMargin}"
Style="{StaticResource SettingsGroupTitleStyle}" />
<TextBlock x:Uid="ColorPicker_Description"
Grid.Row="1"
TextWrapping="Wrap" />
</StackPanel>
<Border x:Name="AboutImage"
Grid.Row="2"
MaxWidth="240"
Margin="{StaticResource SmallTopBottomMargin}"
HorizontalAlignment="Left"
CornerRadius="4"
RelativePanel.Below="DescriptionPanel">
<Image x:Uid="ColorPicker_Image" Source="ms-appx:///Assets/Modules/ColorPicker.png" />
</Border>
<StackPanel x:Name="LinksPanel"
Margin="0,1,0,0"
Orientation="Vertical"
RelativePanel.Below="AboutImage">
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_ColorPicker">
<TextBlock x:Uid="Module_overview" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="https://aka.ms/powerToysGiveFeedback">
<TextBlock x:Uid="Give_Feedback" />
</HyperlinkButton>
<TextBlock x:Uid="AttributionTitle" Style="{StaticResource SettingsGroupTitleStyle}" />
<HyperlinkButton Margin="0,-3,0,0" NavigateUri="https://github.com/martinchrzan/ColorPicker/">
<TextBlock Text="Martin Chrzan's Color Picker" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</RelativePanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutVisualStates">
<VisualState x:Name="WideLayout">
@ -26,109 +115,16 @@
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SidePanel.(Grid.Column)" Value="0"/>
<Setter Target="SidePanel.Width" Value="Auto"/>
<Setter Target="SidePanel.(Grid.Column)" Value="0" />
<Setter Target="SidePanel.Width" Value="Auto" />
<Setter Target="ColorPickerView.(Grid.Row)" Value="1" />
<Setter Target="LinksPanel.(RelativePanel.RightOf)" Value="AboutImage"/>
<Setter Target="LinksPanel.(RelativePanel.AlignTopWith)" Value="AboutImage"/>
<Setter Target="AboutImage.Margin" Value="0,12,12,0"/>
<Setter Target="LinksPanel.(RelativePanel.RightOf)" Value="AboutImage" />
<Setter Target="LinksPanel.(RelativePanel.AlignTopWith)" Value="AboutImage" />
<Setter Target="AboutImage.Margin" Value="0,12,12,0" />
<Setter Target="AboutTitle.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" x:Name="ColorPickerView">
<ToggleSwitch x:Uid="ColorPicker_EnableColorPicker"
IsOn="{Binding IsEnabled, Mode=TwoWay}"/>
<Custom:HotkeySettingsControl x:Uid="ColorPicker_ActivationShortcut"
Margin="{StaticResource MediumTopMargin}"
HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}"
Keys="Win, Ctrl, Alt, Shift"
Enabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"
HorizontalAlignment="Left"
MinWidth="240"
/>
<TextBlock x:Uid="ShortcutGuide_Appearance_Behavior"
Style="{StaticResource SettingsGroupTitleStyle}"
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<ComboBox x:Uid="ColorPicker_CopiedColorRepresentation"
SelectedIndex="{Binding CopiedColorRepresentationIndex, Mode=TwoWay}"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{Binding IsEnabled}"
HorizontalAlignment="Left"
MinWidth="240">
<ComboBoxItem Content="HEX - #FFAA00"/>
<ComboBoxItem Content="RGB - rgb(100, 50, 75)"/>
<ComboBoxItem Content="CMYK - cmyk(100%, 50%, 75%, 0%)"/>
<ComboBoxItem Content="HSL - hsl(100, 50%, 75%)"/>
<ComboBoxItem Content="HSV - hsv(100, 50%, 75%)"/>
</ComboBox>
<!--
Disabling this until we have a safer way to reset cursor as
we can hit a state where the cursor doesn't reset
<CheckBox x:Uid="ColorPicker_ChangeCursor"
IsChecked="{Binding ChangeCursor, Mode=TwoWay}"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{Binding IsEnabled}"/>
-->
</StackPanel>
<RelativePanel x:Name="SidePanel"
HorizontalAlignment="Left"
Width="{StaticResource SidePanelWidth}"
Grid.Column="1">
<StackPanel x:Name="DescriptionPanel">
<TextBlock x:Uid="About_ColorPicker" x:Name="AboutTitle" Grid.ColumnSpan="2"
Style="{StaticResource SettingsGroupTitleStyle}"
Margin="{StaticResource XSmallBottomMargin}"/>
<TextBlock x:Uid="ColorPicker_Description"
TextWrapping="Wrap"
Grid.Row="1" />
</StackPanel>
<Border x:Name="AboutImage"
CornerRadius="4"
Grid.Row="2"
MaxWidth="240"
HorizontalAlignment="Left"
Margin="{StaticResource SmallTopBottomMargin}"
RelativePanel.Below="DescriptionPanel">
<Image x:Uid="ColorPicker_Image" Source="ms-appx:///Assets/Modules/ColorPicker.png" />
</Border>
<StackPanel x:Name="LinksPanel"
Margin="0,1,0,0"
RelativePanel.Below="AboutImage"
Orientation="Vertical" >
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_ColorPicker">
<TextBlock x:Uid="Module_overview" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="https://aka.ms/powerToysGiveFeedback">
<TextBlock x:Uid="Give_Feedback" />
</HyperlinkButton>
<TextBlock
x:Uid="AttributionTitle"
Style="{StaticResource SettingsGroupTitleStyle}" />
<HyperlinkButton Margin="0,-3,0,0"
NavigateUri="https://github.com/martinchrzan/ColorPicker/">
<TextBlock Text="Martin Chrzan's Color Picker" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</RelativePanel>
</Grid>
</Page>

View File

@ -21,5 +21,33 @@ namespace Microsoft.PowerToys.Settings.UI.Views
DataContext = ViewModel;
InitializeComponent();
}
/// <summary>
/// Event is called when the <see cref="ComboBox"/> is completely loaded, inclusive the ItemSource
/// </summary>
/// <param name="sender">The sender of this event</param>
/// <param name="e">The arguments of this event</param>
private void ColorPicker_ComboBox_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
/**
* UWP hack
* because UWP load the bound ItemSource of the ComboBox asynchronous,
* so after InitializeComponent() the ItemSource is still empty and can't automatically select a entry.
* Selection via SelectedItem and SelectedValue is still not working too
*/
var index = 0;
foreach (var item in ViewModel.SelectableColorRepresentations)
{
if (item.Key == ViewModel.SelectedColorRepresentationValue)
{
break;
}
index++;
}
ColorPicker_ComboBox.SelectedIndex = index;
}
}
}

View File

@ -13,44 +13,7 @@ namespace ColorPicker.Helpers
internal static class ColorHelper
{
/// <summary>
/// Convert a given <see cref="Color"/> color to a HSL color (hue, saturation, lightness)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue [0°..360°], saturation [0..1] and lightness [0..1] values of the converted color</returns>
internal static (double hue, double saturation, double lightness) ConvertToHSLColor(Color color)
{
var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d;
var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d;
var lightness = (max + min) / 2d;
if (lightness == 0d || min == max)
{
return (color.GetHue(), 0d, lightness);
}
else if (lightness > 0d && lightness <= 0.5d)
{
return (color.GetHue(), (max - min) / (max + min), lightness);
}
return (color.GetHue(), (max - min) / (2d - (max + min)), lightness);
}
/// <summary>
/// Convert a given <see cref="Color"/> color to a HSV color (hue, saturation, value)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue [0°..360°], saturation [0..1] and value [0..1] of the converted color</returns>
internal static (double hue, double saturation, double value) ConvertToHSVColor(Color color)
{
var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d;
var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d;
return (color.GetHue(), max == 0d ? 0d : (max - min) / max, max);
}
/// <summary>
/// Convert a given <see cref="Color"/> color to a CYMK color (cyan, magenta, yellow, black key)
/// Convert a given <see cref="Color"/> to a CYMK color (cyan, magenta, yellow, black key)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The cyan[0..1], magenta[0..1], yellow[0..1] and black key[0..1] of the converted color</returns>
@ -80,5 +43,135 @@ namespace ColorPicker.Helpers
return (cyan, magenta, yellow, blackKey);
}
/// <summary>
/// Convert a given <see cref="Color"/> to a HSB color (hue, saturation, brightness)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue [0°..360°], saturation [0..1] and brightness [0..1] of the converted color</returns>
internal static (double hue, double saturation, double brightness) ConvertToHSBColor(Color color)
=> (color.GetHue(), color.GetSaturation(), color.GetBrightness());
/// <summary>
/// Convert a given <see cref="Color"/> to a HSI color (hue, saturation, intensity)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue [0°..360°], saturation [0..1] and intensity [0..1] of the converted color</returns>
internal static (double hue, double saturation, double intensity) ConvertToHSIColor(Color color)
{
// special case for black
if (color.R == 0 && color.G == 0 && color.B == 0)
{
return (0d, 0d, 0d);
}
var red = color.R / 255d;
var green = color.G / 255d;
var blue = color.B / 255d;
var intensity = (red + green + blue) / 3d;
var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d;
return (color.GetHue(), 1d - (min / intensity), intensity);
}
/// <summary>
/// Convert a given <see cref="Color"/> to a HSL color (hue, saturation, lightness)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue [0°..360°], saturation [0..1] and lightness [0..1] values of the converted color</returns>
internal static (double hue, double saturation, double lightness) ConvertToHSLColor(Color color)
{
var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d;
var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d;
var lightness = (max + min) / 2d;
if (lightness == 0d || min == max)
{
return (color.GetHue(), 0d, lightness);
}
else if (lightness > 0d && lightness <= 0.5d)
{
return (color.GetHue(), (max - min) / (max + min), lightness);
}
return (color.GetHue(), (max - min) / (2d - (max + min)), lightness);
}
/// <summary>
/// Convert a given <see cref="Color"/> to a HSV color (hue, saturation, value)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue [0°..360°], saturation [0..1] and value [0..1] of the converted color</returns>
internal static (double hue, double saturation, double value) ConvertToHSVColor(Color color)
{
var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d;
var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d;
return (color.GetHue(), max == 0d ? 0d : (max - min) / max, max);
}
/// <summary>
/// Convert a given <see cref="Color"/> to a HWB color (hue, whiteness, blackness)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue [0°..360°], whiteness [0..1] and blackness [0..1] of the converted color</returns>
internal static (double hue, double whiteness, double blackness) ConvertToHWBColor(Color color)
{
var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d;
var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d;
return (color.GetHue(), min, 1 - max);
}
/// <summary>
/// Convert a given <see cref="Color"/> to a natural color (hue, whiteness, blackness)
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert</param>
/// <returns>The hue, whiteness [0..1] and blackness [0..1] of the converted color</returns>
internal static (string hue, double whiteness, double blackness) ConvertToNaturalColor(Color color)
{
var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d;
var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d;
return (GetNaturalColorFromHue(color.GetHue()), min, 1 - max);
}
/// <summary>
/// Return the natural color for the given hue value
/// </summary>
/// <param name="hue">The hue value to convert</param>
/// <returns>A natural color</returns>
private static string GetNaturalColorFromHue(double hue)
{
if (hue < 60d)
{
return $"R{Math.Round(hue / 0.6d, 0)}";
}
if (hue < 120d)
{
return $"Y{Math.Round((hue - 60d) / 0.6d, 0)}";
}
if (hue < 180d)
{
return $"G{Math.Round((hue - 120d) / 0.6d, 0)}";
}
if (hue < 240d)
{
return $"C{Math.Round((hue - 180d) / 0.6d, 0)}";
}
if (hue < 300d)
{
return $"B{Math.Round((hue - 240d) / 0.6d, 0)}";
}
return $"M{Math.Round((hue - 300d) / 0.6d, 0)}";
}
}
}

View File

@ -5,7 +5,7 @@
using System;
using System.Drawing;
using System.Globalization;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
namespace ColorPicker.Helpers
{
@ -25,14 +25,38 @@ namespace ColorPicker.Helpers
{
ColorRepresentationType.CMYK => ColorToCYMK(color),
ColorRepresentationType.HEX => ColorToHex(color),
ColorRepresentationType.HSB => ColorToHSB(color),
ColorRepresentationType.HSI => ColorToHSI(color),
ColorRepresentationType.HSL => ColorToHSL(color),
ColorRepresentationType.HSV => ColorToHSV(color),
ColorRepresentationType.HWB => ColorToHWB(color),
ColorRepresentationType.NCol => ColorToNCol(color),
ColorRepresentationType.RGB => ColorToRGB(color),
// Fall-back value, when "_userSettings.CopiedColorRepresentation.Value" is incorrect
_ => ColorToHex(color),
};
/// <summary>
/// Return a <see cref="string"/> representation of a CYMK color
/// </summary>
/// <param name="color">The <see cref="Color"/> for the CYMK color presentation</param>
/// <returns>A <see cref="string"/> representation of a CYMK color</returns>
private static string ColorToCYMK(Color color)
{
var (cyan, magenta, yellow, blackKey) = ColorHelper.ConvertToCMYKColor(color);
cyan = Math.Round(cyan * 100);
magenta = Math.Round(magenta * 100);
yellow = Math.Round(yellow * 100);
blackKey = Math.Round(blackKey * 100);
return $"cmyk({cyan.ToString(CultureInfo.InvariantCulture)}%"
+ $", {magenta.ToString(CultureInfo.InvariantCulture)}%"
+ $", {yellow.ToString(CultureInfo.InvariantCulture)}%"
+ $", {blackKey.ToString(CultureInfo.InvariantCulture)}%)";
}
/// <summary>
/// Return a hexadecimal <see cref="string"/> representation of a RGB color
/// </summary>
@ -44,19 +68,45 @@ namespace ColorPicker.Helpers
+ $"{color.B.ToString("X2", CultureInfo.InvariantCulture)}";
/// <summary>
/// Return a <see cref="string"/> representation of a RGB color
/// Return a <see cref="string"/> representation of a HSB color
/// </summary>
/// <param name="color">The see cref="Color"/> for the RGB color presentation</param>
/// <returns>A <see cref="string"/> representation of a RGB color</returns>
private static string ColorToRGB(Color color)
=> $"rgb({color.R.ToString(CultureInfo.InvariantCulture)}"
+ $", {color.G.ToString(CultureInfo.InvariantCulture)}"
+ $", {color.B.ToString(CultureInfo.InvariantCulture)})";
/// <param name="color">The <see cref="Color"/> for the HSB color presentation</param>
/// <returns>A <see cref="string"/> representation of a HSB color</returns>
private static string ColorToHSB(Color color)
{
var (hue, saturation, brightness) = ColorHelper.ConvertToHSBColor(color);
hue = Math.Round(hue);
saturation = Math.Round(saturation * 100);
brightness = Math.Round(brightness * 100);
return $"hsb({hue.ToString(CultureInfo.InvariantCulture)}"
+ $", {saturation.ToString(CultureInfo.InvariantCulture)}%"
+ $", {brightness.ToString(CultureInfo.InvariantCulture)}%)";
}
/// <summary>
/// Return a <see cref="string"/> representation of a HSI color
/// </summary>
/// <param name="color">The <see cref="Color"/> for the HSI color presentation</param>
/// <returns>A <see cref="string"/> representation of a HSI color</returns>
private static string ColorToHSI(Color color)
{
var (hue, saturation, intensity) = ColorHelper.ConvertToHSIColor(color);
hue = Math.Round(hue);
saturation = Math.Round(saturation * 100);
intensity = Math.Round(intensity * 100);
return $"hsi({hue.ToString(CultureInfo.InvariantCulture)}"
+ $", {saturation.ToString(CultureInfo.InvariantCulture)}%"
+ $", {intensity.ToString(CultureInfo.InvariantCulture)}%)";
}
/// <summary>
/// Return a <see cref="string"/> representation of a HSL color
/// </summary>
/// <param name="color">The see cref="Color"/> for the HSL color presentation</param>
/// <param name="color">The <see cref="Color"/> for the HSL color presentation</param>
/// <returns>A <see cref="string"/> representation of a HSL color</returns>
private static string ColorToHSL(Color color)
{
@ -75,7 +125,7 @@ namespace ColorPicker.Helpers
/// <summary>
/// Return a <see cref="string"/> representation of a HSV color
/// </summary>
/// <param name="color">The see cref="Color"/> for the HSV color presentation</param>
/// <param name="color">The <see cref="Color"/> for the HSV color presentation</param>
/// <returns>A <see cref="string"/> representation of a HSV color</returns>
private static string ColorToHSV(Color color)
{
@ -92,24 +142,48 @@ namespace ColorPicker.Helpers
}
/// <summary>
/// Return a <see cref="string"/> representation of a HSV color
/// Return a <see cref="string"/> representation of a HWB color
/// </summary>
/// <param name="color">The see cref="Color"/> for the HSV color presentation</param>
/// <returns>A <see cref="string"/> representation of a HSV color</returns>
private static string ColorToCYMK(Color color)
/// <param name="color">The <see cref="Color"/> for the HWB color presentation</param>
/// <returns>A <see cref="string"/> representation of a HWB color</returns>
private static string ColorToHWB(Color color)
{
var (cyan, magenta, yellow, blackKey) = ColorHelper.ConvertToCMYKColor(color);
var (hue, whiteness, blackness) = ColorHelper.ConvertToHWBColor(color);
cyan = Math.Round(cyan * 100);
magenta = Math.Round(magenta * 100);
yellow = Math.Round(yellow * 100);
blackKey = Math.Round(blackKey * 100);
hue = Math.Round(hue);
whiteness = Math.Round(whiteness * 100);
blackness = Math.Round(blackness * 100);
// Using InvariantCulture since this is used for color representation
return $"cmyk({cyan.ToString(CultureInfo.InvariantCulture)}%"
+ $", {magenta.ToString(CultureInfo.InvariantCulture)}%"
+ $", {yellow.ToString(CultureInfo.InvariantCulture)}%"
+ $", {blackKey.ToString(CultureInfo.InvariantCulture)}%)";
return $"hwb({hue.ToString(CultureInfo.InvariantCulture)}"
+ $", {whiteness.ToString(CultureInfo.InvariantCulture)}%"
+ $", {blackness.ToString(CultureInfo.InvariantCulture)}%)";
}
/// <summary>
/// Return a <see cref="string"/> representation of a natural color
/// </summary>
/// <param name="color">The <see cref="Color"/> for the natural color presentation</param>
/// <returns>A <see cref="string"/> representation of a natural color</returns>
private static string ColorToNCol(Color color)
{
var (hue, whiteness, blackness) = ColorHelper.ConvertToNaturalColor(color);
whiteness = Math.Round(whiteness * 100);
blackness = Math.Round(blackness * 100);
return $"{hue}"
+ $", {whiteness.ToString(CultureInfo.InvariantCulture)}%"
+ $", {blackness.ToString(CultureInfo.InvariantCulture)}%";
}
/// <summary>
/// Return a <see cref="string"/> representation of a RGB color
/// </summary>
/// <param name="color">The see cref="Color"/> for the RGB color presentation</param>
/// <returns>A <see cref="string"/> representation of a RGB color</returns>
private static string ColorToRGB(Color color)
=> $"rgb({color.R.ToString(CultureInfo.InvariantCulture)}"
+ $", {color.G.ToString(CultureInfo.InvariantCulture)}"
+ $", {color.B.ToString(CultureInfo.InvariantCulture)})";
}
}

View File

@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
namespace ColorPicker.Settings
{

View File

@ -8,6 +8,7 @@ using System.IO;
using System.IO.Abstractions;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace ColorPicker.Settings

View File

@ -15,7 +15,6 @@ using ColorPicker.Mouse;
using ColorPicker.Settings;
using ColorPicker.Telemetry;
using ColorPicker.ViewModelContracts;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
namespace ColorPicker.ViewModels

View File

@ -1,5 +1,6 @@
using System;
using System.Drawing;
using System.Globalization;
using ColorPicker.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -41,7 +42,7 @@ namespace UnitTest_ColorPickerUI.Helpers
[DataRow(315, 100, 050, 100, 000, 075)] // Red-magenta
[DataRow(330, 100, 050, 100, 000, 050)] // Blue-red
[DataRow(345, 100, 050, 100, 000, 025)] // Light blue-red
public void ColorRGBtoHSL(double hue, double saturation, double lightness, int red, int green, int blue)
public void ColorRGBtoHSLTest(double hue, double saturation, double lightness, int red, int green, int blue)
{
red = Convert.ToInt32(Math.Round(255d / 100d * red)); // [0%..100%] to [0..255]
green = Convert.ToInt32(Math.Round(255d / 100d * green)); // [0%..100%] to [0..255]
@ -90,7 +91,7 @@ namespace UnitTest_ColorPickerUI.Helpers
[DataRow(315, 100, 100, 100, 000, 075)] // Red-magenta
[DataRow(330, 100, 100, 100, 000, 050)] // Blue-red
[DataRow(345, 100, 100, 100, 000, 025)] // Light blue-red
public void ColorRGBtoHSV(double hue, double saturation, double value, int red, int green, int blue)
public void ColorRGBtoHSVTest(double hue, double saturation, double value, int red, int green, int blue)
{
red = Convert.ToInt32(Math.Round(255d / 100d * red)); // [0%..100%] to [0..255]
green = Convert.ToInt32(Math.Round(255d / 100d * green)); // [0%..100%] to [0..255]
@ -138,7 +139,7 @@ namespace UnitTest_ColorPickerUI.Helpers
[DataRow(000, 100, 025, 000, 255, 000, 192)] // Red-magenta
[DataRow(000, 100, 050, 000, 255, 000, 128)] // Blue-red
[DataRow(000, 100, 075, 000, 255, 000, 064)] // Light blue-red
public void ColorRGBtoCMYK(int cyan, int magenta, int yellow, int blackKey, int red, int green, int blue)
public void ColorRGBtoCMYKTest(int cyan, int magenta, int yellow, int blackKey, int red, int green, int blue)
{
var color = Color.FromArgb(255, red, green, blue);
var result = ColorHelper.ConvertToCMYKColor(color);
@ -156,8 +157,130 @@ namespace UnitTest_ColorPickerUI.Helpers
Assert.AreEqual(result.blackKey * 100d, blackKey, 0.5d);
}
// values taken from https://en.wikipedia.org/wiki/HSL_and_HSV#Examples
[TestMethod]
public void ColorRGBtoCMYKZeroDiv()
[DataRow("FFFFFF", 000.0, 000.0, 100.0)] // white
[DataRow("808080", 000.0, 000.0, 050.0)] // gray
[DataRow("000000", 000.0, 000.0, 000.0)] // black
[DataRow("FF0000", 000.0, 100.0, 033.3)] // red
[DataRow("BFBF00", 060.0, 100.0, 050.0)] // yellow
[DataRow("008000", 120.0, 100.0, 016.7)] // green
[DataRow("80FFFF", 180.0, 040.0, 083.3)] // cyan
[DataRow("8080FF", 240.0, 025.0, 066.7)] // blue
[DataRow("BF40BF", 300.0, 057.1, 058.3)] // magenta
[DataRow("A0A424", 061.8, 069.9, 047.1)]
[DataRow("411BEA", 251.1, 075.6, 042.6)]
[DataRow("1EAC41", 134.9, 066.7, 034.9)]
[DataRow("F0C80E", 049.5, 091.1, 059.3)]
[DataRow("B430E5", 283.7, 068.6, 059.6)]
[DataRow("ED7651", 014.3, 044.6, 057.0)]
[DataRow("FEF888", 056.9, 036.3, 083.5)]
[DataRow("19CB97", 162.4, 080.0, 049.5)]
[DataRow("362698", 248.3, 053.3, 031.9)]
[DataRow("7E7EB8", 240.5, 013.5, 057.0)]
public void ColorRGBtoHSITest(string hexValue, double hue, double saturation, double intensity)
{
var red = int.Parse(hexValue.Substring(0, 2), NumberStyles.HexNumber);
var green = int.Parse(hexValue.Substring(2, 2), NumberStyles.HexNumber);
var blue = int.Parse(hexValue.Substring(4, 2), NumberStyles.HexNumber);
var color = Color.FromArgb(255, red, green, blue);
var result = ColorHelper.ConvertToHSIColor(color);
// hue[0°..360°]
Assert.AreEqual(result.hue, hue, 0.5d);
// saturation[0..1]
Assert.AreEqual(result.saturation * 100d, saturation, 0.5d);
// intensity[0..1]
Assert.AreEqual(result.intensity * 100d, intensity, 0.5d);
}
// values taken from https://en.wikipedia.org/wiki/HSL_and_HSV#Examples
// and manual convert via https://colorconv.com/hwb
[TestMethod]
[DataRow("FFFFFF", 000, 100, 000)] // white
[DataRow("808080", 000, 050, 050)] // gray
[DataRow("000000", 000, 000, 100)] // black
[DataRow("FF0000", 000, 000, 000)] // red
[DataRow("BFBF00", 060, 000, 025)] // yellow
[DataRow("008000", 120, 000, 050)] // green
[DataRow("80FFFF", 180, 050, 000)] // cyan
[DataRow("8080FF", 240, 050, 000)] // blue
[DataRow("BF40BF", 300, 025, 025)] // magenta
[DataRow("A0A424", 062, 014, 036)]
[DataRow("411BEA", 251, 011, 008)]
[DataRow("1EAC41", 135, 012, 033)]
[DataRow("F0C80E", 049, 005, 006)]
[DataRow("B430E5", 284, 019, 010)]
[DataRow("ED7651", 014, 032, 007)]
[DataRow("FEF888", 057, 053, 000)]
[DataRow("19CB97", 162, 010, 020)]
[DataRow("362698", 248, 015, 040)]
[DataRow("7E7EB8", 240, 049, 028)]
public void ColorRGBtoHWBTest(string hexValue, double hue, double whiteness, double blackness)
{
var red = int.Parse(hexValue.Substring(0, 2), NumberStyles.HexNumber);
var green = int.Parse(hexValue.Substring(2, 2), NumberStyles.HexNumber);
var blue = int.Parse(hexValue.Substring(4, 2), NumberStyles.HexNumber);
var color = Color.FromArgb(255, red, green, blue);
var result = ColorHelper.ConvertToHWBColor(color);
// hue[0°..360°]
Assert.AreEqual(result.hue, hue, 0.5d);
// whiteness[0..1]
Assert.AreEqual(result.whiteness * 100d, whiteness, 0.5d);
// blackness[0..1]
Assert.AreEqual(result.blackness * 100d, blackness, 0.5d);
}
// values taken from https://en.wikipedia.org/wiki/HSL_and_HSV#Examples
// and manual convert via https://colorconv.com/hwb
[TestMethod]
[DataRow("FFFFFF", "R0", 100, 000)] // white
[DataRow("808080", "R0", 050, 050)] // gray
[DataRow("000000", "R0", 000, 100)] // black
[DataRow("FF0000", "R0", 000, 000)] // red
[DataRow("BFBF00", "Y0", 000, 025)] // yellow
[DataRow("008000", "G0", 000, 050)] // green
[DataRow("80FFFF", "C0", 050, 000)] // cyan
[DataRow("8080FF", "B0", 050, 000)] // blue
[DataRow("BF40BF", "M0", 025, 025)] // magenta
[DataRow("A0A424", "Y3", 014, 036)]
[DataRow("411BEA", "B18", 011, 008)]
[DataRow("1EAC41", "G25", 012, 033)]
[DataRow("F0C80E", "R82", 005, 006)]
[DataRow("B430E5", "B73", 019, 010)]
[DataRow("ED7651", "R24", 032, 007)]
[DataRow("FEF888", "R95", 053, 000)]
[DataRow("19CB97", "G71", 010, 020)]
[DataRow("362698", "B14", 015, 040)]
[DataRow("7E7EB8", "B0", 049, 028)]
public void ColorRGBtoNColTest(string hexValue, string hue, double whiteness, double blackness)
{
var red = int.Parse(hexValue.Substring(0, 2), NumberStyles.HexNumber);
var green = int.Parse(hexValue.Substring(2, 2), NumberStyles.HexNumber);
var blue = int.Parse(hexValue.Substring(4, 2), NumberStyles.HexNumber);
var color = Color.FromArgb(255, red, green, blue);
var result = ColorHelper.ConvertToNaturalColor(color);
// hue
Assert.AreEqual(result.hue, hue);
// whiteness[0..1]
Assert.AreEqual(result.whiteness * 100d, whiteness, 0.5d);
// blackness[0..1]
Assert.AreEqual(result.blackness * 100d, blackness, 0.5d);
}
[TestMethod]
public void ColorRGBtoCMYKZeroDivTest()
{
for(var red = 0; red < 256; red++)
{

View File

@ -1,5 +1,5 @@
using ColorPicker.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Drawing;
@ -11,11 +11,15 @@ namespace UnitTest_ColorPickerUI.Helpers
[TestMethod]
[DataRow(ColorRepresentationType.CMYK, "cmyk(0%, 0%, 0%, 100%)")]
[DataRow(ColorRepresentationType.HEX, "#000000")]
[DataRow(ColorRepresentationType.NCol, "R0, 0%, 100%")]
[DataRow(ColorRepresentationType.HSB, "hsb(0, 0%, 0%)")]
[DataRow(ColorRepresentationType.HSI, "hsi(0, 0%, 0%)")]
[DataRow(ColorRepresentationType.HSL, "hsl(0, 0%, 0%)")]
[DataRow(ColorRepresentationType.HSV, "hsv(0, 0%, 0%)")]
[DataRow(ColorRepresentationType.HWB, "hwb(0, 0%, 100%)")]
[DataRow(ColorRepresentationType.RGB, "rgb(0, 0, 0)")]
public void ColorRGBtoCMYKZeroDiv(ColorRepresentationType type, string expected)
public void GetStringRepresentationTest(ColorRepresentationType type, string expected)
{
var result = ColorRepresentationHelper.GetStringRepresentation(Color.Black, type);
Assert.AreEqual(result, expected);