├── .github
├── FUNDING.yml
├── workflows
│ ├── Windows.runsettings
│ └── build.yaml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── CODE_OF_CONDUCT.md
└── CONTRIBUTING.md
├── art
└── screenshot.png
├── src
├── Resources
│ └── Icon.png
├── source.extension.vsixmanifest
├── Adornments
│ ├── ColorAdornmentTagger.cs
│ ├── ColorTag.cs
│ ├── ColorAdornment.cs
│ ├── ColorConvertion.cs
│ └── ColorUtils.cs
├── VSCommandTable.vsct
├── Properties
│ └── AssemblyInfo.cs
├── VSCommandTable.cs
├── EditorColorPreviewPackage.cs
├── source.extension.cs
└── EditorColorPreview.csproj
├── vs-publish.json
├── test
└── EditorColorPreview.Test
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── CssColorThree.cs
│ ├── HexColors.cs
│ ├── HtmlMatchTests.cs
│ ├── EditorColorPreview.Test.csproj
│ └── CssColorFour.cs
├── .gitattributes
├── EditorColorPreview.sln
├── README.md
├── .editorconfig
├── .gitignore
└── LICENSE
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: madskristensen
4 |
--------------------------------------------------------------------------------
/art/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/EditorColorPreview/master/art/screenshot.png
--------------------------------------------------------------------------------
/src/Resources/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/EditorColorPreview/master/src/Resources/Icon.png
--------------------------------------------------------------------------------
/src/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/EditorColorPreview/master/src/source.extension.vsixmanifest
--------------------------------------------------------------------------------
/src/Adornments/ColorAdornmentTagger.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/EditorColorPreview/master/src/Adornments/ColorAdornmentTagger.cs
--------------------------------------------------------------------------------
/src/Adornments/ColorTag.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text.Tagging;
2 | using System.Windows.Media;
3 |
4 | namespace EditorColorPreview
5 | {
6 | internal class ColorTag : ITag
7 | {
8 | internal Color Color { get; }
9 |
10 | internal ColorTag(Color color)
11 | {
12 | Color = color;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/vs-publish.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/vsix-publish",
3 | "categories": [ "web" ],
4 | "identity": {
5 | "internalName": "ColorPreview",
6 | "tags": [ "color", "css", "rgb", "hex", "hsl" ]
7 | },
8 | "assetFiles": [
9 | {
10 | "pathOnDisk": "art/screenshot.png",
11 | "targetPath": "art/screenshot.png"
12 | }
13 | ],
14 | "overview": "README.md",
15 | "publisher": "MadsKristensen",
16 | "repo": "https://github.com/MadsKristensen/EditorColorPreview"
17 | }
--------------------------------------------------------------------------------
/src/VSCommandTable.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.github/workflows/Windows.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | %GITHUB_WORKSPACE%
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/src/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using EditorColorPreview;
2 |
3 | using System.Reflection;
4 | using System.Runtime.InteropServices;
5 |
6 | [assembly: AssemblyTitle(Vsix.Name)]
7 | [assembly: AssemblyDescription(Vsix.Description)]
8 | [assembly: AssemblyConfiguration("")]
9 | [assembly: AssemblyCompany(Vsix.Author)]
10 | [assembly: AssemblyProduct(Vsix.Name)]
11 | [assembly: AssemblyCopyright(Vsix.Author)]
12 | [assembly: AssemblyTrademark("")]
13 | [assembly: AssemblyCulture("")]
14 |
15 | [assembly: ComVisible(false)]
16 |
17 | [assembly: AssemblyVersion(Vsix.Version)]
18 | [assembly: AssemblyFileVersion(Vsix.Version)]
19 |
20 | namespace System.Runtime.CompilerServices
21 | {
22 | public class IsExternalInit { }
23 | }
--------------------------------------------------------------------------------
/test/EditorColorPreview.Test/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("EditorColorPreview.Test")]
6 | [assembly: AssemblyDescription("")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("")]
9 | [assembly: AssemblyProduct("EditorColorPreview.Test")]
10 | [assembly: AssemblyCopyright("Copyright © 2022")]
11 | [assembly: AssemblyTrademark("")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 |
16 | [assembly: Guid("198e2552-b2b2-437e-8a9b-16ca9dfdc372")]
17 |
18 | // [assembly: AssemblyVersion("1.0.*")]
19 | [assembly: AssemblyVersion("1.0.0.0")]
20 | [assembly: AssemblyFileVersion("1.0.0.0")]
21 |
--------------------------------------------------------------------------------
/src/VSCommandTable.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | //
3 | // This file was generated by the free extension VSIX Synchronizer
4 | //
5 | // ------------------------------------------------------------------------------
6 | using System;
7 |
8 | namespace EditorColorPreview
9 | {
10 | ///
11 | /// Helper class that exposes all GUIDs used across VS Package.
12 | ///
13 | internal sealed partial class PackageGuids
14 | {
15 | public const string EditorColorPreviewString = "cc9345da-4349-4929-b8f8-bf84749143ef";
16 | public static Guid EditorColorPreview = new Guid(EditorColorPreviewString);
17 | }
18 | ///
19 | /// Helper class that encapsulates all CommandIDs uses across VS Package.
20 | ///
21 | internal sealed partial class PackageIds
22 | {
23 | }
24 | }
--------------------------------------------------------------------------------
/src/EditorColorPreviewPackage.cs:
--------------------------------------------------------------------------------
1 | global using Community.VisualStudio.Toolkit;
2 |
3 | global using Microsoft.VisualStudio.Shell;
4 |
5 | global using System;
6 |
7 | global using Task = System.Threading.Tasks.Task;
8 |
9 | using System.Runtime.InteropServices;
10 | using System.Threading;
11 |
12 | namespace EditorColorPreview
13 | {
14 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
15 | [InstalledProductRegistration(Vsix.Name, Vsix.Description, Vsix.Version)]
16 | [ProvideMenuResource("Menus.ctmenu", 1)]
17 | [Guid(PackageGuids.EditorColorPreviewString)]
18 | public sealed class EditorColorPreviewPackage : ToolkitPackage
19 | {
20 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
21 | {
22 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/source.extension.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | //
3 | // This file was generated by VSIX Synchronizer 1.0.45
4 | // Available from https://marketplace.visualstudio.com/items?itemName=MadsKristensen.VsixSynchronizer64
5 | //
6 | // ------------------------------------------------------------------------------
7 | namespace EditorColorPreview
8 | {
9 | internal sealed partial class Vsix
10 | {
11 | public const string Id = "EditorColorPreview.06059b78-ceae-4188-905d-be8877234e35";
12 | public const string Name = "Color Preview";
13 | public const string Description = @"Shows a color preview in front of all named colors, hex, rgb and hsl values in CSS files.";
14 | public const string Language = "en-US";
15 | public const string Version = "1.0.1";
16 | public const string Author = "Mads Kristensen";
17 | public const string Tags = "color, css, hsl, rgb, hex";
18 | public const bool IsPreview = false;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/EditorColorPreview.Test/CssColorThree.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using System.Drawing;
3 |
4 | namespace EditorColorPreview.Test
5 | {
6 | [TestClass]
7 | public class CssColorThree
8 | {
9 | [DataTestMethod]
10 | [DataRow("hsl(120, 30%, 50%)", "rgb(89, 166, 89)")]
11 | [DataRow("hsl(120, 30%, 50%, 0.5)", "rgba(89, 166, 89, 0.5)")]
12 | [DataRow("hsl(0, 0%, 0%)", "rgb(0, 0, 0)")]
13 | [DataRow("hsl(0, 0%, 0%, 0)", "rgba(0, 0, 0, 0)")]
14 | [DataRow("hsla(0, 0%, 0%)", "rgb(0, 0, 0)")]
15 | [DataRow("hsla(0, 0%, 0%, 0)", "rgba(0, 0, 0, 0)")]
16 | [DataRow("hsl(120, 0%, 0%)", "rgb(0, 0, 0)")]
17 | [DataRow("hsl(120, 80%, 0%)", "rgb(0, 0, 0)")]
18 | [DataRow("hsl(120, 0%, 50%)", "rgb(128, 128, 128)")]
19 | [DataRow("hsl(120, 100%, 50%, 0)", "rgba(0, 255, 0, 0)")]
20 | [DataRow("hsl(0, 100%, 50%)", "rgb(255, 0, 0)")]
21 | public void HSLA_Test(string testHtml, string expectedHtml)
22 | {
23 | Color actual = ColorUtils.HtmlToColor(testHtml);
24 |
25 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
26 |
27 | Assert.AreEqual(expected, actual);
28 | }
29 |
30 | [DataTestMethod]
31 | [DataRow("rgb(-2, 3, 4)", "rgb(0, 3, 4)")]
32 | [DataRow("rgb(100, 200, 300)", "rgb(100, 200, 255)")]
33 | [DataRow("rgb(20, 10, 0, -10)", "rgba(20, 10, 0, 0)")]
34 | [DataRow("rgb(100%, 200%, 300%)", "rgb(255, 255, 255)")]
35 | [DataRow("rgb(2, 3, 4)", "rgb(2, 3, 4)")]
36 | [DataRow("rgb(100%, 0%, 0%)", "rgb(255, 0, 0)")]
37 | [DataRow("rgba(2, 3, 4, 0.5)", "rgba(2, 3, 4, 0.5)")]
38 | [DataRow("rgba(2, 3, 4, 50%)", "rgba(2, 3, 4, 0.5)")]
39 | public void RGA_Test(string testHtml, string expectedHtml)
40 | {
41 | Color actual = ColorUtils.HtmlToColor(testHtml);
42 |
43 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
44 |
45 | Assert.AreEqual(expected, actual);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Adornments/ColorAdornment.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Input;
4 | using System.Windows.Media;
5 | using Microsoft.VisualStudio;
6 | using Microsoft.VisualStudio.Shell.Interop;
7 | using Microsoft.VisualStudio.Text.Editor;
8 |
9 | namespace EditorColorPreview
10 | {
11 | internal sealed class ColorAdornment : Border
12 | {
13 | private static readonly SolidColorBrush _borderColor = (SolidColorBrush)Application.Current.Resources[VsBrushes.CaptionTextKey];
14 | private readonly ITextView _view;
15 |
16 | internal ColorAdornment(Color color, ITextView view)
17 | {
18 | ThreadHelper.ThrowIfNotOnUIThread();
19 | _view = view;
20 |
21 | Padding = new Thickness(0);
22 | BorderThickness = new Thickness(1);
23 | BorderBrush = _borderColor;
24 | Margin = new Thickness(0, 0, 2, 3);
25 | Width = GetFontSize() + 2;
26 | Height = Width;
27 | Cursor = Cursors.Arrow;
28 |
29 | Update(color);
30 | }
31 |
32 | protected override void OnMouseUp(MouseButtonEventArgs e)
33 | {
34 | _view.Caret.MoveToNextCaretPosition();
35 | VS.Commands.ExecuteAsync("Edit.ListMembers").FireAndForget();
36 | e.Handled = true;
37 | }
38 |
39 | internal void Update(Color color)
40 | {
41 | Background = new SolidColorBrush(color);
42 | }
43 |
44 | private static int GetFontSize()
45 | {
46 | ThreadHelper.ThrowIfNotOnUIThread();
47 |
48 | try
49 | {
50 | IVsFontAndColorStorage storage = (IVsFontAndColorStorage)Package.GetGlobalService(typeof(IVsFontAndColorStorage));
51 | Guid guid = new("A27B4E24-A735-4d1d-B8E7-9716E1E3D8E0");
52 | if (storage != null && storage.OpenCategory(ref guid, (uint)(__FCSTORAGEFLAGS.FCSF_READONLY | __FCSTORAGEFLAGS.FCSF_LOADDEFAULTS)) == VSConstants.S_OK)
53 | {
54 | LOGFONTW[] Fnt = new LOGFONTW[] { new() };
55 | FontInfo[] Info = new FontInfo[] { new() };
56 | storage.GetFont(Fnt, Info);
57 | return Info[0].wPointSize;
58 | }
59 |
60 | }
61 | catch { }
62 |
63 | return 0;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/test/EditorColorPreview.Test/HexColors.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 |
4 | namespace EditorColorPreview.Test
5 | {
6 | [TestClass]
7 | public class HexColors
8 | {
9 | [DataTestMethod]
10 | [DataRow("#000000", "rgb(0,0,0)")]
11 | [DataRow("#FFFFFF", "rgb(255,255,255)")]
12 | [DataRow("#FF0000", "rgb(255,0,0)")]
13 | [DataRow("#00FF00", "rgb(0,255,0)")]
14 | [DataRow("#0000FF", "rgb(0,0,255)")]
15 | [DataRow("#FFFF00", "rgb(255,255,0)")]
16 | [DataRow("#00FFFF", "rgb(0,255,255)")]
17 | [DataRow("#FF00FF", "rgb(255,0,255)")]
18 | [DataRow("#C0C0C0", "rgb(192,192,192)")]
19 | [DataRow("#808080", "rgb(128,128,128)")]
20 | [DataRow("#800000", "rgb(128,0,0)")]
21 | [DataRow("#808000", "rgb(128,128,0)")]
22 | [DataRow("#008000", "rgb(0,128,0)")]
23 | [DataRow("#800080", "rgb(128,0,128)")]
24 | [DataRow("#008080", "rgb(0,128,128)")]
25 | [DataRow("#000080", "rgb(0,0,128)")]
26 | [DataRow("#000", "rgb(0,0,0)")]
27 | [DataRow("#FFF", "rgb(255,255,255)")]
28 | [DataRow("#F00", "rgb(255,0,0)")]
29 | [DataRow("#0F0", "rgb(0,255,0)")]
30 | [DataRow("#00F", "rgb(0,0,255)")]
31 | [DataRow("#FF0000FF", "rgba(255, 0, 0, 1.00)")]
32 | [DataRow("#FF0000F2", "rgba(255, 0, 0, .95)")]
33 | [DataRow("#FF0000E6", "rgba(255, 0, 0, .90)")]
34 | [DataRow("#FF0000D9", "rgba(255, 0, 0, .85)")]
35 | [DataRow("#FF0000CC", "rgba(255, 0, 0, .80)")]
36 | [DataRow("#FF0000BF", "rgba(255, 0, 0, .75)")]
37 | [DataRow("#FF0000B3", "rgba(255, 0, 0, .70)")]
38 | [DataRow("#FF000099", "rgba(255, 0, 0, .60)")]
39 | [DataRow("#FF00008C", "rgba(255, 0, 0, .55)")]
40 | [DataRow("#FF000080", "rgba(255, 0, 0, .50)")]
41 | [DataRow("#FF000073", "rgba(255, 0, 0, .45)")]
42 | [DataRow("#FF000066", "rgba(255, 0, 0, .40)")]
43 | [DataRow("#FF000059", "rgba(255, 0, 0, .35)")]
44 | [DataRow("#FF00004D", "rgba(255, 0, 0, .30)")]
45 | [DataRow("#FF000040", "rgba(255, 0, 0, .25)")]
46 | [DataRow("#FF000033", "rgba(255, 0, 0, .20)")]
47 | [DataRow("#FF000026", "rgba(255, 0, 0, .15)")]
48 | [DataRow("#FF00001A", "rgba(255, 0, 0, .10)")]
49 | [DataRow("#FF00000D", "rgba(255, 0, 0, .05)")]
50 | [DataRow("#FF000000", "rgba(255, 0, 0, .0)")]
51 | public void Test_RGB_Hex(string testHtml, string expectedHtml)
52 | {
53 | Color actual = ColorUtils.HtmlToColor(testHtml);
54 |
55 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
56 |
57 | Assert.AreEqual(expected, actual);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/EditorColorPreview.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32206.53
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorColorPreview", "src\EditorColorPreview.csproj", "{D06E0E89-FA50-4CA2-8799-692039708231}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C5F84671-A5A4-44DE-90ED-AE41D99FABD1}"
9 | ProjectSection(SolutionItems) = preProject
10 | .github\workflows\build.yaml = .github\workflows\build.yaml
11 | README.md = README.md
12 | vs-publish.json = vs-publish.json
13 | EndProjectSection
14 | EndProject
15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorColorPreview.Test", "test\EditorColorPreview.Test\EditorColorPreview.Test.csproj", "{198E2552-B2B2-437E-8A9B-16CA9DFDC372}"
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Debug|x86 = Debug|x86
21 | Release|Any CPU = Release|Any CPU
22 | Release|x86 = Release|x86
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {D06E0E89-FA50-4CA2-8799-692039708231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {D06E0E89-FA50-4CA2-8799-692039708231}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {D06E0E89-FA50-4CA2-8799-692039708231}.Debug|x86.ActiveCfg = Debug|x86
28 | {D06E0E89-FA50-4CA2-8799-692039708231}.Debug|x86.Build.0 = Debug|x86
29 | {D06E0E89-FA50-4CA2-8799-692039708231}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {D06E0E89-FA50-4CA2-8799-692039708231}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {D06E0E89-FA50-4CA2-8799-692039708231}.Release|x86.ActiveCfg = Release|x86
32 | {D06E0E89-FA50-4CA2-8799-692039708231}.Release|x86.Build.0 = Release|x86
33 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Debug|x86.ActiveCfg = Debug|Any CPU
36 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Debug|x86.Build.0 = Debug|Any CPU
37 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Release|x86.ActiveCfg = Release|Any CPU
40 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}.Release|x86.Build.0 = Release|Any CPU
41 | EndGlobalSection
42 | GlobalSection(SolutionProperties) = preSolution
43 | HideSolutionNode = FALSE
44 | EndGlobalSection
45 | GlobalSection(ExtensibilityGlobals) = postSolution
46 | SolutionGuid = {0B58B8B8-1B82-4E66-B833-590313C0B13F}
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/test/EditorColorPreview.Test/HtmlMatchTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.RegularExpressions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 |
5 | namespace EditorColorPreview.Test
6 | {
7 | [TestClass]
8 | public class HtmlMatchTests
9 | {
10 | [DataTestMethod]
11 | [DataRow("color: rgb(0, 0, 0)")]
12 | [DataRow("color: rgba(0,0,4,0.82);")]
13 | [DataRow("color: rgba(0 0 4 0.82);")]
14 | [DataRow("color: hsl(120 30% 50%)")]
15 | [DataRow("color: hsl(120 30% 50% / 0.5)")]
16 | [DataRow("color: hsl(none none none)")]
17 | [DataRow("color: hsl(0 0% 0%)")]
18 | [DataRow("$sass-color: hsl(0 0% 0%)")]
19 | [DataRow("color: lab(29.69% 44.888% -29.04%)")]
20 | [DataRow("color: lab(67.5345% -8.6911 -41.6019)")]
21 | [DataRow("color: color(rec2020 0.42053 0.979780 0.00579);")]
22 | [DataRow("color: color(display-p3 -0.6112 1.0079 -0.2192);")]
23 | [DataRow("color: color(srgb 0.691 0.139 0.259)")]
24 | [DataRow("color: color(srgb-linear 0.435 0.017 0.055)")]
25 | [DataRow("background-color: color(xyz-d50 0.2005 0.14089 0.4472)")]
26 | [DataRow("background-color: color(xyz-d65 0.21661 0.14602 0.59452)")]
27 | [DataRow("background-color: color(xyz 0.26567 0.69174 0.04511)")]
28 | [DataRow("color: oklch(80% 0.15 160)")]
29 | [DataRow("color: oklch(55% 0.15 345)")]
30 | [DataRow("color: oklch(84.883% 0.36853 145.645)")]
31 | [DataRow("color: oklab(70% 0 0.125)")]
32 | [DataRow("color: oklab(55% 0 -0.2)")]
33 | [DataRow("color: oklab(84.883% -0.3042 0.20797)")]
34 | [DataRow("color: oklch(53.85% .1725 320.67 / .7);")]
35 | public void HtmlMatches_Should_Match(string htmlString)
36 | {
37 | MatchCollection matches = ColorUtils.MatchesColor(htmlString);
38 |
39 | Assert.AreEqual(1, matches.Count);
40 | }
41 |
42 | [DataTestMethod]
43 | [DataRow("color: rgb(0, 0)")]
44 | [DataRow("color: hsx(120 30% 50%)")]
45 | [DataRow("color: hsl(120 30% 50% / 50%)")]
46 | //[DataRow("color: hsl(120 30% / 0.5)")]
47 | [DataRow("background-color: color(profoto-rgb 0.4835 0.9167 0.2188)")]
48 | public void HtmlMatches_Should_Not_Match(string htmlString)
49 | {
50 | MatchCollection matches = ColorUtils.MatchesColor(htmlString);
51 |
52 | Assert.AreEqual(0, matches.Count);
53 | }
54 |
55 | [TestMethod]
56 | public void Multiple_Single_Line_Should_Be_Two()
57 | {
58 | MatchCollection matches = ColorUtils.MatchesColor("$sass-color: hsl(0 0% 0%) color: hsl(0 0% 0%)");
59 |
60 | Assert.AreEqual(2, matches.Count);
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
2 | name: "Build"
3 | permissions:
4 | actions: write
5 | contents: write
6 |
7 | on:
8 | push:
9 | branches: [master]
10 | pull_request:
11 | branches: [master]
12 | workflow_dispatch:
13 |
14 | jobs:
15 | build:
16 | outputs:
17 | version: ${{ steps.vsix_version.outputs.version-number }}
18 | name: Build
19 | runs-on: windows-latest
20 | env:
21 | Configuration: Release
22 | DeployExtension: False
23 | VsixManifestPath: src\source.extension.vsixmanifest
24 | VsixManifestSourcePath: src\source.extension.cs
25 |
26 | steps:
27 | - uses: actions/checkout@v4
28 |
29 | - name: Setup .NET build dependencies
30 | uses: timheuer/bootstrap-dotnet@v1
31 | with:
32 | nuget: 'false'
33 | sdk: 'false'
34 | msbuild: 'true'
35 |
36 | - name: Increment VSIX version
37 | id: vsix_version
38 | uses: timheuer/vsix-version-stamp@v2
39 | with:
40 | manifest-file: ${{ env.VsixManifestPath }}
41 | vsix-token-source-file: ${{ env.VsixManifestSourcePath }}
42 |
43 | - name: Build
44 | run: msbuild /v:m -restore /p:OutDir=\_built
45 |
46 | - name: Setup test
47 | uses: darenm/Setup-VSTest@v1.2
48 |
49 | - name: Test
50 | run: vstest.console.exe \_built\*Test.dll
51 |
52 | - name: Upload artifact
53 | uses: actions/upload-artifact@v4
54 | with:
55 | name: ${{ github.event.repository.name }}.vsix
56 | path: /_built/**/*.vsix
57 |
58 | publish:
59 | if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
60 | needs: build
61 | runs-on: windows-latest
62 |
63 | steps:
64 | - uses: actions/checkout@v4
65 |
66 | - name: Download Package artifact
67 | uses: actions/download-artifact@v4
68 | with:
69 | name: ${{ github.event.repository.name }}.vsix
70 |
71 | - name: Upload to Open VSIX
72 | uses: timheuer/openvsixpublish@v1
73 | with:
74 | vsix-file: ${{ github.event.repository.name }}.vsix
75 |
76 | - name: Publish extension to Marketplace
77 | if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[release]') }}
78 | uses: cezarypiatek/VsixPublisherAction@1.0
79 | with:
80 | extension-file: '${{ github.event.repository.name }}.vsix'
81 | publish-manifest-file: 'vs-publish.json'
82 | personal-access-code: ${{ secrets.VS_PUBLISHER_ACCESS_TOKEN }}
83 |
84 | - name: Tag and release
85 | if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[release]') }}
86 | id: tag_release
87 | uses: softprops/action-gh-release@v2
88 | with:
89 | body: release ${{ needs.build.outputs.version }}
90 | generate_release_notes: true
91 | tag_name: ${{ needs.build.outputs.version }}
92 | files: |
93 | **/*.vsix
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [marketplace]: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ColorPreview
2 | [vsixgallery]: http://vsixgallery.com/extension/EditorColorPreview.06059b78-ceae-4188-905d-be8877234e35/
3 | [repo]: https://github.com/madskristensen/EditorColorPreview
4 |
5 | # Color Preview for Visual Studio
6 |
7 | [](https://github.com/madskristensen/EditorColorPreview/actions/workflows/build.yaml)
8 |
9 | Download this extension from the [Visual Studio Marketplace][marketplace]
10 | or get the [CI build][vsixgallery].
11 |
12 | ---
13 |
14 | Shows a color preview in front of all named colors, hex, rgb and hsl values in CSS and JavaScript files.
15 |
16 | 
17 | **\*Figure 1**: Color preview in light theme and dark theme\*
18 |
19 | ## Supported colors
20 |
21 | These color formats are supported:
22 |
23 | - Named colors (e.g. `blue`)
24 | - Hex 3 digits (e.g. `#ff0`)
25 | - Hex 6 digits (e.g. `#ffff00`)
26 | - Hex 8 digits (e.g. `#ffff00cc`)
27 | - RGB
28 | - `rgb(255, 165, 0)`
29 | - `rgb(0% 50% 0%)`
30 | - `rgb(0 128.0 0)`
31 | - `rgb(0% 50% 0% / 0.25)` (Alpha channel)
32 | - RGBA (e.g.
33 | - `rgba(255, 165, 0)`
34 | - `rgba(0% 50% 0%)`
35 | - `rgba(0 128.0 0)`
36 | - `rgba(0% 50% 0% / 0.25)`
37 | - HSL
38 | - `hsl(9, 100%, 64%)`
39 | - `hsl(120 100% 25%)`
40 | - `hsl(120deg 100% 25%)`
41 | - `hsl(120 100% 25% / 0.25)`
42 | - `hsl(120 none none)`
43 | - HSLA
44 | - `hsla(9, 100%, 64%, 0.7)`
45 | - `hsla(120 100% 25%)`
46 | - `hsla(120deg 100% 25%)`
47 | - `hsla(120 100% 25% / 0.25)`
48 | - `hsla(120 none none)`
49 | - HWB
50 | - `hwb(120 0% 49.8039%)`
51 | - `hwb(0 0% 100%)`
52 | - `hwb(0 100% 100%)`
53 | - `hwb(120 30% 50% / 0.5)`
54 | - `hwb(none none none)`
55 | - Lab (Colors are converted to sRGB. Some colors might not convert properly) [^1]
56 | - `lab(46.2775% -47.5621 48.5837)`
57 | - `lab(100% 0 0)`
58 | - `lab(70% -45 0)`
59 | - `lab(86.6146% -106.5599 102.8717)`
60 | - LCH (Colors are converted to sRGB. Some colors might not convert properly) [^1]
61 | - `lch(46.2775% 67.9892 134.3912)`
62 | - `lch(0% 0 0)`
63 | - `lch(50% 50 0)`
64 | - `lch(70% 45 -180)`
65 | - OKLab (Colors are converted to sRGB. Some colors might not convert properly) [^1]
66 | - `oklab(51.975% -0.1403 0.10768)`
67 | - `oklab(0% 0 0)`
68 | - `oklab(100% 0 0)`
69 | - `oklab(50% 0.05 0)`
70 | - OKLCH (Colors are converted to sRGB. Some colors might not convert properly) [^1]
71 | - `oklch(51.975% 0.17686 142.495)`
72 | - `oklch(0% 0 0)`
73 | - `oklch(100% 0 0)`
74 | - `oklch(50% 0.2 0)`
75 |
76 | [^1]: A color may be a valid color but still be outside the range of colors that can be produced by an output device (a screen, projector, or printer). It is said to be out of gamut for that color space.
77 |
78 | ## How can I help?
79 |
80 | If you enjoy using the extension, please give it a ★★★★★ rating on the [Visual Studio Marketplace][marketplace].
81 |
82 | Should you encounter bugs or if you have feature requests, head on over to the [GitHub repo][repo] to open an issue if one doesn't already exist.
83 |
84 | Pull requests are also very welcome, since I can't always get around to fixing all bugs myself. This is a personal passion project, so my time is limited.
85 |
86 | Another way to help out is to [sponsor me on GitHub](https://github.com/sponsors/madskristensen).
--------------------------------------------------------------------------------
/test/EditorColorPreview.Test/EditorColorPreview.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {198E2552-B2B2-437E-8A9B-16CA9DFDC372}
8 | Library
9 | Properties
10 | EditorColorPreview.Test
11 | EditorColorPreview.Test
12 | v4.8
13 | 512
14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 15.0
16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
18 | False
19 | UnitTest
20 |
21 |
22 |
23 |
24 |
25 | true
26 | full
27 | false
28 | bin\Debug\
29 | DEBUG;TRACE
30 | prompt
31 | 4
32 |
33 |
34 | pdbonly
35 | true
36 | bin\Release\
37 | TRACE
38 | prompt
39 | 4
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | {d06e0e89-fa50-4ca2-8799-692039708231}
56 | EditorColorPreview
57 |
58 |
59 |
60 |
61 | 2.2.10
62 |
63 |
64 | 2.2.10
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome:http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Don't use tabs for indentation.
7 | [*]
8 | indent_style = space
9 | end_of_line = crlf
10 | # (Please don't specify an indent_size here; that has too many unintended consequences.)
11 |
12 | # Code files
13 | [*.{cs,csx,vb,vbx,xaml}]
14 | indent_size = 4
15 |
16 | # Xml project files
17 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
18 | indent_size = 2
19 |
20 | # Xml config files
21 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
22 | indent_size = 2
23 |
24 | # JSON files
25 | [*.json]
26 | indent_size = 2
27 |
28 | # Dotnet code style settings:
29 | [*.{cs,vb}]
30 | # Sort using and Import directives with System.* appearing first
31 | dotnet_sort_system_directives_first = true
32 | dotnet_separate_import_directive_groups = false
33 |
34 | # Avoid "this." and "Me." if not necessary
35 | dotnet_style_qualification_for_field = false : suggestion
36 | dotnet_style_qualification_for_property = false : suggestion
37 | dotnet_style_qualification_for_method = false : suggestion
38 | dotnet_style_qualification_for_event = false : suggestion
39 |
40 | # Use language keywords instead of framework type names for type references
41 | dotnet_style_predefined_type_for_locals_parameters_members = true : suggestion
42 | dotnet_style_predefined_type_for_member_access = true : suggestion
43 |
44 | # Suggest more modern language features when available
45 | dotnet_style_object_initializer = true : suggestion
46 | dotnet_style_collection_initializer = true : suggestion
47 | dotnet_style_coalesce_expression = true : suggestion
48 | dotnet_style_null_propagation = true : suggestion
49 | dotnet_style_explicit_tuple_names = true : suggestion
50 |
51 | # Naming rules - async methods to be prefixed with Async
52 | dotnet_naming_rule.async_methods_must_end_with_async.severity = warning
53 | dotnet_naming_rule.async_methods_must_end_with_async.symbols = method_symbols
54 | dotnet_naming_rule.async_methods_must_end_with_async.style = end_in_async_style
55 |
56 | dotnet_naming_symbols.method_symbols.applicable_kinds = method
57 | dotnet_naming_symbols.method_symbols.required_modifiers = async
58 |
59 | dotnet_naming_style.end_in_async_style.capitalization = pascal_case
60 | dotnet_naming_style.end_in_async_style.required_suffix = Async
61 |
62 | # Naming rules - private fields must start with an underscore
63 | dotnet_naming_rule.field_must_start_with_underscore.severity = warning
64 | dotnet_naming_rule.field_must_start_with_underscore.symbols = private_fields
65 | dotnet_naming_rule.field_must_start_with_underscore.style = start_underscore_style
66 |
67 | dotnet_naming_symbols.private_fields.applicable_kinds = field
68 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private
69 |
70 | dotnet_naming_style.start_underscore_style.capitalization = camel_case
71 | dotnet_naming_style.start_underscore_style.required_prefix = _
72 |
73 | # CSharp code style settings:
74 | [*.cs]
75 | # Prefer "var" everywhere
76 | csharp_style_var_for_built_in_types =false:error
77 | csharp_style_var_when_type_is_apparent =false:error
78 | csharp_style_var_elsewhere =false:error
79 |
80 | # Prefer method-like constructs to have a block body
81 | csharp_style_expression_bodied_methods = false : none
82 | csharp_style_expression_bodied_constructors = false : none
83 | csharp_style_expression_bodied_operators = false : none
84 |
85 | # Prefer property-like constructs to have an expression-body
86 | csharp_style_expression_bodied_properties = true : none
87 | csharp_style_expression_bodied_indexers = true : none
88 | csharp_style_expression_bodied_accessors = true : none
89 |
90 | # Suggest more modern language features when available
91 | csharp_style_pattern_matching_over_is_with_cast_check = true : suggestion
92 | csharp_style_pattern_matching_over_as_with_null_check = true : suggestion
93 | csharp_style_inlined_variable_declaration = true : suggestion
94 | csharp_style_throw_expression = true : suggestion
95 | csharp_style_conditional_delegate_call = true : suggestion
96 |
97 | # Newline settings
98 | csharp_new_line_before_open_brace = all
99 | csharp_new_line_before_else = true
100 | csharp_new_line_before_catch = true
101 | csharp_new_line_before_finally = true
102 | csharp_new_line_before_members_in_object_initializers = true
103 | csharp_new_line_before_members_in_anonymous_types = true
104 |
105 | # IDE
106 | dotnet_diagnostic.IDE0058.severity = none
107 | dotnet_diagnostic.RS2008.severity = none # RS2008: Enable analyzer release tracking
108 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | @mkristensen on Twitter.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/src/EditorColorPreview.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
5 | latest
6 |
7 |
8 |
9 | Debug
10 | AnyCPU
11 | 2.0
12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
13 | {D06E0E89-FA50-4CA2-8799-692039708231}
14 | Library
15 | Properties
16 | EditorColorPreview
17 | EditorColorPreview
18 | v4.8
19 | true
20 | true
21 | true
22 | true
23 | false
24 | true
25 | true
26 | Program
27 | $(DevEnvDir)devenv.exe
28 | /rootsuffix Exp
29 |
30 |
31 | true
32 | full
33 | false
34 | bin\Debug\
35 | DEBUG;TRACE
36 | prompt
37 | 4
38 |
39 |
40 | pdbonly
41 | true
42 | bin\Release\
43 | TRACE
44 | prompt
45 | 4
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | True
57 | True
58 | source.extension.vsixmanifest
59 |
60 |
61 |
62 |
63 | Menus.ctmenu
64 | VsctGenerator
65 | VSCommandTable.cs
66 |
67 |
68 | True
69 | True
70 | VSCommandTable.vsct
71 |
72 |
73 |
74 |
75 | Resources\LICENSE
76 | true
77 |
78 |
79 | Designer
80 | VsixManifestGenerator
81 | source.extension.cs
82 |
83 |
84 | PreserveNewest
85 | true
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | 17.0.533
101 |
102 |
103 |
104 | runtime; build; native; contentfiles; analyzers; buildtransitive
105 | all
106 |
107 |
108 |
109 |
110 |
117 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Looking to contribute something? **Here's how you can help.**
4 |
5 | Please take a moment to review this document in order to make the contribution
6 | process easy and effective for everyone involved.
7 |
8 | Following these guidelines helps to communicate that you respect the time of
9 | the developers managing and developing this open source project. In return,
10 | they should reciprocate that respect in addressing your issue or assessing
11 | patches and features.
12 |
13 |
14 | ## Using the issue tracker
15 |
16 | The issue tracker is the preferred channel for [bug reports](#bug-reports),
17 | [features requests](#feature-requests) and
18 | [submitting pull requests](#pull-requests), but please respect the
19 | following restrictions:
20 |
21 | * Please **do not** use the issue tracker for personal support requests. Stack
22 | Overflow is a better place to get help.
23 |
24 | * Please **do not** derail or troll issues. Keep the discussion on topic and
25 | respect the opinions of others.
26 |
27 | * Please **do not** open issues or pull requests which *belongs to* third party
28 | components.
29 |
30 |
31 | ## Bug reports
32 |
33 | A bug is a _demonstrable problem_ that is caused by the code in the repository.
34 | Good bug reports are extremely helpful, so thanks!
35 |
36 | Guidelines for bug reports:
37 |
38 | 1. **Use the GitHub issue search** — check if the issue has already been
39 | reported.
40 |
41 | 2. **Check if the issue has been fixed** — try to reproduce it using the
42 | latest `master` or development branch in the repository.
43 |
44 | 3. **Isolate the problem** — ideally create an
45 | [SSCCE](http://www.sscce.org/) and a live example.
46 | Uploading the project on cloud storage (OneDrive, DropBox, et el.)
47 | or creating a sample GitHub repository is also helpful.
48 |
49 |
50 | A good bug report shouldn't leave others needing to chase you up for more
51 | information. Please try to be as detailed as possible in your report. What is
52 | your environment? What steps will reproduce the issue? What browser(s) and OS
53 | experience the problem? Do other browsers show the bug differently? What
54 | would you expect to be the outcome? All these details will help people to fix
55 | any potential bugs.
56 |
57 | Example:
58 |
59 | > Short and descriptive example bug report title
60 | >
61 | > A summary of the issue and the Visual Studio, browser, OS environments
62 | > in which it occurs. If suitable, include the steps required to reproduce the bug.
63 | >
64 | > 1. This is the first step
65 | > 2. This is the second step
66 | > 3. Further steps, etc.
67 | >
68 | > `` - a link to the project/file uploaded on cloud storage or other publicly accessible medium.
69 | >
70 | > Any other information you want to share that is relevant to the issue being
71 | > reported. This might include the lines of code that you have identified as
72 | > causing the bug, and potential solutions (and your opinions on their
73 | > merits).
74 |
75 |
76 | ## Feature requests
77 |
78 | Feature requests are welcome. But take a moment to find out whether your idea
79 | fits with the scope and aims of the project. It's up to *you* to make a strong
80 | case to convince the project's developers of the merits of this feature. Please
81 | provide as much detail and context as possible.
82 |
83 |
84 | ## Pull requests
85 |
86 | Good pull requests, patches, improvements and new features are a fantastic
87 | help. They should remain focused in scope and avoid containing unrelated
88 | commits.
89 |
90 | **Please ask first** before embarking on any significant pull request (e.g.
91 | implementing features, refactoring code, porting to a different language),
92 | otherwise you risk spending a lot of time working on something that the
93 | project's developers might not want to merge into the project.
94 |
95 | Please adhere to the [coding guidelines](#code-guidelines) used throughout the
96 | project (indentation, accurate comments, etc.) and any other requirements
97 | (such as test coverage).
98 |
99 | Adhering to the following process is the best way to get your work
100 | included in the project:
101 |
102 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
103 | and configure the remotes:
104 |
105 | ```bash
106 | # Clone your fork of the repo into the current directory
107 | git clone https://github.com//.git
108 | # Navigate to the newly cloned directory
109 | cd
110 | # Assign the original repo to a remote called "upstream"
111 | git remote add upstream https://github.com/madskristensen/.git
112 | ```
113 |
114 | 2. If you cloned a while ago, get the latest changes from upstream:
115 |
116 | ```bash
117 | git checkout master
118 | git pull upstream master
119 | ```
120 |
121 | 3. Create a new topic branch (off the main project development branch) to
122 | contain your feature, change, or fix:
123 |
124 | ```bash
125 | git checkout -b
126 | ```
127 |
128 | 4. Commit your changes in logical chunks. Please adhere to these [git commit
129 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
130 | or your code is unlikely be merged into the main project. Use Git's
131 | [interactive rebase](https://help.github.com/articles/interactive-rebase)
132 | feature to tidy up your commits before making them public. Also, prepend name of the feature
133 | to the commit message. For instance: "SCSS: Fixes compiler results for IFileListener.\nFixes `#123`"
134 |
135 | 5. Locally merge (or rebase) the upstream development branch into your topic branch:
136 |
137 | ```bash
138 | git pull [--rebase] upstream master
139 | ```
140 |
141 | 6. Push your topic branch up to your fork:
142 |
143 | ```bash
144 | git push origin
145 | ```
146 |
147 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
148 | with a clear title and description against the `master` branch.
149 |
150 |
151 | ## Code guidelines
152 |
153 | - Always use proper indentation.
154 | - In Visual Studio under `Tools > Options > Text Editor > C# > Advanced`, make sure
155 | `Place 'System' directives first when sorting usings` option is enabled (checked).
156 | - Before committing, organize usings for each updated C# source file. Either you can
157 | right-click editor and select `Organize Usings > Remove and sort` OR use extension
158 | like [BatchFormat](https://marketplace.visualstudio.com/items?itemName=vs-publisher-147549.BatchFormat).
159 | - Before committing, run Code Analysis in `Debug` configuration and follow the guidelines
160 | to fix CA issues. Code Analysis commits can be made separately.
161 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2020 Mads Kristensen
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/Adornments/ColorConvertion.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace EditorColorPreview
4 | {
5 | public class ColorConvertion
6 | {
7 | private static double[] _d50 = { 0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585 };
8 | private static double[] _d65 = { 0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290 };
9 |
10 | // Lab & LCH
11 |
12 | public static double[] LabToRgb(double l, double a, double b)
13 | {
14 | if (l == 0.0)
15 | {
16 | return new double[] { 0.0, 0.0, 0.0 };
17 | }
18 | double[] result = LabToXYZ(new double[] { l, a, b });
19 | result = D50ToD65(result);
20 | result = XyzToLinearRGB(result);
21 | return LinearRgbToRgb(result);
22 | }
23 |
24 | public static double[] LchToRgb(double l, double c, double h)
25 | {
26 | double[] results = LchToLab(new double[] { l, c, h });
27 | return LabToRgb(results[0], results[1], results[2]);
28 | }
29 |
30 | private static double[] LchToLab(double[] lch) =>
31 | new double[] { lch[0], lch[1] * Math.Cos(lch[2] * Math.PI / 108), lch[1] * Math.Sin(lch[2] * Math.PI / 180) };
32 |
33 | private static double[] LabToXYZ(double[] lab)
34 | {
35 | // Convert Lab to D50-adapted XYZ
36 | // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
37 | double κ = 24389 / 27; // 29^3/3^3
38 | double ε = 216 / 24389; // 6^3/29^3
39 | double[] f = new double[3];
40 |
41 | // compute f, starting with the luminance-related term
42 | f[1] = (lab[0] + 16) / 116;
43 | f[0] = lab[1] / 500 + f[1];
44 | f[2] = f[1] - lab[2] / 200;
45 |
46 | // compute xyz
47 | double[] xyz = {
48 | Math.Pow(f[0], 3) > ε ? Math.Pow(f[0], 3) : (116 * f[0] - 16) / κ,
49 | lab[0] > κ * ε ? Math.Pow((lab[0] + 16) / 116, 3) : lab[0] / κ,
50 | Math.Pow(f[2], 3) > ε ? Math.Pow(f[2], 3) : (116 * f[2] - 16) / κ
51 | };
52 |
53 | // Compute XYZ by scaling xyz by reference white
54 | return xyz.Select((value, i) => value * _d50[i]).ToArray();
55 | }
56 |
57 | // OKLab & OKLCH
58 |
59 | public static double[] OkLabToRgb(double l, double a, double b)
60 | {
61 | return OkLabToRgb(new double[] { l , a, b });
62 | }
63 |
64 | private static double[] OkLabToRgb(double[] okLab)
65 | {
66 | double[] results = OkLabToXyz(okLab);
67 | results = XyzToLinearRGB(results);
68 | return LinearRgbToRgb(results);
69 | }
70 |
71 | public static double[] OkLchToRgb(double l, double a, double b)
72 | {
73 | double[] results = OkLchToOkLab(new double[] { l, a, b });
74 | return OkLabToRgb(results);
75 | }
76 |
77 | private static double[] OkLabToXyz(double[] oKLab)
78 | {
79 | // Given OKLab, convert to XYZ relative to D65
80 | double[,] LMStoXYZ = {
81 | { 1.2268798733741557, -0.5578149965554813, 0.28139105017721583 },
82 | { -0.04057576262431372, 1.1122868293970594, -0.07171106666151701 },
83 | { -0.07637294974672142, -0.4214933239627914, 1.5869240244272418 }
84 | };
85 | double[,] OKLabtoLMS = {
86 | { 0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339 },
87 | { 1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402 },
88 | { 1.0000000546724109177, -0.089484182094965759684, -1.291485537864091739 },
89 | };
90 |
91 | double[] LMSnl = MultiplyMatrices(OKLabtoLMS, oKLab);
92 | LMSnl = LMSnl.Select(c => Math.Pow(c, 3)).ToArray();
93 | return MultiplyMatrices(LMStoXYZ, LMSnl);
94 | }
95 |
96 | private static double[] OkLchToOkLab(double[] okLch)
97 | {
98 | return new double[] {
99 | okLch[0], // L is still L
100 | okLch[1] * Math.Cos(okLch[2] * Math.PI / 180), // a
101 | okLch[1] * Math.Sin(okLch[2] * Math.PI / 180) // b
102 | };
103 | }
104 |
105 | // sRGB function
106 |
107 | public static double[] RgbToLinearRgb(double[] rgb)
108 | {
109 | // convert an array of sRGB values
110 | // where in-gamut values are in the range [0 - 1]
111 | // to linear light (un-companded) form.
112 | // https://en.wikipedia.org/wiki/SRGB
113 | // Extended transfer function:
114 | // for negative values, linear portion is extended on reflection of axis,
115 | // then reflected power function is used.
116 | return rgb.Select(val =>
117 | {
118 | double sign = val < 0 ? -1 : 1;
119 | double abs = Math.Abs(val);
120 |
121 | if (abs < 0.04045)
122 | {
123 | return val / 12.92;
124 | }
125 |
126 | return sign * (Math.Pow((abs + 0.055) / 1.055, 2.4));
127 | }).ToArray();
128 | }
129 |
130 | public static double[] LinearRgbToRgb(double[] RGB)
131 | {
132 | // convert an array of linear-light sRGB values in the range 0.0-1.0
133 | // to gamma corrected form
134 | // https://en.wikipedia.org/wiki/SRGB
135 | // Extended transfer function:
136 | // For negative values, linear portion extends on reflection
137 | // of axis, then uses reflected pow below that
138 | return RGB.Select(v =>
139 | {
140 | double sign = v < 0 ? -1 : 1;
141 | double abs = Math.Abs(v);
142 |
143 | if (abs > 0.0031308)
144 | {
145 | return sign * (1.055 * Math.Pow(abs, 1 / 2.4) - 0.055);
146 | }
147 |
148 | return 12.92 * v;
149 | }).ToArray();
150 | }
151 |
152 | private static double[] XyzToLinearRGB(double[] xyz)
153 | {
154 | // convert XYZ to linear-light sRGB
155 |
156 | double[,] m = {
157 | { 3.2409699419045226, -1.537383177570094, -0.4986107602930034 },
158 | { -0.9692436362808796, 1.8759675015077202, 0.04155505740717559 },
159 | { 0.05563007969699366, -0.20397695888897652, 1.056971514242878 },
160 | };
161 |
162 | return MultiplyMatrices(m, xyz);
163 | }
164 |
165 | // display-p3-related functions
166 |
167 | public static double[] DisplayP3ToRgb(double n1, double n2, double n3)
168 | {
169 | double[] results = RgbToLinearRgb(new double[] { n1, n2, n3 });
170 | results = LinearP3ToXyz(results);
171 | results = XyzToLinearRGB(results);
172 | return LinearRgbToRgb(results);
173 | }
174 |
175 | private static double[] LinearP3ToXyz(double[] rgb)
176 | {
177 | // convert an array of linear-light display-p3 values to CIE XYZ
178 | // using D65 (no chromatic adaptation)
179 | // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
180 | double[,] m = {
181 | { 0.4865709486482162, 0.26566769316909306, 0.1982172852343625 },
182 | { 0.2289745640697488, 0.6917385218365064, 0.079286914093745 },
183 | { 0.0000000000000000, 0.04511338185890264, 1.043944368900976 }
184 | };
185 | // 0 was computed as -3.972075516933488e-17
186 |
187 | return MultiplyMatrices(m, rgb);
188 | }
189 |
190 | // a98-rgb functions
191 |
192 | public static double[] A98RgbToRgb(double n1, double n2, double n3)
193 | {
194 | double[] results = A98rgbToLinear(new double[] { n1, n2, n3 });
195 | results = LinearA98RgbToXyz(results);
196 | results = XyzToLinearRGB(results);
197 | return LinearRgbToRgb(results);
198 | }
199 |
200 | private static double[] A98rgbToLinear(double[] rgb)
201 | {
202 | // convert an array of a98-rgb values in the range 0.0 - 1.0
203 | // to linear light (un-companded) form.
204 | // negative values are also now accepted
205 | return rgb.Select(val =>
206 | {
207 | double sign = val < 0 ? -1 : 1;
208 | double abs = Math.Abs(val);
209 |
210 | return sign * Math.Pow(abs, 563 / 256);
211 | }).ToArray();
212 | }
213 |
214 | private static double[] LinearA98RgbToXyz(double[] rgb)
215 | {
216 | // convert an array of linear-light a98-rgb values to CIE XYZ
217 | // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
218 | // has greater numerical precision than section 4.3.5.3 of
219 | // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
220 | // but the values below were calculated from first principles
221 | // from the chromaticity coordinates of R G B W
222 | // see matrixmaker.html
223 | double[,] m = {
224 | { 0.5766690429101305, 0.1855582379065463, 0.1882286462349947 },
225 | { 0.29734497525053605, 0.6273635662554661, 0.07529145849399788 },
226 | { 0.02703136138641234, 0.07068885253582723, 0.9913375368376388 }
227 | };
228 |
229 | return MultiplyMatrices(m, rgb);
230 | }
231 |
232 | // ProPhoto
233 |
234 | public static double[] ProPhotoToRgb(double n1, double n2, double n3)
235 | {
236 | double[] results = ProPhotoToLinear(new double[] { n1, n2, n3 });
237 | results = LinerProPhotoToXyz(results);
238 | results = XyzToLinearRGB(results);
239 | results = D50ToD65(results);
240 | return LinearRgbToRgb(results);
241 | }
242 |
243 | private static double[] ProPhotoToLinear(double[] rgb)
244 | {
245 | // convert an array of prophoto-rgb values
246 | // where in-gamut colors are in the range [0.0 - 1.0]
247 | // to linear light (un-companded) form.
248 | // Transfer curve is gamma 1.8 with a small linear portion
249 | // Extended transfer function
250 | double Et2 = 16 / 512;
251 | return rgb.Select(v => {
252 | double sign = v < 0 ? -1 : 1;
253 | double abs = Math.Abs(v);
254 |
255 | if (abs <= Et2)
256 | {
257 | return v / 16;
258 | }
259 |
260 | return sign * Math.Pow(v, 1.8);
261 | }).ToArray();
262 | }
263 |
264 | private static double[] LinerProPhotoToXyz(double[] rgb)
265 | {
266 | // convert an array of linear-light prophoto-rgb values to CIE XYZ
267 | // using D50 (so no chromatic adaptation needed afterwards)
268 | // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
269 | double[,] m = {
270 | { 0.7977604896723027, 0.13518583717574031, 0.0313493495815248 },
271 | { 0.2880711282292934, 0.7118432178101014, 0.00008565396060525902 },
272 | { 0.0, 0.0, 0.8251046025104601 }
273 | };
274 |
275 | return MultiplyMatrices(m, rgb);
276 | }
277 |
278 | // Rec. 2020
279 |
280 | public static double[] Rec2020ToRgb(double n1, double n2, double n3)
281 | {
282 | double[] results = Rec2020ToLinear(new double[] { n1, n2, n3 });
283 | results = LinearRec2020ToXyz(results);
284 | results = XyzToLinearRGB(results);
285 | return LinearRgbToRgb(results);
286 | }
287 |
288 | private static double[] Rec2020ToLinear(double[] rgb)
289 | {
290 | // convert an array of rec2020 RGB values in the range 0.0 - 1.0
291 | // to linear light (un-companded) form.
292 | // ITU-R BT.2020-2 p.4
293 |
294 | double α = 1.09929682680944;
295 | double β = 0.018053968510807;
296 |
297 | return rgb.Select(val =>
298 | {
299 | double sign = val < 0 ? -1 : 1;
300 | double abs = Math.Abs(val);
301 |
302 | if (abs < β * 4.5)
303 | {
304 | return val / 4.5;
305 | }
306 |
307 | return sign * (Math.Pow((abs + α - 1) / α, 1 / 0.45));
308 | }).ToArray();
309 | }
310 |
311 | private static double[] LinearRec2020ToXyz(double[] rgb)
312 | {
313 | // convert an array of linear-light rec2020 values to CIE XYZ
314 | // using D65 (no chromatic adaptation)
315 | // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
316 | double[,] m = {
317 | { 0.6369580483012914, 0.14461690358620832, 0.1688809751641721 },
318 | { 0.2627002120112671, 0.6779980715188708, 0.05930171646986196 },
319 | { 0.000000000000000, 0.028072693049087428, 1.060985057710791 }
320 | };
321 | // 0 is actually calculated as 4.994106574466076e-17
322 |
323 | return MultiplyMatrices(m, rgb);
324 | }
325 |
326 | // XYZ
327 |
328 | public static double[] XyzToRgb(double n1, double n2, double n3, bool covertWhitePoint = false)
329 | {
330 | double[] results;
331 | if (covertWhitePoint)
332 | {
333 | results = D50ToD65(new double[] { n1, n2, n3 });
334 | results = XyzToLinearRGB(results);
335 | }
336 | else
337 | {
338 | results = XyzToLinearRGB(new double[] { n1, n2, n3 });
339 | }
340 |
341 | return LinearRgbToRgb(results);
342 | }
343 |
344 |
345 | public double[] D65ToD50(double[] xyz)
346 | {
347 | // Bradford chromatic adaptation from D65 to D50
348 | // The matrix below is the result of three operations:
349 | // - convert from XYZ to retinal cone domain
350 | // - scale components from one reference white to another
351 | // - convert back to XYZ
352 | // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
353 | double[,] m = {
354 | { 1.0479298208405488, 0.022946793341019088, -0.05019222954313557 },
355 | { 0.029627815688159344, 0.990434484573249, -0.01707382502938514 },
356 | { -0.009243058152591178, 0.015055144896577895, 0.7518742899580008 }
357 | };
358 |
359 | return MultiplyMatrices(m, xyz);
360 | }
361 |
362 | private static double[] D50ToD65(double[] xyz)
363 | {
364 | // Bradford chromatic adaptation from D50 to D65
365 | double[,] M = {
366 | { 0.9554734527042182, -0.023098536874261423, 0.0632593086610217 },
367 | { -0.028369706963208136, 1.0099954580058226, 0.021041398966943008 },
368 | { 0.012314001688319899, -0.020507696433477912, 1.3303659366080753 }
369 | };
370 |
371 | return MultiplyMatrices(M, xyz);
372 | }
373 |
374 | // Matrix Math
375 |
376 | private static double[] MultiplyMatrices(double[,] m, double[] vector)
377 | {
378 | double c1 = m[0, 0] * vector[0]
379 | + m[0, 1] * vector[1]
380 | + m[0, 2] * vector[2];
381 | double c2 = m[1, 0] * vector[0]
382 | + m[1, 1] * vector[1]
383 | + m[1, 2] * vector[2];
384 | double c3 = m[2, 0] * vector[0]
385 | + m[2, 1] * vector[1]
386 | + m[2, 2] * vector[2];
387 |
388 | return new double[] { c1, c2, c3 };
389 | }
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/src/Adornments/ColorUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Linq;
3 | using System.Text.RegularExpressions;
4 |
5 |
6 | namespace EditorColorPreview
7 | {
8 |
9 | public static class ColorUtils
10 | {
11 | // Add timeout to prevent regex hangs - 100ms should be more than enough for normal cases
12 | public static readonly Regex _regex = new(@"(?<=.*:.*)(#(?:[0-9a-f]{2}){2,4}\b|(#[0-9a-f]{3})\b|\b(color|rgb|rgb|hsl|hwb|lab|lch|oklab|oklch)a?\(((srgb|srgb-linear|display-p3|a98-rgb|photo-rgb|rec2020|xyz-d50|xyz-d65|xyz)\s*)?((-?(\d*\.)?\d+(%|deg)?|none)(,|,\s*|\s*)){2}(-?(\d*\.)?\d+(%|deg)?|none){1}((,|,\s*|\s*)-?(\d*\.)?\d+(%|deg)?|none)?\s*[\/\d\.]*%?\s*([\d\.]*)*\)|(?<=[\s""'])(black|silver|gray|whitesmoke|maroon|red|purple|fuchsia|green|lime|olivedrab|yellow|navy|blue|teal|aquamarine|orange|aliceblue|antiquewhite|aqua|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|goldenrod|gold|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavenderblush|lavender|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olive|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato|transparent|turquoise|violet|wheat|white|yellowgreen|rebeccapurple)\b)", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(100));
13 |
14 | public static Color HtmlToColor(string htmlColor)
15 | {
16 | try
17 | {
18 | // RGB
19 | if (htmlColor.StartsWith("rgb", StringComparison.OrdinalIgnoreCase))
20 | {
21 | return ArgbToColor(htmlColor);
22 | }
23 |
24 | // HSL
25 | if (htmlColor.StartsWith("hsl", StringComparison.OrdinalIgnoreCase))
26 | {
27 | return HslToColor(htmlColor);
28 | }
29 |
30 | // HWB
31 | if (htmlColor.StartsWith("hwb", StringComparison.OrdinalIgnoreCase))
32 | {
33 | return HwbToColor(htmlColor);
34 | }
35 |
36 | // LAB
37 | if (htmlColor.StartsWith("lab", StringComparison.OrdinalIgnoreCase))
38 | {
39 | return LabToColor(htmlColor);
40 | }
41 |
42 | // LCH
43 | if (htmlColor.StartsWith("lch", StringComparison.OrdinalIgnoreCase))
44 | {
45 | return LchToColor(htmlColor);
46 | }
47 |
48 | // OKLAB
49 | if (htmlColor.StartsWith("oklab", StringComparison.OrdinalIgnoreCase))
50 | {
51 | return OkLabToColor(htmlColor);
52 | }
53 |
54 | // OKLCH
55 | if (htmlColor.StartsWith("oklch", StringComparison.OrdinalIgnoreCase))
56 | {
57 | return OkLchToColor(htmlColor);
58 | }
59 |
60 | // Color
61 | if (htmlColor.StartsWith("color", StringComparison.OrdinalIgnoreCase))
62 | {
63 | return ColorToColor(htmlColor);
64 | }
65 |
66 | // HEX
67 | if (htmlColor.StartsWith("#", StringComparison.Ordinal))
68 | {
69 | return HexToColor(htmlColor);
70 | }
71 |
72 | // Named colors
73 | if (htmlColor.Equals("rebeccapurple", StringComparison.OrdinalIgnoreCase))
74 | {
75 | htmlColor = "#663399";
76 | }
77 |
78 | return ColorTranslator.FromHtml(htmlColor);
79 |
80 | }
81 | catch
82 | {
83 | return Color.Empty;
84 | }
85 | }
86 |
87 | public static MatchCollection MatchesColor(string html)
88 | {
89 | try
90 | {
91 | return _regex.Matches(html);
92 | }
93 | catch (RegexMatchTimeoutException)
94 | {
95 | // Regex timed out - return empty collection to prevent hangs
96 | return new Regex("(?!)").Matches("");
97 | }
98 | }
99 |
100 | private static Color HexToColor(string htmlColor)
101 | {
102 | int len = htmlColor.Length;
103 |
104 | // #RGB
105 | if (len == 4)
106 | {
107 | int r = Convert.ToInt32(htmlColor.Substring(1, 1), 16);
108 | int g = Convert.ToInt32(htmlColor.Substring(2, 1), 16);
109 | int b = Convert.ToInt32(htmlColor.Substring(3, 1), 16);
110 |
111 | return Color.FromArgb(r + (r * 16), g + (g * 16), b + (b * 16));
112 | }
113 |
114 | // #RGBA
115 | else if (len == 5)
116 | {
117 | int r = Convert.ToInt32(htmlColor.Substring(1, 1), 16);
118 | int g = Convert.ToInt32(htmlColor.Substring(2, 1), 16);
119 | int b = Convert.ToInt32(htmlColor.Substring(3, 1), 16);
120 | int a = Convert.ToInt32(htmlColor.Substring(4, 1), 16);
121 |
122 | return Color.FromArgb(a + (a * 16), r + (r * 16), g + (g * 16), b + (b * 16));
123 | }
124 |
125 | // #RRGGBB
126 | else if (len == 7)
127 | {
128 | return Color.FromArgb(
129 | Convert.ToInt32(htmlColor.Substring(1, 2), 16),
130 | Convert.ToInt32(htmlColor.Substring(3, 2), 16),
131 | Convert.ToInt32(htmlColor.Substring(5, 2), 16));
132 | }
133 |
134 | // #RRGGBBAA
135 | else if (len == 9)
136 | {
137 | return Color.FromArgb(
138 | Convert.ToInt32(htmlColor.Substring(7, 2), 16),
139 | Convert.ToInt32(htmlColor.Substring(1, 2), 16),
140 | Convert.ToInt32(htmlColor.Substring(3, 2), 16),
141 | Convert.ToInt32(htmlColor.Substring(5, 2), 16));
142 | }
143 |
144 | return Color.Empty;
145 | }
146 |
147 | private static Color ArgbToColor(string htmlColor)
148 | {
149 | string[] parts = ColorParts(htmlColor);
150 |
151 | double r = GetNumberOrPercentage(parts[0]);
152 | double g = GetNumberOrPercentage(parts[1]);
153 | double b = GetNumberOrPercentage(parts[2]);
154 |
155 | double a = parts.Length > 3 ? GetNumberOrPercentage(parts[3]) : 1.0;
156 |
157 | if (parts[0].Contains('%') || parts[1].Contains('%') || parts[2].Contains('%'))
158 | {
159 | return FromArgb(a, r, g, b);
160 | }
161 | else
162 | {
163 | return FromArgb(a, (int)r, (int)g, (int)b);
164 | }
165 | }
166 |
167 | private static Color HslToColor(string htmlColor)
168 | {
169 | string[] parts = ColorParts(htmlColor);
170 |
171 | double h = GetNumberOrPercentage(parts[0]);
172 | double s = GetNumberOrPercentage(parts[1]);
173 | double l = GetNumberOrPercentage(parts[2]);
174 | double a = parts.Length > 3 ? GetNumberOrPercentage(parts[3]) : 1.0;
175 |
176 | double[] rgb = HslToRgb(h, s, l);
177 |
178 | return FromArgb(a, rgb[0], rgb[1], rgb[2]);
179 | }
180 |
181 | private static Color HwbToColor(string htmlColor)
182 | {
183 | string[] parts = ColorParts(htmlColor);
184 |
185 | double hue = GetNumberOrPercentage(parts[0]);
186 | double white = GetNumberOrPercentage(parts[1]);
187 | double black = GetNumberOrPercentage(parts[2]);
188 |
189 | double a = parts.Length > 3 ? GetNumberOrPercentage(parts[3]) : 1.0;
190 |
191 | if (white + black >= 1)
192 | {
193 | double gray = white / (white + black);
194 | return FromArgb(a, gray, gray, gray);
195 | }
196 |
197 | double[] rgb = HslToRgb(hue, 1, .5);
198 |
199 | for (int i = 0; i < 3; i++)
200 | {
201 | rgb[i] *= Math.Round((1 - white - black), 2);
202 | rgb[i] += white;
203 | }
204 |
205 | return FromArgb(a, rgb[0], rgb[1], rgb[2]);
206 | }
207 |
208 | private static Color LabToColor(string htmlColor)
209 | {
210 | string[] parts = ColorParts(htmlColor);
211 |
212 | double l = GetNumberOrPercentage(parts[0]);
213 | double a = GetNumberOrPercentage(parts[1]);
214 | double b = GetNumberOrPercentage(parts[2]);
215 |
216 | double[] rgb = ColorConvertion.LabToRgb(l * 100, a, b);
217 |
218 | return FromArgb(1, rgb[0], rgb[1], rgb[2]);
219 | }
220 |
221 | private static Color LchToColor(string htmlColor)
222 | {
223 | string[] parts = ColorParts(htmlColor);
224 |
225 | double l = GetNumberOrPercentage(parts[0]);
226 | double c = GetNumberOrPercentage(parts[1]);
227 | double h = GetNumberOrPercentage(parts[2]);
228 |
229 | double[] rgb = ColorConvertion.LchToRgb(l * 100, c, h);
230 |
231 | return FromArgb(rgb[0], rgb[1], rgb[2]);
232 | }
233 |
234 | private static Color OkLabToColor(string htmlColor)
235 | {
236 | string[] parts = ColorParts(htmlColor);
237 |
238 | double l = GetNumberOrPercentage(parts[0]);
239 | double a = GetNumberOrPercentage(parts[1]);
240 | double b = GetNumberOrPercentage(parts[2]);
241 |
242 | double[] rgb = ColorConvertion.OkLabToRgb(l, a, b);
243 |
244 | return FromArgb(1, rgb[0], rgb[1], rgb[2]);
245 | }
246 |
247 | private static Color OkLchToColor(string htmlColor)
248 | {
249 | string[] parts = ColorParts(htmlColor);
250 |
251 | double l = GetNumberOrPercentage(parts[0]);
252 | double c = GetNumberOrPercentage(parts[1]);
253 | double h = GetNumberOrPercentage(parts[2]);
254 |
255 | double[] rgb = ColorConvertion.OkLchToRgb(l, c, h);
256 |
257 | return FromArgb(rgb[0], rgb[1], rgb[2]);
258 | }
259 |
260 | private static Color ColorToColor(string htmlColors)
261 | {
262 | string[] parts = ColorParts(htmlColors);
263 |
264 | double n1 = parts[0].ToUpper().StartsWith("XYZ") ?
265 | GetNumberOrPercentage(parts[1]) :
266 | ClampZeroToOne(GetNumberOrPercentage(parts[1]));
267 | double n2 = parts[0].ToUpper().StartsWith("XYZ") ?
268 | GetNumberOrPercentage(parts[2]) :
269 | ClampZeroToOne(GetNumberOrPercentage(parts[2]));
270 | double n3 = parts[0].ToUpper().StartsWith("XYZ") ?
271 | GetNumberOrPercentage(parts[3]) :
272 | ClampZeroToOne(GetNumberOrPercentage(parts[3]));
273 |
274 | double a = parts.Length == 5 ? GetNumberOrPercentage(parts[4]) : 1;
275 |
276 | if (parts[0].Equals("SRGB", StringComparison.OrdinalIgnoreCase))
277 | {
278 | return FromArgb(a, n1, n2, n3);
279 | }
280 |
281 | double[] rgb = parts[0].ToUpper() switch
282 | {
283 | "SRGB-LINEAR" => ColorConvertion.LinearRgbToRgb(new double[] { n1, n2, n3 }),
284 | "DISPLAY-P3" => ColorConvertion.DisplayP3ToRgb(n1, n2, n3),
285 | "A98-RGB" => ColorConvertion.A98RgbToRgb(n1, n2, n3),
286 | "PROPHOTO-RGB" => ColorConvertion.ProPhotoToRgb(n1, n2, n3),
287 | "REC2020" => ColorConvertion.Rec2020ToRgb(n1, n2, n3),
288 | "XYZ" or "XYZ-D65" => ColorConvertion.XyzToRgb(n1, n2, n3),
289 | "XYZ-D50" => ColorConvertion.XyzToRgb(n1, n2, n3, true),
290 | _ => new double[] { 0, 0, 0 },
291 | };
292 |
293 | return FromArgb(a, rgb[0], rgb[1], rgb[2]);
294 | }
295 |
296 | private static double[] HslToRgb(double hue, double sat, double light)
297 | {
298 | hue %= 360.0;
299 |
300 | if (hue < 0)
301 | {
302 | hue += 360;
303 | }
304 |
305 | double f(double n)
306 | {
307 | double k = (n + hue / 30.0) % 12.0;
308 | double a = sat * Math.Min(light, 1 - light);
309 | return light - a * Math.Max(-1, Math.Min(Math.Min(k - 3.0, 9.0 - k), 1));
310 | };
311 |
312 | return new double[] { f(0), f(8), f(4) };
313 | }
314 |
315 | private static string[] ColorParts(string htmlColor)
316 | {
317 | int left = htmlColor.IndexOf('(');
318 | int right = htmlColor.IndexOf(')');
319 |
320 | if (left < 0 || right < 0)
321 | {
322 | return new string[] { };
323 | }
324 |
325 | string noBrackets = htmlColor.Substring(left + 1, right - left - 1).Replace("deg", "");
326 |
327 | char seperator = htmlColor.Contains(',') ? ',' : ' ';
328 |
329 | string[] parts;
330 | string[] alphaChannel = noBrackets.Split('/');
331 | if (alphaChannel.Length == 2)
332 | {
333 | parts = alphaChannel[0].Trim().Split(new char[] { seperator }, StringSplitOptions.RemoveEmptyEntries).Append(alphaChannel[1]).ToArray();
334 | }
335 | else
336 | {
337 | parts = alphaChannel[0].Split(new char[] { seperator }, StringSplitOptions.RemoveEmptyEntries);
338 | }
339 |
340 | return parts.Select(p => p.Trim().Equals("none") ? "0" : p.Trim()).ToArray(); ;
341 | }
342 |
343 | private static double GetNumberOrPercentage(string part)
344 | {
345 | string raw = part.TrimEnd('%');
346 |
347 | if (double.TryParse(raw, out double d))
348 | {
349 | if (part.Contains('%') && d > 1)
350 | {
351 | return d / 100.0;
352 | }
353 | }
354 |
355 | return d;
356 | }
357 |
358 | public static Color FromArgb(double a, int r, int g, int b)
359 | {
360 | a = Math.Round(ClampZeroToOne(a) * 255.0, MidpointRounding.AwayFromZero);
361 | return Color.FromArgb((int)a, ClampRgb(r), ClampRgb(g), ClampRgb(b));
362 | }
363 |
364 | public static Color FromArgb(double r, double g, double b)
365 | {
366 | return FromArgb(1.0, r, g, b);
367 | }
368 |
369 | public static Color FromArgb(double a, double r, double g, double b)
370 | {
371 | a = Math.Round(ClampZeroToOne(a) * 255.0, MidpointRounding.AwayFromZero);
372 | r = Math.Round(r * 255.0, MidpointRounding.AwayFromZero);
373 | g = Math.Round(g * 255.0, MidpointRounding.AwayFromZero);
374 | b = Math.Round(b * 255.0, MidpointRounding.AwayFromZero);
375 | return Color.FromArgb(ClampRgb((int)a), ClampRgb((int)r), ClampRgb((int)g), ClampRgb((int)b));
376 | }
377 |
378 | public static int ClampRgb(int value)
379 | {
380 | return value < 0 ? 0 : value > 255 ? 255 : value;
381 |
382 | }
383 |
384 | public static double ClampZeroToOne(double value)
385 | {
386 | return value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
387 |
388 | }
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/test/EditorColorPreview.Test/CssColorFour.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using System.Drawing;
3 |
4 | namespace EditorColorPreview.Test
5 | {
6 | [TestClass]
7 | public class CssColorFour
8 | {
9 |
10 | [DataTestMethod]
11 | [DataRow("rgb(0% 50% 0%)", "#008000")]
12 | [DataRow("rgb(0 128.0 0)", "#008000")]
13 | [DataRow("rgb(0% 50% 0% / 1.0)", "#008000")]
14 | [DataRow("rgb(0 128.0 0 / 1)", "#008000")]
15 | [DataRow("rgb(0% 50% 0% / 100%)", "#008000")]
16 | [DataRow("rgb(0 128.0 0 / 100%)", "#008000")]
17 | [DataRow("rgb(0%, 50%, 0%, 100%)", "#008000")]
18 | [DataRow("rgb(0, 128.0, 0, 100%)", "#008000")]
19 | [DataRow("rgb(none none none)", "rgb(0, 0, 0)")]
20 | [DataRow("rgba(0% 50% 0%)", "#008000")]
21 | [DataRow("rgba(0 128.0 0)", "#008000")]
22 | [DataRow("rgba(0% 50% 0% / 1.0)", "#008000")]
23 | [DataRow("rgba(0 128.0 0 / 1)", "#008000")]
24 | [DataRow("rgba(0% 50% 0% / 100%)", "#008000")]
25 | [DataRow("rgba(0 128.0 0 / 100%)", "#008000")]
26 | [DataRow("rgba(0%, 50%, 0%, 100%)", "#008000")]
27 | [DataRow("rgba(0, 128.0, 0, 100%)", "#008000")]
28 | [DataRow("rgb(none none none / none)", "rgba(0, 0, 0, 0)")]
29 | [DataRow("rgb(128 none none)", "rgb(128, 0, 0)")]
30 | [DataRow("rgb(128 none none / none)", "rgba(128, 0, 0, 0)")]
31 | [DataRow("rgb(none none none / .5)", "rgba(0, 0, 0, 0.5)")]
32 | [DataRow("rgb(20% none none)", "rgb(51, 0, 0)")]
33 | [DataRow("rgb(20% none none / none)", "rgba(51, 0, 0, 0)")]
34 | [DataRow("rgb(none none none / 50%)", "rgba(0, 0, 0, 0.5)")]
35 | [DataRow("rgba(none none none)", "rgb(0, 0, 0)")]
36 | [DataRow("rgba(none none none / none)", "rgba(0, 0, 0, 0)")]
37 | [DataRow("rgba(128 none none)", "rgb(128, 0, 0)")]
38 | [DataRow("rgba(128 none none / none)", "rgba(128, 0, 0, 0)")]
39 | [DataRow("rgba(none none none / .5)", "rgba(0, 0, 0, 0.5)")]
40 | [DataRow("rgba(20% none none)", "rgb(51, 0, 0)")]
41 | [DataRow("rgba(20% none none / none)", "rgba(51, 0, 0, 0)")]
42 | [DataRow("rgba(none none none / 50%)", "rgba(0, 0, 0, 0.5)")]
43 | public void RGA_Test(string testHtml, string expectedHtml)
44 | {
45 | Color actual = ColorUtils.HtmlToColor(testHtml);
46 |
47 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
48 |
49 | Assert.AreEqual(expected, actual);
50 | }
51 |
52 | [DataTestMethod]
53 | [DataRow("hsl(120 100% 25%)", "#008000")]
54 | [DataRow("hsl(120deg 100% 25%)", "#008000")]
55 | [DataRow("hsl(120 100% 25% / 1.0)", "#008000")]
56 | [DataRow("hsl(120deg 100% 25% / 1)", "#008000")]
57 | [DataRow("hsl(120 100% 25% / 100%)", "#008000")]
58 | [DataRow("hsl(120deg 100% 25% / 100%)", "#008000")]
59 | [DataRow("hsl(120, 100%, 25%, 100%)", "#008000")]
60 | [DataRow("hsl(120deg, 100%, 25%, 100%)", "#008000")]
61 | [DataRow("hsla(120 100% 25%)", "#008000")]
62 | [DataRow("hsla(120deg 100% 25%)", "#008000")]
63 | [DataRow("hsla(120 100% 25% / 1.0)", "#008000")]
64 | [DataRow("hsla(120deg 100% 25% / 1)", "#008000")]
65 | [DataRow("hsla(120 100% 25% / 100%)", "#008000")]
66 | [DataRow("hsla(120deg 100% 25% / 100%)", "#008000")]
67 | [DataRow("hsla(120, 100%, 25%, 100%)", "#008000")]
68 | [DataRow("hsl(120deg, 100%, 25%, 100%)", "#008000")]
69 | [DataRow("hsl(120 30% 50%)", "rgb(89, 166, 89)")]
70 | [DataRow("hsl(120 30% 50% / 0.5)", "rgba(89, 166, 89, 0.5)")]
71 | [DataRow("hsl(none none none)", "rgb(0, 0, 0)")]
72 | [DataRow("hsl(0 0% 0%)", "rgb(0, 0, 0)")]
73 | [DataRow("hsl(none none none / none)", "rgba(0, 0, 0, 0)")]
74 | [DataRow("hsl(0 0% 0% / 0)", "rgba(0, 0, 0, 0)")]
75 | [DataRow("hsla(none none none)", "rgb(0, 0, 0)")]
76 | [DataRow("hsla(0 0% 0%)", "rgb(0, 0, 0)")]
77 | [DataRow("hsla(none none none / none)", "rgba(0, 0, 0, 0)")]
78 | [DataRow("hsla(0 0% 0% / 0)", "rgba(0, 0, 0, 0)")]
79 | [DataRow("hsl(120 none none)", "rgb(0, 0, 0)")]
80 | [DataRow("hsl(120 0% 0%)", "rgb(0, 0, 0)")]
81 | [DataRow("hsl(120 80% none)", "rgb(0, 0, 0)")]
82 | [DataRow("hsl(120 80% 0%)", "rgb(0, 0, 0)")]
83 | [DataRow("hsl(120 none 50%)", "rgb(128, 128, 128)")]
84 | [DataRow("hsl(120 0% 50%)", "rgb(128, 128, 128)")]
85 | [DataRow("hsl(120 100% 50% / none)", "rgba(0, 255, 0, 0)")]
86 | [DataRow("hsl(120 100% 50% / 0)", "rgba(0, 255, 0, 0)")]
87 | [DataRow("hsl(none 100% 50%)", "rgb(255, 0, 0)")]
88 | [DataRow("hsl(0 100% 50%)", "rgb(255, 0, 0)")]
89 | [DataRow("hsl(120deg none none)", "rgb(0, 0, 0)")]
90 | [DataRow("hsl(120deg 0% 0%)", "rgb(0, 0, 0)")]
91 | [DataRow("hsl(120deg 80% none)", "rgb(0, 0, 0)")]
92 | [DataRow("hsl(120deg 80% 0%)", "rgb(0, 0, 0)")]
93 | [DataRow("hsl(120deg none 50%)", "rgb(128, 128, 128)")]
94 | [DataRow("hsl(120deg 0% 50%)", "rgb(128, 128, 128)")]
95 | [DataRow("hsl(120deg 100% 50% / none)", "rgba(0, 255, 0, 0)")]
96 | [DataRow("hsl(120deg 100% 50% / 0)", "rgba(0, 255, 0, 0)")]
97 | public void HSLA_Test(string testHtml, string expectedHtml)
98 | {
99 | Color actual = ColorUtils.HtmlToColor(testHtml);
100 |
101 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
102 |
103 | Assert.AreEqual(expected, actual);
104 | }
105 |
106 |
107 | [DataTestMethod]
108 | [DataRow("hwb(120 0% 49.8039%)", "#008000")]
109 | [DataRow("hwb(0 0% 100%)", "#000000")]
110 | [DataRow("hwb(0 100% 100%)", "rgb(50% 50% 50%)")]
111 | [DataRow("hwb(120 20% 30%)", "rgb(20% 70% 20%)")]
112 | [DataRow("hwb(120 70% 60%)", "rgb(53.8462% 53.8462% 53.8462%)")]
113 | [DataRow("hwb(120 30% 50%)", "rgb(77, 128, 77)")]
114 | [DataRow("hwb(120 30% 50% / 0.5)", "rgba(77, 128, 77, 0.5)")]
115 | [DataRow("hwb(none none none)", "rgb(255, 0, 0)")]
116 | [DataRow("hwb(0 0% 0%)", "rgb(255, 0, 0)")]
117 | [DataRow("hwb(none none none / none)", "rgba(255, 0, 0, 0)")]
118 | [DataRow("hwb(0 0% 0% / 0)", "rgba(255, 0, 0, 0)")]
119 | [DataRow("hwb(120 none none)", "rgb(0, 255, 0)")]
120 | [DataRow("hwb(120 0% 0%)", "rgb(0, 255, 0)")]
121 | [DataRow("hwb(120 80% none)", "rgb(204, 255, 204)")]
122 | [DataRow("hwb(120 80% 0%)", "rgb(204, 255, 204)")]
123 | [DataRow("hwb(120 none 50%)", "rgb(0, 128, 0)")]
124 | [DataRow("hwb(120 0% 50%)", "rgb(0, 128, 0)")]
125 | [DataRow("hwb(120 30% 50% / none)", "rgba(77, 128, 77, 0)")]
126 | [DataRow("hwb(120 30% 50% / 0)", "rgba(77, 128, 77, 0)")]
127 | [DataRow("hwb(none 100% 50% / none)", "rgba(170, 170, 170, 0)")]
128 | [DataRow("hwb(0 100% 50% / 0)", "rgba(170, 170, 170, 0)")]
129 | public void HWB_Test(string testHtml, string expectedHtml)
130 | {
131 | Color actual = ColorUtils.HtmlToColor(testHtml);
132 |
133 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
134 |
135 | Assert.AreEqual(expected, actual);
136 | }
137 |
138 | [DataTestMethod]
139 | [DataRow("color(srgb 0 0.6 0)", "#009900")]
140 | [DataRow("color(srgb 0% 60% 0%)", "#009900")]
141 | [DataRow("color(srgb-linear 0 0.21586 0)", "#008000")]
142 | [DataRow("color(srgb-linear 0 0 0)", "#000")]
143 | [DataRow("color(srgb-linear 1 1 1)", "color(srgb 1 1 1)")]
144 | [DataRow("color(srgb-linear 0 1 0)", "lab(87.8185% -79.271 80.9946)")]
145 | [DataRow("color(srgb 0.691 0.139 0.259)", "color(srgb-linear 0.435 0.017 0.055)")]
146 | [DataRow("color(display-p3 0.21604 0.49418 0.13151)", "#008000")]
147 | [DataRow("color(display-p3 0 0 0)", "#000000")]
148 | [DataRow("color(display-p3 1 1 1)", "rgb(100% 100% 100%)")]
149 | [DataRow("color(display-p3 26.374% 59.085% 16.434%)", "#009900")]
150 | [DataRow("color(display-p3 0 1 0)", "lab(86.61399% -106.539 102.871)")]
151 | [DataRow("color(display-p3 1 1 0.330897)", "yellow")]
152 | [DataRow("color(display-p3 0.465377 0.532768 0.317713)", "rgb(44.8436% 53.537% 28.8112%)")]
153 | [DataRow("color(srgb 0.448436 0.53537 0.288113)", "rgb(44.8436% 53.537% 28.8112%)")]
154 | // TODO: Test not passing
155 | //[DataRow("color(a98-rgb 0.281363 0.498012 0.116746)", "#008000")]
156 | [DataRow("color(a98-rgb 0 0 0)", "#000000")]
157 | [DataRow("color(a98-rgb 1 1 1)", "rgb(99.993% 100% 100%)")]
158 | [DataRow("color(a98-rgb 0 1 0)", "lab(83.2141% -129.1072 87.1718)")]
159 | // TODO: Test not passing
160 | //[DataRow("color(prophoto-rgb 0.230479 0.395789 0.129968)", "#008000")]
161 | [DataRow("color(prophoto-rgb 0 0 0)", "#000000")]
162 | // TODO: Test not passing
163 | //[DataRow("color(prophoto-rgb 1 1 1)", "lab(100% 0.0131 0.0085)")]
164 | [DataRow("color(prophoto-rgb 0 1 0)", "lab(87.5745% -186.6921 150.9905)")]
165 | [DataRow("color(prophoto-rgb 0.42444 0.934918 0.190922)", "color(display-p3 0 1 0)")]
166 | // TODO: Test not passing
167 | //[DataRow("color(prophoto-rgb 28.610% 49.131% 16.133%)", "#009900")]
168 | [DataRow("color(rec2020 0.235202 0.431704 0.085432)", "#008000")]
169 | [DataRow("color(rec2020 0 0 0)", "#000000")]
170 | [DataRow("color(rec2020 1 1 1)", "rgb(99.993% 100% 100%)")]
171 | [DataRow("color(rec2020 0 1 0)", "lab(85.7729% -160.7259 109.2319)")]
172 | [DataRow("color(rec2020 0.431836 0.970723 0.079208)", "color(display-p3 0 1 0)")]
173 | [DataRow("color(rec2020 29.9218% 53.3327% 12.0785%)", "#009900")]
174 | [DataRow("color(rec2020 0.299218 0.533327 0.120785)", "#009900")]
175 | [DataRow("color(xyz 0.07719 0.15438 0.02573)", "#008000")]
176 | [DataRow("color(xyz 0 0 0)", "#000000")]
177 | [DataRow("color(xyz 1 1 1)", "lab(100.115% 9.06448 5.80177)")]
178 | [DataRow("color(xyz 0 1 0)", "lab(99.6289% -354.58 146.707)")]
179 | [DataRow("color(xyz 0.26567 0.69174 0.04511)", "color(display-p3 0 1 0)")]
180 | [DataRow("color(xyz-d50 0.08312 0.154746 0.020961)", "#008000")]
181 | [DataRow("color(xyz-d50 0 0 0)", "#000000")]
182 | [DataRow("color(xyz-d50 1 1 1)", "lab(100% 6.1097 -13.2268)")]
183 | [DataRow("color(xyz-d50 0 1 0)", "lab(100% -431.0345 172.4138)")]
184 | [DataRow("color(xyz-d50 0.29194 0.692236 0.041884)", "color(display-p3 0 1 0)")]
185 | [DataRow("color(xyz-d65 0.07719 0.15438 0.02573)", "#008000")]
186 | [DataRow("color(xyz-d65 0 0 0)", "#000000")]
187 | [DataRow("color(xyz-d65 1 1 1)", "lab(100.115% 9.06448 5.80177)")]
188 | [DataRow("color(xyz-d65 0 1 0)", "lab(99.6289% -354.58 146.707)")]
189 | [DataRow("color(xyz-d65 0.26567 0.69174 0.04511)", "color(display-p3 0 1 0)")]
190 | public void Color_Test(string testHtml, string expectedHtml)
191 | {
192 | Color actual = ColorUtils.HtmlToColor(testHtml);
193 |
194 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
195 | if (expected.IsKnownColor)
196 | {
197 | Assert.AreEqual(expected.A, actual.A);
198 | Assert.AreEqual(expected.R, actual.R);
199 | Assert.AreEqual(expected.G, actual.G);
200 | Assert.AreEqual(expected.B, actual.B);
201 | }
202 | else
203 | {
204 | Assert.AreEqual(expected, actual);
205 | }
206 |
207 | }
208 |
209 | [DataTestMethod]
210 | [DataRow("Basic sRGB white", "color(srgb 1 1 1)", "color(srgb 1 1 1)")]
211 | [DataRow("White with lots of space", "color( srgb 1 1 1 )", "color(srgb 1 1 1)")]
212 | [DataRow("sRGB color", "color(srgb 0.25 0.5 0.75)", "color(srgb 0.25 0.5 0.75)")]
213 | [DataRow("Different case for sRGB", "color(SrGb 0.25 0.5 0.75)", "color(srgb 0.25 0.5 0.75)")]
214 | [DataRow("sRGB color with unnecessary decimals", "color(srgb 1.00000 0.500000 0.20)", "color(srgb 1 0.5 0.2)")]
215 | [DataRow("sRGB white with 0.5 alpha", "color(srgb 1 1 1 / 0.5)", "color(srgb 1 1 1 / 0.5)")]
216 | [DataRow("sRGB white with 0 alpha", "color(srgb 1 1 1 / 0)", "color(srgb 1 1 1 / 0)")]
217 | [DataRow("sRGB white with 50% alpha", "color(srgb 1 1 1 / 50%)", "color(srgb 1 1 1 / 0.5)")]
218 | [DataRow("sRGB white with 0% alpha", "color(srgb 1 1 1 / 0%)", "color(srgb 1 1 1 / 0)")]
219 | [DataRow("Display P3 color", "color(display-p3 0.6 0.7 0.8)", "color(display-p3 0.6 0.7 0.8)")]
220 | [DataRow("Different case for Display P3", "color(dIspLaY-P3 0.6 0.7 0.8)", "color(display-p3 0.6 0.7 0.8)")]
221 | [DataRow("sRGB color with negative component should clamp to 0", "color(srgb -0.25 0.5 0.75)", "color(srgb 0 0.5 0.75)")]
222 | [DataRow("sRGB color with component > 1 should clamp", "color(srgb 0.25 1.5 0.75)", "color(srgb 0.25 1 0.75)")]
223 | [DataRow("Display P3 color with negative component should clamp to 0", "color(display-p3 0.5 -199 0.75)", "color(display-p3 0.5 0 0.75)")]
224 | [DataRow("Display P3 color with component > 1 should clamp", "color(display-p3 184 1.00001 2347329746587)", "color(display-p3 1 1 1)")]
225 | [DataRow("Alpha > 1 should clamp", "color(srgb 0.1 0.2 0.3 / 1.9)", "color(srgb 0.1 0.2 0.3)")]
226 | [DataRow("Negative alpha should clamp", "color(srgb 1 1 1 / -0.2)", "color(srgb 1 1 1 / 0)")]
227 | public void Parsing_Tests(string _, string inputHtml, string expectedHtml)
228 | {
229 | Color actual = ColorUtils.HtmlToColor(inputHtml);
230 |
231 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
232 |
233 | Assert.AreEqual(expected, actual);
234 | }
235 |
236 | [DataTestMethod]
237 | [DataRow("lab(46.2775% -47.5621 48.5837)", "#008000")]
238 | // TODO: Test not passing
239 | //[DataRow("lab(0% 0 0)", "#000000")]
240 | [DataRow("lab(100% 0 0)", "#FFFFFF")]
241 | [DataRow("lab(50% 50 0)", "rgb(75.6208% 30.4487% 47.5634%)")]
242 | [DataRow("lab(70% -45 0)", "rgb(10.751% 75.558% 66.398%)")]
243 | [DataRow("lab(70% 0 70)", "rgb(76.6254% 66.3607% 5.5775%)")]
244 | [DataRow("lab(55% 0 -60)", "rgb(12.8128% 53.105% 92.7645%)")]
245 | [DataRow("lab(86.6146% -106.5599 102.8717)", "color(display-p3 0 1 0)")]
246 | public void Lab_Color_test(string inputHtml, string expectedHtml)
247 | {
248 | Color actual = ColorUtils.HtmlToColor(inputHtml);
249 |
250 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
251 |
252 | Assert.AreEqual(expected, actual);
253 | }
254 |
255 | [DataTestMethod]
256 | [DataRow("lch(46.2775% 67.9892 134.3912)", "#008000")]
257 | [DataRow("lch(0% 0 0)", "#000000")]
258 | [DataRow("lch(100% 0 0)", "#FFFFFF")]
259 | [DataRow("lch(50% 50 0)", "rgb(75.6208% 30.4487% 47.5634%)")]
260 | // TODO: Test not passing
261 | //[DataRow("lch(70% 45 180)", "color(display-p3 0.3551 0.7445 0.6662)")]
262 | // TODO: Test not passing
263 | //[DataRow("lch(70% 70 90)", "rgb(76.6254% 66.3607% 5.5775%)")]
264 | [DataRow("lch(55% 60 270)", "rgb(12.8128% 53.105% 92.7645%)")]
265 | public void Lch_Color_test(string inputHtml, string expectedHtml)
266 | {
267 | Color actual = ColorUtils.HtmlToColor(inputHtml);
268 |
269 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
270 |
271 | Assert.AreEqual(expected, actual);
272 | }
273 |
274 | [DataTestMethod]
275 | [DataRow("oklab(51.975% -0.1403 0.10768)", "#008000")]
276 | [DataRow("oklab(0% 0 0)", "#000000")]
277 | [DataRow("oklab(100% 0 0)", "#FFFFFF")]
278 | [DataRow("oklab(50% 0.05 0)", "rgb(48.477% 34.29% 38.412%)")]
279 | [DataRow("oklab(70% -0.1 0)", "rgb(29.264% 70.096% 63.017%)")]
280 | [DataRow("oklab(70% 0 0.125)", "rgb(73.942% 60.484% 19.65%)")]
281 | [DataRow("oklab(55% 0 -0.2)", "rgb(27.888% 38.072% 89.414%)")]
282 | [DataRow("oklab(84.883% -0.3042 0.20797)", "color(display-p3 0 1 0)")]
283 | public void OkLab_Color_Test(string inputHtml, string expectedHtml)
284 | {
285 | Color actual = ColorUtils.HtmlToColor(inputHtml);
286 |
287 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
288 |
289 | Assert.AreEqual(expected, actual);
290 | }
291 |
292 | [DataTestMethod]
293 | [DataRow("oklch(51.975% 0.17686 142.495)", "#008000")]
294 | [DataRow("oklch(0% 0 0)", "#000000")]
295 | [DataRow("oklch(100% 0 0)", "#FFFFFF")]
296 | [DataRow("oklch(50% 0.2 0)", "rgb(70.492% 2.351% 37.073%)")]
297 | [DataRow("oklch(50% 0.2 270)", "rgb(23.056% 31.73% 82.628%)")]
298 | [DataRow("oklch(80% 0.15 160)", "rgb(32.022% 85.805% 61.147%)")]
299 | [DataRow("oklch(55% 0.15 345)", "rgb(67.293% 27.791% 52.28%)")]
300 | [DataRow("oklch(84.883% 0.36853 145.645)", "color(display-p3 0 1 0)")]
301 | public void OkLch_Color_Test(string inputHtml, string expectedHtml)
302 | {
303 | Color actual = ColorUtils.HtmlToColor(inputHtml);
304 |
305 | Color expected = ColorUtils.HtmlToColor(expectedHtml);
306 |
307 | Assert.AreEqual(expected, actual);
308 | }
309 | }
310 | }
311 |
312 |
313 |
314 |
--------------------------------------------------------------------------------