// Copyright 2003-2024 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. // // Use, duplication, or disclosure by the U.S. Government is subject to // restrictions set forth in FAR 52.227-19 (Commercial Computer // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. using System; using Color = System.Drawing.Color; namespace Wpf.Ui.Extend.Controls; /// /// Helper class to easier work with color formats /// /// /// Implementation: https://github.com/microsoft/PowerToys/blob/main/src/common/ManagedCommon/ColorFormatHelper.cs /// public static class ColorFormatUtils { /// /// Return a drawing color of a given /// public static Color GetDrawingColor(this System.Windows.Media.Color color) { return Color.FromArgb(1, color.R, color.G, color.B); } ///// ///// Return a drawing color of a given ///// //public static Color GetDrawingColor(this Autodesk.Revit.DB.Color color) //{ // return Color.FromArgb(1, color.Red, color.Green, color.Blue); //} /// /// Convert a given to a CMYK color (cyan, magenta, yellow, black key) /// /// The to convert /// The cyan[0..1], magenta[0..1], yellow[0..1] and black key[0..1] of the converted color public static (double Cyan, double Magenta, double Yellow, double BlackKey) ConvertToCmykColor(Color color) { // special case for black (avoid division by zero) if (color is { R: 0, G: 0, B: 0 }) { return (0d, 0d, 0d, 1d); } var red = color.R / 255d; var green = color.G / 255d; var blue = color.B / 255d; var blackKey = 1d - Math.Max(Math.Max(red, green), blue); // special case for black (avoid division by zero) if (1d - blackKey == 0d) { return (0d, 0d, 0d, 1d); } var cyan = (1d - red - blackKey) / (1d - blackKey); var magenta = (1d - green - blackKey) / (1d - blackKey); var yellow = (1d - blue - blackKey) / (1d - blackKey); return (cyan, magenta, yellow, blackKey); } /// /// Convert a given to a HSB color (hue, saturation, brightness) /// /// The to convert /// The hue [0°..360°], saturation [0..1] and brightness [0..1] of the converted color public static (double Hue, double Saturation, double Brightness) ConvertToHsbColor(Color color) { // HSB and HSV represents the same color space return ConvertToHsvColor(color); } /// /// Convert a given to a HSV color (hue, saturation, value) /// /// The to convert /// The hue [0°..360°], saturation [0..1] and value [0..1] of the converted color public static (double Hue, double Saturation, double Value) ConvertToHsvColor(Color color) { var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; return (color.GetHue(), max == 0d ? 0d : (max - min) / max, max); } /// /// Convert a given to a HSI color (hue, saturation, intensity) /// /// The to convert /// The hue [0°..360°], saturation [0..1] and intensity [0..1] of the converted color public static (double Hue, double Saturation, double Intensity) ConvertToHsiColor(Color color) { // special case for black if (color.R == 0 && color.G == 0 && color.B == 0) { return (0d, 0d, 0d); } var red = color.R / 255d; var green = color.G / 255d; var blue = color.B / 255d; var intensity = (red + green + blue) / 3d; var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; return (color.GetHue(), 1d - (min / intensity), intensity); } /// /// Convert a given to a HSL color (hue, saturation, lightness) /// /// The to convert /// The hue [0°..360°], saturation [0..1] and lightness [0..1] values of the converted color public static (double Hue, double Saturation, double Lightness) ConvertToHslColor(Color color) { var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; var lightness = (max + min) / 2d; if (lightness == 0d || Math.Abs(min - max) < 1e-9) { return (color.GetHue(), 0d, lightness); } if (lightness is > 0d and <= 0.5d) { return (color.GetHue(), (max - min) / (max + min), lightness); } return (color.GetHue(), (max - min) / (2d - (max + min)), lightness); } /// /// Convert a given to a HWB color (hue, whiteness, blackness) /// /// The to convert /// The hue [0°..360°], whiteness [0..1] and blackness [0..1] of the converted color public static (double Hue, double Whiteness, double Blackness) ConvertToHwbColor(Color color) { var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; return (color.GetHue(), min, 1 - max); } /// /// Convert a given to a CIE LAB color (LAB) /// /// The to convert /// The lightness [0..100] and two chromaticities [-128..127] public static (double Lightness, double ChromaticityA, double ChromaticityB) ConvertToCielabColor(Color color) { var xyz = ConvertToCiexyzColor(color); var lab = GetCielabColorFromCieXyz(xyz.X, xyz.Y, xyz.Z); return lab; } /// /// Convert a given to a CIE XYZ color (XYZ) /// The constants of the formula matches this Wikipedia page, but at a higher precision: /// https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ) /// This page provides a method to calculate the constants: /// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html /// /// The to convert /// The X [0..1], Y [0..1] and Z [0..1] public static (double X, double Y, double Z) ConvertToCiexyzColor(Color color) { var r = color.R / 255d; var g = color.G / 255d; var b = color.B / 255d; // inverse companding, gamma correction must be undone var rLinear = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : (r / 12.92); var gLinear = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : (g / 12.92); var bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92); return ( (rLinear * 0.41239079926595948) + (gLinear * 0.35758433938387796) + (bLinear * 0.18048078840183429), (rLinear * 0.21263900587151036) + (gLinear * 0.71516867876775593) + (bLinear * 0.07219231536073372), (rLinear * 0.01933081871559185) + (gLinear * 0.11919477979462599) + (bLinear * 0.95053215224966058) ); } /// /// Convert a CIE XYZ color to a CIE LAB color (LAB) adapted to sRGB D65 white point /// The constants of the formula used come from this wikipedia page: /// https://en.wikipedia.org/wiki/CIELAB_color_space#Converting_between_CIELAB_and_CIEXYZ_coordinates /// /// The represents a mix of the three CIE RGB curves /// The represents the luminance /// The is quasi-equal to blue (of CIE RGB) /// The lightness [0..100] and two chromaticities [-128..127] private static (double Lightness, double ChromaticityA, double ChromaticityB) GetCielabColorFromCieXyz(double x, double y, double z) { // sRGB reference white (x=0.3127, y=0.3290, Y=1.0), actually CIE Standard Illuminant D65 truncated to 4 decimal places, // then converted to XYZ using the formula: // X = x * (Y / y) // Y = Y // Z = (1 - x - y) * (Y / y) const double xN = 0.9504559270516717; const double yN = 1.0; const double zN = 1.0890577507598784; // Scale XYZ values relative to reference white x /= xN; y /= yN; z /= zN; // XYZ to CIELab transformation const double delta = 6d / 29; var m = (1d / 3) * Math.Pow(delta, -2); var t = Math.Pow(delta, 3); var fx = (x > t) ? Math.Pow(x, 1.0 / 3.0) : (x * m) + (16.0 / 116.0); var fy = (y > t) ? Math.Pow(y, 1.0 / 3.0) : (y * m) + (16.0 / 116.0); var fz = (z > t) ? Math.Pow(z, 1.0 / 3.0) : (z * m) + (16.0 / 116.0); var l = (116 * fy) - 16; var a = 500 * (fx - fy); var b = 200 * (fy - fz); return (l, a, b); } /// /// Convert a given to a natural color (hue, whiteness, blackness) /// /// The to convert /// The hue, whiteness [0..1] and blackness [0..1] of the converted color public static (string Hue, double Whiteness, double Blackness) ConvertToNaturalColor(Color color) { var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; return (GetNaturalColorFromHue(color.GetHue()), min, 1 - max); } /// /// Return the natural color for the given hue value /// /// The hue value to convert /// A natural color private static string GetNaturalColorFromHue(double hue) { return hue switch { < 60d => $"R{Math.Round(hue / 0.6d, 0)}", < 120d => $"Y{Math.Round((hue - 60d) / 0.6d, 0)}", < 180d => $"G{Math.Round((hue - 120d) / 0.6d, 0)}", < 240d => $"C{Math.Round((hue - 180d) / 0.6d, 0)}", < 300d => $"B{Math.Round((hue - 240d) / 0.6d, 0)}", _ => $"M{Math.Round((hue - 300d) / 0.6d, 0)}" }; } }