diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 6470c94..e510cff 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -1,28 +1,31 @@ -name: .NET Core +name: CI on: push: - branches: [ master ] + branches: [ master, develop ] pull_request: - branches: [ master ] + branches: [ master, develop ] jobs: - build: - - runs-on: ubuntu-latest - + build-and-test: + runs-on: windows-latest + strategy: + matrix: + test-framework: ['net9.0'] + #test-framework: [ 'net8.0', 'net9.0', 'net48', 'net481' ] steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.101 - - name: Install dependencies - working-directory: src - run: dotnet restore - - name: Build - working-directory: src - run: dotnet build --configuration Release --no-restore - - name: Test - working-directory: src - run: dotnet test --no-restore --verbosity normal + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: + 9.0.x + - name: Install dependencies + working-directory: src + run: dotnet restore + - name: Build + working-directory: src + run: dotnet build --configuration Release --no-restore + - name: Test + working-directory: src + run: dotnet test --no-restore --verbosity normal --framework ${{ matrix.test-framework }} diff --git a/.gitignore b/.gitignore index 3e759b7..51db318 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ +/src/ColorHashSharp.Tests/coverage.json diff --git a/Benchmark.md b/Benchmark.md new file mode 100644 index 0000000..55f6744 --- /dev/null +++ b/Benchmark.md @@ -0,0 +1,20 @@ +# ColorHashSharp Benchmark + +### Benchmark NET6 & NET7 + +BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.1281/21H2) +Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +.NET SDK=7.0.100 + [Host] : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT AVX2 [AttachedDebugger] + .NET 6.0 : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT AVX2 + .NET 7.0 : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2 + + +| Method | Job | Runtime | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | +|------- |--------- |--------- |---------:|----------:|----------:|---------:|------:|--------:|-------:|----------:|------------:| +| Hsl | .NET 6.0 | .NET 6.0 | 1.289 us | 0.0292 us | 0.0789 us | 1.263 us | 1.00 | 0.00 | 0.5360 | 2.2 KB | 1.00 | +| Rgb | .NET 6.0 | .NET 6.0 | 1.296 us | 0.0256 us | 0.0639 us | 1.280 us | 1.00 | 0.08 | 0.5360 | 2.2 KB | 1.00 | +| Hex | .NET 6.0 | .NET 6.0 | 1.435 us | 0.0284 us | 0.0433 us | 1.432 us | 1.09 | 0.07 | 0.5741 | 2.35 KB | 1.07 | +| Hsl | .NET 7.0 | .NET 7.0 | 1.432 us | 0.0674 us | 0.1911 us | 1.372 us | 1.12 | 0.17 | 0.5360 | 2.2 KB | 1.00 | +| Rgb | .NET 7.0 | .NET 7.0 | 1.497 us | 0.0678 us | 0.1890 us | 1.448 us | 1.15 | 0.15 | 0.5360 | 2.2 KB | 1.00 | +| Hex | .NET 7.0 | .NET 7.0 | 1.358 us | 0.0272 us | 0.0381 us | 1.350 us | 1.03 | 0.07 | 0.5741 | 2.35 KB | 1.07 | \ No newline at end of file diff --git a/Links.md b/Links.md new file mode 100644 index 0000000..75bf708 --- /dev/null +++ b/Links.md @@ -0,0 +1,17 @@ +# ColorHashSharp resources links + +#### Links +- [Packaging Icon within the nupkg](https://github.com/NuGet/Home/wiki/Packaging-Icon-within-the-nupkg) +- [Supporting Multiple Target Frameworks](https://learn.microsoft.com/en-us/nuget/create-packages/supporting-multiple-target-frameworks) +- [NuGet Package Explorer](https://apps.microsoft.com/detail/9WZDNCRDMDM3?hl=en-us&gl=AR&ocid=pdpshare) +- [NuGet Package Explorer GitHub](https://github.com/NuGetPackageExplorer/NuGetPackageExplorer) + +#### Command to push a package to NuGet +```bash +dotnet nuget push file.nupkg -k {YOUR_API_KEY_HERE} -s https://api.nuget.org/v3/index.json +``` + +#### Command to pack a project +```bash +dotnet pack --configuration Release +``` \ No newline at end of file diff --git a/README.md b/README.md index 6761901..41fa831 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ # ColorHashSharp Generate color based on the given string. C# port of [ColorHash Javascript Library](https://github.com/zenozeng/color-hash). -[![NuGet version (ColorHashSharp)](https://img.shields.io/nuget/v/ColorHashSharp.svg?style=flat-square)](https://www.nuget.org/packages/ColorHashSharp/) -![.NET Core](https://github.com/fernandezja/ColorHashSharp/workflows/.NET%20Core/badge.svg?branch=master) -[![Build status](https://fernandezja.visualstudio.com/ColorHashSharp/_apis/build/status/ColorHashSharp-CI)](https://fernandezja.visualstudio.com/ColorHashSharp/_build/latest?definitionId=4) +[![NuGet version (ColorHashSharp)](https://img.shields.io/nuget/v/ColorHashSharp.svg?style=flat-square)](https://www.nuget.org/packages/ColorHashSharp/)   [![NuGet Downloads](https://img.shields.io/nuget/dt/ColorHashSharp.svg)](https://www.nuget.org/packages/Serilog/) + +[![CI](https://github.com/fernandezja/ColorHashSharp/actions/workflows/dotnet-core.yml/badge.svg)](https://github.com/fernandezja/ColorHashSharp/actions/workflows/dotnet-core.yml)  [![Build status](https://fernandezja.visualstudio.com/ColorHashSharp/_apis/build/status/ColorHashSharp-CI)](https://fernandezja.visualstudio.com/ColorHashSharp/_build/latest?definitionId=4) + +#### Status + + +|Actions |master |develop | +|--- |--- |--- | +|CI |[![CI](https://github.com/fernandezja/ColorHashSharp/actions/workflows/dotnet-core.yml/badge.svg?branch=master)](https://github.com/fernandezja/ColorHashSharp/actions/workflows/dotnet-core.yml) |[![CI](https://github.com/fernandezja/ColorHashSharp/actions/workflows/dotnet-core.yml/badge.svg?branch=develop)](https://github.com/fernandezja/ColorHashSharp/actions/workflows/dotnet-core.yml) | + #### Basic diff --git a/assets/icon/ColorHashSharp-icon.png b/assets/icon/ColorHashSharp-icon.png index b311eb8..a998d1b 100644 Binary files a/assets/icon/ColorHashSharp-icon.png and b/assets/icon/ColorHashSharp-icon.png differ diff --git a/assets/icon/ColorHashSharp.png b/assets/icon/ColorHashSharp.png new file mode 100644 index 0000000..b311eb8 Binary files /dev/null and b/assets/icon/ColorHashSharp.png differ diff --git a/src/ColorHashSharp.Benchmarks/ColorHashSharp.Benchmarks.csproj b/src/ColorHashSharp.Benchmarks/ColorHashSharp.Benchmarks.csproj new file mode 100644 index 0000000..ffaa1a7 --- /dev/null +++ b/src/ColorHashSharp.Benchmarks/ColorHashSharp.Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0;net9.0 + enable + enable + + + + + + + + + + + diff --git a/src/ColorHashSharp.Benchmarks/ColorHashSharpBenchmarks.cs b/src/ColorHashSharp.Benchmarks/ColorHashSharpBenchmarks.cs new file mode 100644 index 0000000..6ef1e87 --- /dev/null +++ b/src/ColorHashSharp.Benchmarks/ColorHashSharpBenchmarks.cs @@ -0,0 +1,44 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using Fernandezja.ColorHashSharp; +using Fernandezja.ColorHashSharp.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ColorHashSharp.Benchmarks +{ + [MemoryDiagnoser] + [SimpleJob(RuntimeMoniker.Net48)] + [SimpleJob(RuntimeMoniker.Net481)] + [SimpleJob(RuntimeMoniker.Net80, baseline: true)] + [SimpleJob(RuntimeMoniker.Net90)] + [SimpleJob(RuntimeMoniker.Net10_0)] + public class ColorHashSharpBenchmarks + { + private const string STRING_TO_HASH = "The Force will be with you always"; + + private ColorHash _colorHash = new(); + + + [Benchmark(Baseline = true)] + public void Hsl() + { + _ = _colorHash.Hsl(STRING_TO_HASH); + } + + [Benchmark] + public void Rgb() + { + _ = _colorHash.Rgb(STRING_TO_HASH); + } + + [Benchmark] + public void Hex() + { + _ = _colorHash.Hex(STRING_TO_HASH); + } + } +} diff --git a/src/ColorHashSharp.Benchmarks/Program.cs b/src/ColorHashSharp.Benchmarks/Program.cs new file mode 100644 index 0000000..e2e060f --- /dev/null +++ b/src/ColorHashSharp.Benchmarks/Program.cs @@ -0,0 +1,8 @@ +using BenchmarkDotNet.Running; +using ColorHashSharp.Benchmarks; + +Console.WriteLine("ColorHashSharp benchmarks!"); + +_ = BenchmarkRunner.Run(); + +Console.ReadKey(); \ No newline at end of file diff --git a/src/ColorHashSharp.Tests/ColorHashSharp.Tests.csproj b/src/ColorHashSharp.Tests/ColorHashSharp.Tests.csproj index 287bf41..51e27a4 100644 --- a/src/ColorHashSharp.Tests/ColorHashSharp.Tests.csproj +++ b/src/ColorHashSharp.Tests/ColorHashSharp.Tests.csproj @@ -1,15 +1,18 @@  - netcoreapp3.0 - + net9.0 false - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all runtime; build; native; contentfiles; analyzers diff --git a/src/ColorHashSharp.Tests/ColorHashTest.cs b/src/ColorHashSharp.Tests/ColorHashTest.cs index 73a770d..914c9d4 100644 --- a/src/ColorHashSharp.Tests/ColorHashTest.cs +++ b/src/ColorHashSharp.Tests/ColorHashTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Text; @@ -42,6 +43,53 @@ public void BuildToHsl_ShouldCreateAColorInHSL( Assert.Equal(lExpected, result.L); } + + [Theory(DisplayName = "BuildToHsl_ShouldCreateAColorInHSLWithCustomHueOption")] + [InlineData("yoda", 90, 90, 0.35, 0.35)] + [InlineData("yoda", 270, 270, 0.35, 0.35)] + public void BuildToHsl_ShouldCreateAColorInHSLWithCustomHueOption( + string phrase, int hue, + int hExpected, double sExpected, double lExpected) + { + var options = new Options(); + options.SetHue(hue); + + var colorHash = new Fernandezja.ColorHashSharp.ColorHash(options); + + var result = colorHash.BuildToHsl(phrase); + + Assert.NotNull(result); + Assert.Equal(hExpected, result.H); + Assert.Equal(sExpected, result.S); + Assert.Equal(lExpected, result.L); + + + } + + + + [Theory(DisplayName = "BuildToHsl_ShouldCreateAColorInHexWithCustomHueOption")] + [InlineData("yoda", 90, "59783A")] + [InlineData("yoda", 210, "3A5978")] + [InlineData("yoda", 270, "593A78")] + [InlineData("yoda", 360, "783A3A")] + public void BuildToHsl_ShouldCreateAColorInHexWithCustomHueOption( + string phrase, int hue, string hexExpected) + { + var options = new Options(); + options.SetHue(hue); + + var colorHash = new Fernandezja.ColorHashSharp.ColorHash(options); + + var result = colorHash.BuildToHex(phrase); + + Assert.NotNull(result); + + Assert.Equal(hexExpected, result); + + + } + [Theory(DisplayName = "BuildToHex_ShouldCreateAColorInHex")] [InlineData("yoda", "68783A")] [InlineData("Yoda", "D279BE")] diff --git a/src/ColorHashSharp.Tests/OptionsTest.cs b/src/ColorHashSharp.Tests/OptionsTest.cs index 987bf71..66a7124 100644 --- a/src/ColorHashSharp.Tests/OptionsTest.cs +++ b/src/ColorHashSharp.Tests/OptionsTest.cs @@ -46,5 +46,61 @@ public void GetLS_ArrayValues() } + [Theory(DisplayName = "SetHue_SimpleValue")] + [InlineData(90)] + [InlineData(0.35)] + public void SetHue_SimpleValue(int value) + { + var options = new Fernandezja.ColorHashSharp.Options(); + options.SetHue(value); + + Assert.NotNull(options.HueRanges); + Assert.Single(options.HueRanges); + Assert.Equal(value, options.HueRanges[0].Min); + Assert.Equal(value, options.HueRanges[0].Max); + } + + + [Fact(DisplayName = "SetHue_ListValues")] + public void SetHue_ListValues() + { + var options = new Fernandezja.ColorHashSharp.Options(); + + var hueValues = new List<(int Min, int Max)>(); + hueValues.Add((90, 270)); + + options.SetHue(hueValues); + + Assert.NotNull(options.HueRanges); + Assert.Single(options.HueRanges); + Assert.Equal(90, options.HueRanges[0].Min); + Assert.Equal(270, options.HueRanges[0].Max); + } + + [Fact(DisplayName = "SetHue_ListValues2")] + public void SetHue_ListValues2() + { + var options = new Fernandezja.ColorHashSharp.Options(); + + var hueValues = new List<(int Min, int Max)>(); + hueValues.Add((30, 90)); + hueValues.Add((180, 210)); + hueValues.Add((270, 285)); + + options.SetHue(hueValues); + + Assert.NotNull(options.HueRanges); + Assert.Equal(3, options.HueRanges.Count); + + Assert.Equal(30, options.HueRanges[0].Min); + Assert.Equal(90, options.HueRanges[0].Max); + + Assert.Equal(180, options.HueRanges[1].Min); + Assert.Equal(210, options.HueRanges[1].Max); + + Assert.Equal(270, options.HueRanges[2].Min); + Assert.Equal(285, options.HueRanges[2].Max); + } + } } diff --git a/src/ColorHashSharp.sln b/src/ColorHashSharp.sln index 3c2c754..2f25105 100644 --- a/src/ColorHashSharp.sln +++ b/src/ColorHashSharp.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.168 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33103.201 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorHashSharp", "ColorHashSharp\ColorHashSharp.csproj", "{68766358-4CA1-4A57-94B9-0E1CF0F809BF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorHashSharp.Tests", "ColorHashSharp.Tests\ColorHashSharp.Tests.csproj", "{2170724A-3466-4604-B243-D3B0936B44A3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorHashSharp.Benchmarks", "ColorHashSharp.Benchmarks\ColorHashSharp.Benchmarks.csproj", "{2CC808B2-AF1F-4556-B601-C755129CCEB0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {2170724A-3466-4604-B243-D3B0936B44A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {2170724A-3466-4604-B243-D3B0936B44A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {2170724A-3466-4604-B243-D3B0936B44A3}.Release|Any CPU.Build.0 = Release|Any CPU + {2CC808B2-AF1F-4556-B601-C755129CCEB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CC808B2-AF1F-4556-B601-C755129CCEB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CC808B2-AF1F-4556-B601-C755129CCEB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CC808B2-AF1F-4556-B601-C755129CCEB0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ColorHashSharp/.editorconfig b/src/ColorHashSharp/.editorconfig new file mode 100644 index 0000000..2a5e964 --- /dev/null +++ b/src/ColorHashSharp/.editorconfig @@ -0,0 +1,71 @@ + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf + +[*.cs] +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent \ No newline at end of file diff --git a/src/ColorHashSharp/ColorHash.cs b/src/ColorHashSharp/ColorHash.cs index 99e0c63..8a85060 100644 --- a/src/ColorHashSharp/ColorHash.cs +++ b/src/ColorHashSharp/ColorHash.cs @@ -18,6 +18,11 @@ public ColorHash() _options = new Options(); } + public ColorHash(Options options) + { + _options = options; + } + #region IColorHash public string Build(string value) diff --git a/src/ColorHashSharp/ColorHashSharp.csproj b/src/ColorHashSharp/ColorHashSharp.csproj index 856f450..a0eeaf0 100644 --- a/src/ColorHashSharp/ColorHashSharp.csproj +++ b/src/ColorHashSharp/ColorHashSharp.csproj @@ -1,22 +1,32 @@  - netstandard2.1 + netstandard2.0;netstandard2.1;net48;net481;net8.0;net9.0 Fernandezja.ColorHashSharp false - 1.0.0.0 - 1.0.0 - 1.0.0 + 1.1.0.0 + 1.1.0 + 1.1.0 Generate color based on the given string Generate color based on the given string. C# port of ColorHash Javascript Library. Jose A. Fernandez - https://licenses.nuget.org/MIT + https://licenses.nuget.org/MIT - https://raw.githubusercontent.com/fernandezja/ColorHashSharp/master/assets/icon/ColorHashSharp-icon.png + ColorHashSharp-icon.png https://github.com/fernandezja/ColorHashSharp color hash string hsl hex hexadecimal Generate color based on the given string. C# port of ColorHash Javascript Library. See https://github.com/fernandezja/ColorHashSharp true + + + + + + + + + + diff --git a/src/ColorHashSharp/ColorHashSharp.nuspec b/src/ColorHashSharp/ColorHashSharp.nuspec new file mode 100644 index 0000000..8a5372a --- /dev/null +++ b/src/ColorHashSharp/ColorHashSharp.nuspec @@ -0,0 +1,27 @@ + + + + ColorHashSharp + 1.1.0 + Jose A. Fernandez + Jose A. Fernandez + MIT + https://github.com/fernandezja/ColorHashSharp + ColorHashSharp-icon.png + false + Generate color based on the given string. C# port of ColorHash Javascript Library. + Generate color based on the given string. C# port of ColorHash Javascript Library. + Generate color based on the given string. C# port of ColorHash Javascript Library. See https://github.com/fernandezja/ColorHashSharp + color hash string hsl hex hexadecimal + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ColorHashSharp/Entities/Hue.cs b/src/ColorHashSharp/Entities/Hue.cs index 7d44389..fa1a10f 100644 --- a/src/ColorHashSharp/Entities/Hue.cs +++ b/src/ColorHashSharp/Entities/Hue.cs @@ -18,6 +18,12 @@ public Hue() Max = 360; } + public Hue(int min, int max) + { + Min = min; + Max = max; + } + public int Min { get; set; } public int Max { get; set; } diff --git a/src/ColorHashSharp/Images/ColorHashSharp-icon.png b/src/ColorHashSharp/Images/ColorHashSharp-icon.png new file mode 100644 index 0000000..a998d1b Binary files /dev/null and b/src/ColorHashSharp/Images/ColorHashSharp-icon.png differ diff --git a/src/ColorHashSharp/Options.cs b/src/ColorHashSharp/Options.cs index f1bdf10..7306ece 100644 --- a/src/ColorHashSharp/Options.cs +++ b/src/ColorHashSharp/Options.cs @@ -1,4 +1,5 @@ -using System; +using Fernandezja.ColorHashSharp.Entities; +using System; using System.Collections; using System.Collections.Generic; using System.Text; @@ -7,7 +8,9 @@ namespace Fernandezja.ColorHashSharp { public class Options { - public ArrayList HueRanges { get; set; } + public List HueRanges { get; set; } + + //public List Hue { get; set; } /// /// Saturation @@ -25,7 +28,7 @@ public Options() S = GetLS(new ArrayList() { 0.35, 0.5, 0.65 }); L = GetLS(new ArrayList() { 0.35, 0.5, 0.65 }); - HueRanges = new ArrayList(); + HueRanges = new List(); } @@ -61,5 +64,22 @@ internal protected ArrayList GetLS() } + internal protected void SetHue(int value) + { + HueRanges = new List(); + HueRanges.Add(new Hue(value, value)); + } + + internal protected void SetHue(List<(int Min, int Max)> values) + { + HueRanges = new List(); + + foreach (var value in values) + { + HueRanges.Add(new Hue(value.Min, value.Max)); + } + } + + } } diff --git a/src/ColorHashSharp/SimpleColorTransforms.cs b/src/ColorHashSharp/SimpleColorTransforms.cs new file mode 100644 index 0000000..9cdd6d6 --- /dev/null +++ b/src/ColorHashSharp/SimpleColorTransforms.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using SystemMath = System.Math; + +namespace Fernandezja.ColorHashSharp +{ + class SimpleColorTransforms + { + private static double tolerance + => 0.000000000000001; + + + /// + /// Defines brightness levels. + /// + public enum Brightness + : byte + { + Bright = 255, + MediumBright = 210, + Medium = 142, + Dim = 98, + XDim = 50 + } + + + /// + /// Defines alpha levels. + /// + public enum Alpha + : byte + { + Opaque = 255, + MediumHigh = 230, + Medium = 175, + MediumLow = 142, + Low = 109, + XLow = 45 + } + + + /// + /// Defines hint alpha levels. + /// + public enum HintAlpha + : byte + { + Low = 64, + XLow = 48, + XxLow = 32, + XxxLow = 16 + } + + + /// + /// Specifies a mode for argb transformations. + /// + public enum ColorTransformMode + : byte + { + Hsl, + Hsb + } + + + /// + /// Converts RGB to HSL. Alpha is ignored. + /// Output is: { H: [0, 360], S: [0, 1], L: [0, 1] }. + /// + /// The color to convert. + public static double[] RgBtoHsl(Color color) + { + double h = 0D; + double s = 0D; + double l; + + // normalize red, green, blue values + double r = color.R / 255D; + double g = color.G / 255D; + double b = color.B / 255D; + + double max = SystemMath.Max(r, SystemMath.Max(g, b)); + double min = SystemMath.Min(r, SystemMath.Min(g, b)); + + // hue + if (SystemMath.Abs(max - min) < SimpleColorTransforms.tolerance) + h = 0D; // undefined + else if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance) + && (g >= b)) + h = (60D * (g - b)) / (max - min); + else if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance) + && (g < b)) + h = ((60D * (g - b)) / (max - min)) + 360D; + else if (SystemMath.Abs(max - g) < SimpleColorTransforms.tolerance) + h = ((60D * (b - r)) / (max - min)) + 120D; + else if (SystemMath.Abs(max - b) < SimpleColorTransforms.tolerance) + h = ((60D * (r - g)) / (max - min)) + 240D; + + // luminance + l = (max + min) / 2D; + + // saturation + if ((SystemMath.Abs(l) < SimpleColorTransforms.tolerance) + || (SystemMath.Abs(max - min) < SimpleColorTransforms.tolerance)) + s = 0D; + else if ((0D < l) + && (l <= .5D)) + s = (max - min) / (max + min); + else if (l > .5D) + s = (max - min) / (2D - (max + min)); //(max-min > 0)? + + return new[] + { + SystemMath.Max(0D, SystemMath.Min(360D, double.Parse($"{h:0.##}"))), + SystemMath.Max(0D, SystemMath.Min(1D, double.Parse($"{s:0.##}"))), + SystemMath.Max(0D, SystemMath.Min(1D, double.Parse($"{l:0.##}"))) + }; + } + + + /// + /// Converts HSL to RGB, with a specified output Alpha. + /// Arguments are limited to the defined range: + /// does not raise exceptions. + /// + /// Hue, must be in [0, 360]. + /// Saturation, must be in [0, 1]. + /// Luminance, must be in [0, 1]. + /// Output Alpha, must be in [0, 255]. + public static Color HsLtoRgb(double h, double s, double l, int a = 255) + { + h = SystemMath.Max(0D, SystemMath.Min(360D, h)); + s = SystemMath.Max(0D, SystemMath.Min(1D, s)); + l = SystemMath.Max(0D, SystemMath.Min(1D, l)); + a = SystemMath.Max(0, SystemMath.Min(255, a)); + + // achromatic argb (gray scale) + if (SystemMath.Abs(s) < SimpleColorTransforms.tolerance) + { + return Color.FromArgb( + a, + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{l * 255D:0.00}")))), + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{l * 255D:0.00}")))), + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{l * 255D:0.00}"))))); + } + + double q = l < .5D + ? l * (1D + s) + : (l + s) - (l * s); + double p = (2D * l) - q; + + double hk = h / 360D; + double[] T = new double[3]; + T[0] = hk + (1D / 3D); // Tr + T[1] = hk; // Tb + T[2] = hk - (1D / 3D); // Tg + + for (int i = 0; i < 3; i++) + { + if (T[i] < 0D) + T[i] += 1D; + if (T[i] > 1D) + T[i] -= 1D; + + if ((T[i] * 6D) < 1D) + T[i] = p + ((q - p) * 6D * T[i]); + else if ((T[i] * 2D) < 1) + T[i] = q; + else if ((T[i] * 3D) < 2) + T[i] = p + ((q - p) * ((2D / 3D) - T[i]) * 6D); + else + T[i] = p; + } + + return Color.FromArgb( + a, + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{T[0] * 255D:0.00}")))), + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{T[1] * 255D:0.00}")))), + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{T[2] * 255D:0.00}"))))); + } + + + /// + /// Converts RGB to HSB. Alpha is ignored. + /// Output is: { H: [0, 360], S: [0, 1], B: [0, 1] }. + /// + /// The color to convert. + public static double[] RgBtoHsb(Color color) + { + // normalize red, green and blue values + double r = color.R / 255D; + double g = color.G / 255D; + double b = color.B / 255D; + + // conversion start + double max = SystemMath.Max(r, SystemMath.Max(g, b)); + double min = SystemMath.Min(r, SystemMath.Min(g, b)); + + double h = 0D; + if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance) + && (g >= b)) + h = (60D * (g - b)) / (max - min); + else if ((SystemMath.Abs(max - r) < SimpleColorTransforms.tolerance) + && (g < b)) + h = ((60D * (g - b)) / (max - min)) + 360D; + else if (SystemMath.Abs(max - g) < SimpleColorTransforms.tolerance) + h = ((60D * (b - r)) / (max - min)) + 120D; + else if (SystemMath.Abs(max - b) < SimpleColorTransforms.tolerance) + h = ((60D * (r - g)) / (max - min)) + 240D; + + double s = SystemMath.Abs(max) < SimpleColorTransforms.tolerance + ? 0D + : 1D - (min / max); + + return new[] + { + SystemMath.Max(0D, SystemMath.Min(360D, h)), + SystemMath.Max(0D, SystemMath.Min(1D, s)), + SystemMath.Max(0D, SystemMath.Min(1D, max)) + }; + } + + + /// + /// Converts HSB to RGB, with a specified output Alpha. + /// Arguments are limited to the defined range: + /// does not raise exceptions. + /// + /// Hue, must be in [0, 360]. + /// Saturation, must be in [0, 1]. + /// Brightness, must be in [0, 1]. + /// Output Alpha, must be in [0, 255]. + public static Color HsBtoRgb(double h, double s, double b, int a = 255) + { + h = SystemMath.Max(0D, SystemMath.Min(360D, h)); + s = SystemMath.Max(0D, SystemMath.Min(1D, s)); + b = SystemMath.Max(0D, SystemMath.Min(1D, b)); + a = SystemMath.Max(0, SystemMath.Min(255, a)); + + double r = 0D; + double g = 0D; + double bl = 0D; + + if (SystemMath.Abs(s) < SimpleColorTransforms.tolerance) + r = g = bl = b; + else + { + // the argb wheel consists of 6 sectors. Figure out which sector + // you're in. + double sectorPos = h / 60D; + int sectorNumber = (int)SystemMath.Floor(sectorPos); + // get the fractional part of the sector + double fractionalSector = sectorPos - sectorNumber; + + // calculate values for the three axes of the argb. + double p = b * (1D - s); + double q = b * (1D - (s * fractionalSector)); + double t = b * (1D - (s * (1D - fractionalSector))); + + // assign the fractional colors to r, g, and b based on the sector + // the angle is in. + switch (sectorNumber) + { + case 0: + r = b; + g = t; + bl = p; + break; + case 1: + r = q; + g = b; + bl = p; + break; + case 2: + r = p; + g = b; + bl = t; + break; + case 3: + r = p; + g = q; + bl = b; + break; + case 4: + r = t; + g = p; + bl = b; + break; + case 5: + r = b; + g = p; + bl = q; + break; + } + } + + return Color.FromArgb( + a, + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{r * 255D:0.00}")))), + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{g * 255D:0.00}")))), + SystemMath.Max(0, SystemMath.Min(255, Convert.ToInt32(double.Parse($"{bl * 250D:0.00}"))))); + } + + + /// + /// Multiplies the Color's Luminance or Brightness by the argument; + /// and optionally specifies the output Alpha. + /// + /// The color to transform. + /// Transform mode. + /// The transformation multiplier. + /// Can optionally specify the Alpha to directly + /// set on the output. If null, then the input + /// Alpha is used. + public static Color TransformBrightness( + Color color, + ColorTransformMode colorTransformMode, + double brightnessTransform, + byte? outputAlpha = null) + { + double[] hsl = colorTransformMode == ColorTransformMode.Hsl + ? SimpleColorTransforms.RgBtoHsl(color) + : SimpleColorTransforms.RgBtoHsb(color); + if ((SystemMath.Abs(hsl[2]) < SimpleColorTransforms.tolerance) + && (brightnessTransform > 1D)) + hsl[2] = brightnessTransform - 1D; + else + hsl[2] *= brightnessTransform; + return colorTransformMode == ColorTransformMode.Hsl + ? SimpleColorTransforms.HsLtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A) + : SimpleColorTransforms.HsBtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A); + } + + + /// + /// Multiplies the Color's Saturation, and Luminance or Brightness by the argument; + /// and optionally specifies the output Alpha. + /// + /// The color to transform. + /// Transform mode. + /// The transformation multiplier. + /// The transformation multiplier. + /// Can optionally specify the Alpha to directly + /// set on the output. If null, then the input + /// Alpha is used. + public static Color TransformSaturationAndBrightness( + Color color, + ColorTransformMode colorTransformMode, + double saturationTransform, + double brightnessTransform, + byte? outputAlpha = null) + { + double[] hsl = colorTransformMode == ColorTransformMode.Hsl + ? SimpleColorTransforms.RgBtoHsl(color) + : SimpleColorTransforms.RgBtoHsb(color); + if ((SystemMath.Abs(hsl[1]) < SimpleColorTransforms.tolerance) + && (saturationTransform > 1D)) + hsl[1] = saturationTransform - 1D; + else + hsl[1] *= saturationTransform; + if ((SystemMath.Abs(hsl[2]) < SimpleColorTransforms.tolerance) + && (brightnessTransform > 1D)) + hsl[2] = brightnessTransform - 1D; + else + hsl[2] *= brightnessTransform; + return colorTransformMode == ColorTransformMode.Hsl + ? SimpleColorTransforms.HsLtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A) + : SimpleColorTransforms.HsBtoRgb(hsl[0], hsl[1], hsl[2], outputAlpha ?? color.A); + } + + + /// + /// Creates a new Color by combining R, G, and B from each Color, scaled by the Color's Alpha. + /// The R, G, B of each Color is scaled by the Color's Alpha. The R, G, B of both results is + /// then added together and divided by 2. The valuea are limited to [0, 255]. + /// The Alpha of the output Color is specified; and is also limited to [0, 255] + /// (does not raise exceptions). + /// + /// Combined by scaling RGB by the A. + /// Combined by scaling RGB by the A. + /// The Alpha of the output Color. + public static Color AlphaCombine(Color color1, Color color2, byte outputAlpha) + { + double a1 = color1.A / 255D; + double a2 = color2.A / 255D; + return Color.FromArgb( + outputAlpha, + (byte)SystemMath.Max(0D, SystemMath.Min(255D, ((color1.R * a1) + (color2.R * a2)) * .5D)), + (byte)SystemMath.Max(0D, SystemMath.Min(255D, ((color1.G * a1) + (color2.G * a2)) * .5D)), + (byte)SystemMath.Max(0D, SystemMath.Min(255D, ((color1.B * a1) + (color2.B * a2)) * .5D))); + } + + } +}