diff --git a/ThemeEditor.sln b/ThemeEditor.sln index ade1b51..2ae0a68 100644 --- a/ThemeEditor.sln +++ b/ThemeEditor.sln @@ -41,8 +41,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "props", "props", "{A88815F2 build\Newtonsoft.Json.props = build\Newtonsoft.Json.props build\ReactiveUI.props = build\ReactiveUI.props build\Rx.props = build\Rx.props + build\XUnit.props = build\XUnit.props EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F2D7C825-1AC1-45FF-A221-CEE80E2F2374}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThemeEditor.Colors", "src\ThemeEditor.Colors\ThemeEditor.Colors.csproj", "{BFDD803E-2B9B-46A2-AC25-C77FD2376893}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThemeEditor.Colors.UnitTests", "tests\ThemeEditor.Colors.UnitTests\ThemeEditor.Colors.UnitTests.csproj", "{E2079787-7F2F-46F8-BD56-2C226D883904}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +64,14 @@ Global {2B5611EB-1E0D-4EAB-96C9-821A965E496B}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B5611EB-1E0D-4EAB-96C9-821A965E496B}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B5611EB-1E0D-4EAB-96C9-821A965E496B}.Release|Any CPU.Build.0 = Release|Any CPU + {BFDD803E-2B9B-46A2-AC25-C77FD2376893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFDD803E-2B9B-46A2-AC25-C77FD2376893}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFDD803E-2B9B-46A2-AC25-C77FD2376893}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFDD803E-2B9B-46A2-AC25-C77FD2376893}.Release|Any CPU.Build.0 = Release|Any CPU + {E2079787-7F2F-46F8-BD56-2C226D883904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2079787-7F2F-46F8-BD56-2C226D883904}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2079787-7F2F-46F8-BD56-2C226D883904}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2079787-7F2F-46F8-BD56-2C226D883904}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -68,6 +83,8 @@ Global {497FBEFD-7228-4E2F-A947-9F416498E263} = {3353FD4A-FE9B-4670-86C9-09EF0F7CEDB2} {9A80222C-EDE8-42AF-9D6D-76325FC4673F} = {3353FD4A-FE9B-4670-86C9-09EF0F7CEDB2} {A88815F2-21C3-4EBA-8DBC-0A461EA7F82C} = {3353FD4A-FE9B-4670-86C9-09EF0F7CEDB2} + {BFDD803E-2B9B-46A2-AC25-C77FD2376893} = {219954C4-6A40-4D94-914E-148B7EBB718E} + {E2079787-7F2F-46F8-BD56-2C226D883904} = {F2D7C825-1AC1-45FF-A221-CEE80E2F2374} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CF676549-9409-4598-8229-80E04B51ADC5} diff --git a/src/ThemeEditor.Colors/HSV.cs b/src/ThemeEditor.Colors/HSV.cs new file mode 100644 index 0000000..5d31e90 --- /dev/null +++ b/src/ThemeEditor.Colors/HSV.cs @@ -0,0 +1,98 @@ +using System; + +namespace ThemeEditor.Colors +{ + public readonly struct HSV + { + public double H { get; } + public double S { get; } + public double V { get; } + + public HSV(double h, double s, double v) + { + H = h; + S = s; + V = v; + } + + public HSV(HSV hsv) + { + H = hsv.H; + S = hsv.S; + V = hsv.V; + } + + public HSV(RGB rgb) + { + HSV hsv = rgb.ToHSV(); + H = hsv.H; + S = hsv.S; + V = hsv.V; + } + + public RGB ToRGB() => ToRGB(H, S, V); + + public static RGB ToRGB(double h, double s, double v) + { + double R = default; + double G = default; + double B = default; + + if (s == 0) + { + R = G = B = Math.Round(v * 2.55); + return new RGB(R, G, B); + } + + s = s / 100; + v = v / 100; + h /= 60; + + var i = Math.Floor(h); + var f = h - i; + var p = v * (1 - s); + var q = v * (1 - s * f); + var t = v * (1 - s * (1 - f)); + + switch ((int)i) + { + case 0: + R = v; + G = t; + B = p; + break; + case 1: + R = q; + G = v; + B = p; + break; + case 2: + R = p; + G = v; + B = t; + break; + case 3: + R = p; + G = q; + B = v; + break; + case 4: + R = t; + G = p; + B = v; + break; + default: + R = v; + G = p; + B = q; + break; + } + + R = Math.Round(R * 255); + G = Math.Round(G * 255); + B = Math.Round(B * 255); + + return new RGB(R, G, B); + } + } +} diff --git a/src/ThemeEditor.Colors/RGB.cs b/src/ThemeEditor.Colors/RGB.cs new file mode 100644 index 0000000..3c26d20 --- /dev/null +++ b/src/ThemeEditor.Colors/RGB.cs @@ -0,0 +1,109 @@ +using System; + +namespace ThemeEditor.Colors +{ + public readonly struct RGB + { + public double R { get; } + public double G { get; } + public double B { get; } + + public RGB(double r, double g, double b) + { + R = r; + G = g; + B = b; + } + + public RGB(RGB rgb) + { + R = rgb.R; + G = rgb.G; + B = rgb.B; + } + + public RGB(HSV hsv) + { + RGB rgb = hsv.ToRGB(); + R = rgb.R; + G = rgb.G; + B = rgb.B; + } + + public HSV ToHSV() => ToHSV(R, G, B); + + public static HSV ToHSV(double r, double g, double b) + { + double H = default; + double S = default; + double V = default; + + var m = r; + + if (g < m) + { + m = g; + } + + if (b < m) + { + m = b; + } + + var v = r; + + if (g > v) + { + v = g; + } + + if (b > v) + { + v = b; + } + + var value = 100 * v / 255; + var delta = v - m; + + if (v == 0.0) + { + S = 0; + } + else + { + S = 100 * delta / v; + } + + if (S == 0) + { + H = 0; + } + else + { + if (r == v) + { + H = 60.0 * (g - b) / delta; + } + else if (g == v) + { + H = 120.0 + 60.0 * (b - r) / delta; + } + else if (b == v) + { + H = 240.0 + 60.0 * (r - g) / delta; + } + + if (H < 0.0) + { + H = H + 360.0; + } + } + + H = Math.Round(H); + S = Math.Round(S); + V = Math.Round(value); + + return new HSV(H, S, V); + } + } +} diff --git a/src/ThemeEditor.Colors/ThemeEditor.Colors.csproj b/src/ThemeEditor.Colors/ThemeEditor.Colors.csproj new file mode 100644 index 0000000..2b71fbd --- /dev/null +++ b/src/ThemeEditor.Colors/ThemeEditor.Colors.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + Library + False + latest + + + + + + + + + diff --git a/src/ThemeEditor/Controls/ColorPicker.xaml b/src/ThemeEditor/Controls/ColorPicker.xaml index af5ee57..ed36bc8 100644 --- a/src/ThemeEditor/Controls/ColorPicker.xaml +++ b/src/ThemeEditor/Controls/ColorPicker.xaml @@ -1,12 +1,12 @@  - +