From a900ea2ed9be51f8a9a444f0a90579f617e3e8dc Mon Sep 17 00:00:00 2001 From: Lexa Date: Fri, 4 Nov 2022 19:19:54 +0200 Subject: [PATCH] Initial commit --- .gitignore | 5 + ClrHlp.cs | 107 +++++++++++++++ HSLColorSelector.xaml | 19 +++ HSLColorSelector.xaml.cs | 153 +++++++++++++++++++++ Properties/AssemblyInfo.cs | 55 ++++++++ Properties/Resources.Designer.cs | 62 +++++++++ Properties/Resources.resx | 117 ++++++++++++++++ Properties/Settings.Designer.cs | 30 +++++ Properties/Settings.settings | 7 + README.md | 57 +++++++- SelectColorDlg.xaml | 125 +++++++++++++++++ SelectColorDlg.xaml.cs | 224 +++++++++++++++++++++++++++++++ WPFColorLib.csproj | 103 ++++++++++++++ WPFColorLib.sln | 25 ++++ 14 files changed, 1087 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 ClrHlp.cs create mode 100644 HSLColorSelector.xaml create mode 100644 HSLColorSelector.xaml.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 Properties/Resources.Designer.cs create mode 100644 Properties/Resources.resx create mode 100644 Properties/Settings.Designer.cs create mode 100644 Properties/Settings.settings create mode 100644 SelectColorDlg.xaml create mode 100644 SelectColorDlg.xaml.cs create mode 100644 WPFColorLib.csproj create mode 100644 WPFColorLib.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d666448 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +.git/ +bin_Debug/ +bin_Release/ +*.user \ No newline at end of file diff --git a/ClrHlp.cs b/ClrHlp.cs new file mode 100644 index 0000000..094c2c2 --- /dev/null +++ b/ClrHlp.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace WPFColorLib +{ + public class ClrHlp + { + public static (int, int, int) ColorToHSL(Color rgb) + { + var r = (rgb.R / 255.0); + var g = (rgb.G / 255.0); + var b = (rgb.B / 255.0); + + var min = Math.Min(Math.Min(r, g), b); + var max = Math.Max(Math.Max(r, g), b); + var delta = max - min; + + var lum = (max + min) / 2; + double hue, sat; + + if (delta == 0) + { + hue = 0.0; + sat = 0.0; + } + else + { + sat = (lum <= 0.5) ? (delta / (max + min)) : (delta / (2 - max - min)); + + if (r == max) + { + hue = ((g - b) / 6.0) / delta; + } + else if (g == max) + { + hue = (1.0 / 3.0) + ((b - r) / 6.0) / delta; + } + else + { + hue = (2.0 / 3.0) + ((r - g) / 6.0) / delta; + } + + if (hue < 0) + hue += 1; + if (hue > 1) + hue -= 1; + } + + return ((int)(hue*360), (int)(sat*100), (int)(lum*100)); + } + + public static Regex reHexRGB = new Regex(@"([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static Color Hex2color(string s) + { + var m = reHexRGB.Match(s); + if (m.Success) + return Color.FromRgb(Hex2Byte(m.Groups[1].Value), Hex2Byte(m.Groups[2].Value), Hex2Byte(m.Groups[3].Value)); + throw new ArgumentException($"Invalid hex RGB value '{s}'"); + } + + public static string Color2hex(Color clr) + { + return clr.R.ToString("X02") + clr.G.ToString("X02") + clr.B.ToString("X02"); + } + + public static byte Hex2Byte(string hex) + { + return byte.Parse(hex, NumberStyles.AllowHexSpecifier); + } + + public static int HSL2RGBInt(int h, int s, int l) + { + var rgb = HSL2RGB(h, s, l); + return rgb[0] << 16 | rgb[1] << 8 | rgb[2]; + } + + public static int[] HSL2RGB(int h, int s, int l) + { + var rgb = new int[3]; + + var baseColor = (h + 60) % 360 / 120; + var shift = (h + 60) % 360 - (120 * baseColor + 60); + var secondaryColor = (baseColor + (shift >= 0 ? 1 : -1) + 3) % 3; + + //Setting Hue + rgb[baseColor] = 255; + rgb[secondaryColor] = (int)((Math.Abs(shift) / 60.0f) * 255.0f); + + //Setting Saturation + for (var i = 0; i < 3; i++) + rgb[i] += (int)((255 - rgb[i]) * ((100 - s) / 100.0f)); + + //Setting Value + for (var i = 0; i < 3; i++) + rgb[i] -= (int)(rgb[i] * (100 - l) / 100.0f); + + return rgb; + } + } +} diff --git a/HSLColorSelector.xaml b/HSLColorSelector.xaml new file mode 100644 index 0000000..973708d --- /dev/null +++ b/HSLColorSelector.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/HSLColorSelector.xaml.cs b/HSLColorSelector.xaml.cs new file mode 100644 index 0000000..45d14cb --- /dev/null +++ b/HSLColorSelector.xaml.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace WPFColorLib +{ + public partial class HSLColorSelector : UserControl + { + public HSLColorSelector() + { + InitializeComponent(); + + slidHue.ValueChanged += SlidHue_ValueChanged; + } + + public event EventHandler> ColorRGBChanged; + public event EventHandler> ColorHSLChanged;// 0..360, 0..100, 0..100 + + void Control_Resized(object sender, SizeChangedEventArgs e) + { + #region recreate hue bar + var imgW = (int)(brdHue.ActualWidth - 2); + var imgH = (int)(brdHue.ActualHeight - 2); + imgHue.Source = new WriteableBitmap(imgW, imgH, 96, 96, PixelFormats.Bgr32, null); + DrawHueBar(); + #endregion + + #region recreate Saturation/Lightness area + imgW = (int)(brdSaturLight.ActualWidth - 2); + imgH = (int)(brdSaturLight.ActualHeight - 2); + imgSaturLight.Source = new WriteableBitmap(imgW, imgH, 96, 96, PixelFormats.Bgr32, null); + RedrawSaturLight(slidHue.Value); + #endregion + } + + void DrawHueBar() + { + var recW = (int)(brdHue.ActualWidth - 2); + var recH = (int)(brdHue.ActualHeight - 2); + var k = 360.0 / recW; + var bmp = (WriteableBitmap)imgHue.Source; + bmp.Lock(); + unsafe { + var buf = bmp.BackBuffer; + // fill the first line of hue bar + for(int x=0; x < recW; x++){ + int hue = (int)Math.Floor(x * k); + *((int*)(buf + x * 4)) = ClrHlp.HSL2RGBInt(hue, 100, 100); + } + var origColorLine = new Span(buf.ToPointer(), bmp.BackBufferStride); + for (int y = 1; y < recH; y++) { + var destColorLine = new Span((buf + y * bmp.BackBufferStride).ToPointer(), bmp.BackBufferStride); + origColorLine.CopyTo(destColorLine); + } + } + bmp.AddDirtyRect(new Int32Rect(0, 0, recW, recH)); + bmp.Unlock(); + } + + void SlidHue_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + RedrawSaturLight(e.NewValue); + + // calc contrast color for ellipse(target above color) + var contrastHue = ((int)e.NewValue + 180) % 360;// opposite side of color ring + var contrastColor = ClrHlp.HSL2RGB(contrastHue, 100, 100); + ellClrTarget.Stroke = new SolidColorBrush(Color.FromRgb((byte)contrastColor[0], (byte)contrastColor[1], (byte)contrastColor[2])); + } + + void RedrawSaturLight(double newHue) + { + var hue = (int)newHue; + var recW = (int)(brdSaturLight.ActualWidth - 2); + var recH = (int)(brdSaturLight.ActualHeight - 2); + var kX = 100.0 / recW; + var kY = 100.0 / recH; + var bmp = (WriteableBitmap)imgSaturLight.Source; + bmp.Lock(); + unsafe { + var buf = bmp.BackBuffer; + for (int y = 0; y < recH; y++) { + var lineStart = buf + y * bmp.BackBufferStride; + for (int x = 0; x < recW; x++) { + *((int*)(lineStart + x * 4)) = ClrHlp.HSL2RGBInt(hue, (int)(kX * x), (int)(kY * y)); + } + } + } + bmp.AddDirtyRect(new Int32Rect(0, 0, recW, recH)); + bmp.Unlock(); + } + + bool trackColorMode = false; + + void imgSaturLight_MouDown(object sender, MouseButtonEventArgs e) + { + trackColorMode = true; + imgSaturLight.CaptureMouse(); + + SelectNewColor(e.GetPosition(imgSaturLight)); + } + + void imgSaturLight_MouMove(object sender, MouseEventArgs e) + { + if (!trackColorMode) return; + + SelectNewColor(e.GetPosition(imgSaturLight)); + } + + void SelectNewColor(Point mou) + { + var recW = (int)(brdSaturLight.ActualWidth - 2); + var recH = (int)(brdSaturLight.ActualHeight - 2); + var kX = 100.0 / recW; + var kY = 100.0 / recH; + + if (mou.X < 0) + mou.X = 0; + else if (mou.X >= recW) + mou.X = recW - 1; + if (mou.Y < 0) + mou.Y = 0; + else if (mou.Y >= recH) + mou.Y = recH - 1; + + Canvas.SetLeft(ellClrTarget, mou.X-2); + Canvas.SetTop(ellClrTarget, mou.Y-2); + + var hue = (int)slidHue.Value; + var sat = (int)(mou.X * kX); + var lum = (int)(mou.Y * kY); + ColorHSLChanged?.Invoke(this, new Tuple(hue, sat, lum)); + + if (ColorRGBChanged != null) { + var clr = ClrHlp.HSL2RGB(hue, sat, lum); + ColorRGBChanged.Invoke(this, new Tuple((byte)clr[0], (byte)clr[1], (byte)clr[2])); + } + } + + void imgSaturLight_MouUp(object sender, MouseButtonEventArgs e) + { + trackColorMode = false; + imgSaturLight.ReleaseMouseCapture(); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f735530 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WPFColorLib")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WPFColorLib")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a09b77b --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WPFColorLib.Properties { + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WPFColorLib.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..cf60c09 --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WPFColorLib.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..8f2fd95 --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index b8382db..f36a2bc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ -# WPFColorLib -WPF library to work with colors +# WPFColorLib + +This library is created for "WPF/.NET FW 4.8" and intended to work with colors. + +------------------------------------------------------------------------------------- +Standard WPF lacks of color selection dialog and WPFColorLib fills this niche with brand new approach: + +![Picture of dialog]( https://i.imgur.com/yeyOuX5_d.webp?maxwidth=760&fidelity=grand ) + +Description of functionality: + +- First, user selects desirable Hue level using top slider +- Saturation/Luminosity gradient below shows color variations of current hue +- Clicking/moving mouse on gradient selects one specific color (and button `OK` become enabled) + and its RGB value is reflected in Hex and Dec fields below. Contrast circle on gradient + shows the place of selection +- Right from "New color" block there is "Old color" if dialog was called with some original color (to compare) +- Each value, hex or dec, can be typed directly (and confirmed with `Enter`) or pasted from + Clipboard (using button on the left of the field) + > Note that pasted text can be any free text taken from CSS, for example. Strings like + `#p1 {background-color: #ff0000;} /* red */` will be correctly parsed, catchig first matching + RGB hex value (here `ff0000`) +- Right from Dec/Hex fields there is ❍ (copy to Clipboard) button. Old color also can be copied +- Once color is selected, "Luminosity palette" is filled with 11 variations of current color, + where H/S is the same and "Luminosity" level vary from 0 to 100 with step 10 - these + samples also can be selected +- Selected color can be added to "Favorite colors", clicking on ⊕ button right from color sample +- When you click with *left* mouse button on "Favorite color" sample, its color become selected. + *Right* click deletes this sample +- "Favorite colors" also can be *generated*: right click on favorite area and select 16-color + Apple's palette or click "Standard 6 level" to generate 216-colors palette, where each + color has 6-level variations of RGB components /easier to try than explain :) /. + From the same popup you can clear favorites +- "Favorite colors" are saved between dialog calls, but lost when you exit application + +Usage: + +```C# +var dlg = new SelectColorDlg(Colors.Bisque) { Owner = this }; +if (dlg.ShowDialog() == true) { + var color = dlg.SelectedColor;// as Color struct + var brush = dlg.SelectedBrush;// the same, but as SolidColorBrush +} +``` + +------------------------------------------------------------------------------------- + +WPFColorLib also contains a few helpers in a static class `ClrHlp`: + +- ColorToHSL - convert `Color` struct to (H, S, L) presentation (H=0..359, S=0..100, L=0..100) +- Hex2color - takes free text with hex RGB presentation and returns created `Color` +- Color2hex - takes `Color` and returns hex RGB presentation +- Hex2Byte - parse hex byte presentation to native `byte` type +- HSL2RGB - convert (H,S,L) color to its RGB presentation (as array of 3 `int`s) +- HSL2RGBInt - the same as `HSL2RGB`, but returns RGB components packed into integer diff --git a/SelectColorDlg.xaml b/SelectColorDlg.xaml new file mode 100644 index 0000000..623501c --- /dev/null +++ b/SelectColorDlg.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SelectColorDlg.xaml.cs b/SelectColorDlg.xaml.cs new file mode 100644 index 0000000..e85098c --- /dev/null +++ b/SelectColorDlg.xaml.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace WPFColorLib +{ + public partial class SelectColorDlg : Window + { + static List favColors = new List();// here we keep favorite colors between dialog calls + + public Color SelectedColor => ((SolidColorBrush)bordNewClr.Background).Color; + public Brush SelectedBrush => bordNewClr.Background; + + public SelectColorDlg(Color? preselColor = null, string title = null) + { + InitializeComponent(); + if (title != null) Title = title; + + foreach(var clr in favColors){ + AddToFavorites(new SolidColorBrush(clr), false); + } + if (preselColor.HasValue) { + var clr = preselColor.Value; + bordOldClr.Background = new SolidColorBrush(clr); + txtOldClrHEX.Text = ClrHlp.Color2hex(clr); + txtOldClrDEC.Text = $"{clr.R},{clr.G},{clr.B}"; + } + hslColorSel.ColorRGBChanged += HslColorSel_ColorRGBChanged; + } + + void HslColorSel_ColorRGBChanged(object sender, Tuple e) + { + ShowSelectedColor(Color.FromRgb(e.Item1, e.Item2, e.Item3)); + } + + void ShowSelectedColor(Color clr) + { + bordNewClr.Background = new SolidColorBrush(clr); + txtNewClrHEX.Text = ClrHlp.Color2hex(clr); + txtNewClrDEC.Text = $"{clr.R}, {clr.G}, {clr.B}"; + + GenerateLuminosityPalette(clr); + + btnOK.IsEnabled = true; + } + + void GenerateLuminosityPalette(Color baseClr) + { + pnlLumPalette.Children.Clear(); + var (hue, sat, lum) = ClrHlp.ColorToHSL(baseClr); + for (int i = 0; i <= 100; i += 10) { + var clrArr = ClrHlp.HSL2RGB(hue, sat, i); + var clr = Color.FromRgb((byte)clrArr[0], (byte)clrArr[1], (byte)clrArr[2]); + var ctrl = new Border { Background = new SolidColorBrush(clr), BorderBrush = Brushes.Black, BorderThickness = thck1, CornerRadius = rad3, Width = 30, Height = 30, Margin = thck2 }; + ctrl.MouseLeftButtonDown += (_, e) => { + ShowSelectedColor(clr); + e.Handled = true; + }; + pnlLumPalette.Children.Add(ctrl); + } + } + + void btnOK_Click(object sender, RoutedEventArgs e) + { + DialogResult = true; + } + + void AddFavorites_NewClr_Click(object sender, RoutedEventArgs e) + { + AddToFavorites(bordNewClr.Background); + } + + void CopyNewClrHEX_Click(object sender, RoutedEventArgs e) + { + Clipboard.SetText(txtNewClrHEX.Text); + //this.ShowHint("Copied " + txtNewClrHEX.Text); + } + + void CopyNewClrDEC_Click(object sender, RoutedEventArgs e) + { + Clipboard.SetText(txtNewClrDEC.Text); + //this.ShowHint("Copied " + txtNewClrDEC.Text); + } + + void AddFavorites_OldClr_Click(object sender, RoutedEventArgs e) + { + AddToFavorites(bordOldClr.Background); + } + + void CopyOldClrHEX_Click(object sender, RoutedEventArgs e) + { + Clipboard.SetText(txtOldClrHEX.Text); + //this.ShowHint("Copied " + txtOldClrHEX.Text); + } + + void CopyOldClrDEC_Click(object sender, RoutedEventArgs e) + { + Clipboard.SetText(txtOldClrDEC.Text); + //this.ShowHint("Copied " + txtOldClrDEC.Text); + } + + static Thickness thck1 = new Thickness(1); + static Thickness thck2 = new Thickness(2); + static CornerRadius rad3 = new CornerRadius(3); + + bool RemoveFavoriteMode;// 'true' when fav.color has RMB-Down + void AddToFavorites(Brush clr, bool saveColor = true) + { + var color = ((SolidColorBrush)clr).Color; + if (saveColor) favColors.Add(color); + + var ctrl = new Border { Background = clr, BorderBrush = Brushes.Black, BorderThickness = thck1, CornerRadius = rad3, Width = 30, Height = 30, Margin = thck2, Tag = color }; + ctrl.MouseLeftButtonDown += (_, e) => { + ShowSelectedColor(color); + e.Handled = true; + }; + ctrl.PreviewMouseRightButtonDown += (_, e) => { + pnlFavColors.Children.Remove(ctrl); + favColors.Remove((Color)ctrl.Tag); + RemoveFavoriteMode = true; + e.Handled = true; + }; + pnlFavColors.Children.Add(ctrl); + } + + /// When fav.color is removed by RMB-Down, next RMB-Up can appear above empty container and should not call popup menu. + void pnlFavColors_RMouUp(object sender, MouseButtonEventArgs e) + { + if (RemoveFavoriteMode){ + RemoveFavoriteMode = false; + e.Handled = true; + } + } + + Regex reHex6 = new Regex(@"[0-9a-f]{6}", RegexOptions.Compiled|RegexOptions.IgnoreCase); + void txtNewClrHEX_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key != Key.Enter) return; + + ParseHexRGB(); + e.Handled = true; + } + + bool ParseHexRGB() + { + if (reHex6.IsMatch(txtNewClrHEX.Text)){ + ShowSelectedColor(ClrHlp.Hex2color(txtNewClrHEX.Text)); + return true; + } else { + MessageBox.Show("Color must be in RRGGBB hex form", "Error parsing color value", MessageBoxButton.OK, MessageBoxImage.Error); + return false; + } + } + + Regex reDec3 = new Regex(@"(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", RegexOptions.Compiled); + void txtNewClrDEC_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key != Key.Enter) return; + + ParseDecRGB(); + e.Handled = true; + } + + bool ParseDecRGB() + { + var m = reDec3.Match(txtNewClrDEC.Text); + if (m.Success){ + try { + var clr = Color.FromRgb(byte.Parse(m.Groups[1].Value), byte.Parse(m.Groups[2].Value), byte.Parse(m.Groups[3].Value)); + ShowSelectedColor(clr); + return true; + } catch { + MessageBox.Show("Check values - they must fit BYTE", "Error parsing color value", MessageBoxButton.OK, MessageBoxImage.Error); + } + } else { + MessageBox.Show("Color must be in 0,0,0 decimal form", "Error parsing color value", MessageBoxButton.OK, MessageBoxImage.Error); + } + return false; + } + + void PasteNewClrHEX_Click(object sender, RoutedEventArgs e) + { + var oldText = txtNewClrHEX.Text; + txtNewClrHEX.Text = Clipboard.GetText(); + if (!ParseHexRGB()) txtNewClrHEX.Text = oldText; + } + + void PasteNewClrDEC_Click(object sender, RoutedEventArgs e) + { + var oldText = txtNewClrDEC.Text; + txtNewClrDEC.Text = Clipboard.GetText(); + if (!ParseDecRGB()) txtNewClrDEC.Text = oldText; + } + + static List paletteApple16 = new List { + "FFFFFF", "FCF400", "FF6400", "DD0202", "F00285", "4600A5", "0000D5", "00AEE9", + "1AB90C", "006407", "572800", "917035", "C1C1C1", "818181", "3E3E3E", "000000", }; + void GenerateAppleColors_Click(object sender, RoutedEventArgs e) + { + foreach (var s in paletteApple16) + AddToFavorites(new SolidColorBrush(ClrHlp.Hex2color(s))); + } + + static string[] hex6LevelColor = new string[] { "00", "33", "66", "99", "CC", "FF" }; + void GenerateStdColors_Click(object sender, RoutedEventArgs e) + { + for(int b=0; b < hex6LevelColor.Length; b++) + for(int g=0; g < hex6LevelColor.Length; g++) + for(int r=0; r < hex6LevelColor.Length; r++){ + AddToFavorites(new SolidColorBrush(ClrHlp.Hex2color(hex6LevelColor[r] + hex6LevelColor[g] + hex6LevelColor[b]))); + } + } + + void ClearFav_Click(object sender, RoutedEventArgs e) + { + pnlFavColors.Children.Clear(); + favColors.Clear(); + } + } +} diff --git a/WPFColorLib.csproj b/WPFColorLib.csproj new file mode 100644 index 0000000..5f112c2 --- /dev/null +++ b/WPFColorLib.csproj @@ -0,0 +1,103 @@ + + + + + Debug + AnyCPU + {8658B379-126A-4A66-91C9-D3FF5D352623} + library + WPFColorLib + WPFColorLib + v4.8 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + R:\TEMP\obj\$(ProjectGuid)\ + Latest + + + true + full + false + bin_Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin_Release\ + TRACE + prompt + 4 + true + + + + + + False + ..\..\DEV\ExtLibs\CoreLibs\System.Memory.dll + + + False + ..\..\DEV\ExtLibs\CoreLibs\System.Runtime.CompilerServices.Unsafe.dll + + + + + + + + + 4.0 + + + + + + + + SelectColorDlg.xaml + + + MSBuild:Compile + Designer + + + + HSLColorSelector.xaml + + + Designer + MSBuild:Compile + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + \ No newline at end of file diff --git a/WPFColorLib.sln b/WPFColorLib.sln new file mode 100644 index 0000000..f60f067 --- /dev/null +++ b/WPFColorLib.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32929.388 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFColorLib", "WPFColorLib.csproj", "{8658B379-126A-4A66-91C9-D3FF5D352623}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8658B379-126A-4A66-91C9-D3FF5D352623}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8658B379-126A-4A66-91C9-D3FF5D352623}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8658B379-126A-4A66-91C9-D3FF5D352623}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8658B379-126A-4A66-91C9-D3FF5D352623}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DFD9C410-9C0C-454F-8F18-34CE3DD9C03A} + EndGlobalSection +EndGlobal