From 49aa454fa9260397bdd02a54b205c12ab10c39fc Mon Sep 17 00:00:00 2001 From: Johann Dirry Date: Mon, 20 May 2024 14:54:58 +0200 Subject: [PATCH] implementing gradient generator --- .../GradientGeneralType.cs | 10 + TinyColorMap.ColorSpaces/GradientGenerator.cs | 172 ++++++++++++++++++ .../GradientJoiningType.cs | 12 ++ TinyColorMap.ColorSpaces/Readme.md | 35 +++- .../TinyColorMap.ColorSpaces.csproj | 3 + 5 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 TinyColorMap.ColorSpaces/GradientGeneralType.cs create mode 100644 TinyColorMap.ColorSpaces/GradientGenerator.cs create mode 100644 TinyColorMap.ColorSpaces/GradientJoiningType.cs diff --git a/TinyColorMap.ColorSpaces/GradientGeneralType.cs b/TinyColorMap.ColorSpaces/GradientGeneralType.cs new file mode 100644 index 0000000..2fb1a90 --- /dev/null +++ b/TinyColorMap.ColorSpaces/GradientGeneralType.cs @@ -0,0 +1,10 @@ +using NetEscapades.EnumGenerators; + +namespace TinyColorMap; + +[EnumExtensions] +public enum GradientGeneralType +{ + Continuous = 0, + Discrete = 1 +} \ No newline at end of file diff --git a/TinyColorMap.ColorSpaces/GradientGenerator.cs b/TinyColorMap.ColorSpaces/GradientGenerator.cs new file mode 100644 index 0000000..b1c57db --- /dev/null +++ b/TinyColorMap.ColorSpaces/GradientGenerator.cs @@ -0,0 +1,172 @@ +namespace TinyColorMap; + +/// +/// Generator for creating color gradients. +/// +public sealed class GradientGenerator +{ + private const int Segments = 4; + private const int DataClasses = 7; + private const double PStep = 1.0 / DataClasses; + + /// + /// Creates a sequence of gradients based on the specified parameters. + /// + /// The general type of the gradient. + /// The type of gradient joining. + /// The base hue of the gradient. + /// The saturation level of the gradient. + /// The number of colors in the gradient. + /// Enumerable of tuples containing the position and RGB color of each gradient step. + [Pure] + public static IEnumerable<(double position, RGB color)> CreateGradient( + GradientGeneralType generalType, + GradientJoiningType joiningType, + double hue, + double saturation, + int numberOfColors) + { + var complementaryHue = GetComplementaryHue(hue); + + for (int i = 0; i < numberOfColors; ++i) + { + double position = (double)i / numberOfColors; + yield return CalculateColorAndModifiedPosition(position, generalType, joiningType, hue, saturation, complementaryHue); + } + } + + /// + /// Calculates the modified position and the corresponding RGB color for a given position. + /// + /// The position in the gradient. + /// The general type of the gradient. + /// The type of gradient joining. + /// The base hue of the gradient. + /// The saturation level of the gradient. + /// The complementary hue of the base hue. + /// A tuple containing the modified position and the RGB color. + [Pure] + private static (double mp, RGB rgb) CalculateColorAndModifiedPosition( + double position, + GradientGeneralType generalType, + GradientJoiningType joiningType, + double hue, + double saturation, + double complementaryHue) + { + double mh = hue; + double modifiedPosition = ModifyPosition(position, generalType, joiningType); + if (joiningType == GradientJoiningType.Diverging && position > 0.5) + { + mh = complementaryHue; + } + + var rgb = SequentialMapVaryingLightnessSingleHue(modifiedPosition, mh, saturation); + return (modifiedPosition, rgb); + } + + /// + /// Maps a position to an RGB color with varying lightness and a single hue. + /// + /// The position in the gradient. + /// The hue of the color. + /// The saturation level of the color. + /// The RGB color at the given position. + [Pure] + private static RGB SequentialMapVaryingLightnessSingleHue( + double position, + double hue, + double saturation) + { + double lightness = position * 100.0; + return ColorSpaceConverter.Hsluv2Rgb(new(hue, saturation, lightness)); + } + + /// + /// Modifies the position in the gradient based on the general and joining types. + /// + /// The position in the gradient. + /// The general type of the gradient. + /// The type of gradient joining. + /// The modified position. + [Pure] + private static double ModifyPosition( + double position, + GradientGeneralType generalType, + GradientJoiningType joiningType) + { + double p = position; + + switch (joiningType) + { + case GradientJoiningType.No: + break; + case GradientJoiningType.Steps: + p *= Segments; + p = Frac(p); + break; + case GradientJoiningType.Tubes: + p *= Segments; + int ip = (int)p; + p = p - ip; + if (ip % 2 != 0) p = 1.0 - p; + break; + case GradientJoiningType.Diverging: + p *= 2.0; + if (p > 1.0) p = 2.0 - p; + break; + default: + throw new ArgumentOutOfRangeException(nameof(joiningType), joiningType, null); + } + + if (generalType == GradientGeneralType.Discrete) + { + p = Double2Steps(p); + } + + return p; + } + + /// + /// Converts a double value to a step value based on the predefined step size. + /// + /// The double value to convert. + /// The step value. + [Pure] + private static double Double2Steps(double p) + { + double s = p / PStep; + s -= Frac(s); + s *= PStep; + return s; + } + + /// + /// Returns the fractional part (digits after the comma) of a double. + /// + /// The double value. + /// The fractional part of the double value. + [Pure] + private static double Frac(double d) => d - Math.Floor(d); + + /// + /// Calculates the complementary hue of a given hue. + /// + /// The base hue. + /// The complementary hue. + /// Thrown when the hue is out of the valid range [0, 360]. + [Pure] + private static double GetComplementaryHue(double hue) + { + if (hue is < 0.0 or > 360.0) + throw new ArgumentOutOfRangeException(nameof(hue), hue, "Hue must be in the range [0, 360]."); + + double complementaryHue = 180.0 + hue; + if (complementaryHue > 360.0) + { + complementaryHue -= 360.0; + } + + return complementaryHue; + } +} diff --git a/TinyColorMap.ColorSpaces/GradientJoiningType.cs b/TinyColorMap.ColorSpaces/GradientJoiningType.cs new file mode 100644 index 0000000..f08c6af --- /dev/null +++ b/TinyColorMap.ColorSpaces/GradientJoiningType.cs @@ -0,0 +1,12 @@ +using NetEscapades.EnumGenerators; + +namespace TinyColorMap; + +[EnumExtensions] +public enum GradientJoiningType +{ + No = 0, + Steps = 1, + Tubes = 2, + Diverging = 3 +} \ No newline at end of file diff --git a/TinyColorMap.ColorSpaces/Readme.md b/TinyColorMap.ColorSpaces/Readme.md index 0fc7f92..3785809 100644 --- a/TinyColorMap.ColorSpaces/Readme.md +++ b/TinyColorMap.ColorSpaces/Readme.md @@ -1,6 +1,6 @@ -# Color Space +## ColorSpaceConverter -Provides methods to convert colors between different color spaces. The following color spaces are supported: +The `ColorSpaceConverter` class provides methods to convert colors between different color spaces. The following color spaces are supported: | Color Space | Usage | |-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -17,6 +17,37 @@ Provides methods to convert colors between different color spaces. The following | YCbCr | YCbCr is used in digital video and image compression formats like JPEG and MPEG, where separating luminance and chrominance allows for more efficient encoding. | | HSV | HSV (Hue, Saturation, Value) is used in many applications where color description is key, such as image editing and graphic design, providing a more intuitive way to adjust colors. | +## GradientGenerator + +The `GradientGenerator` class provides methods to generate color gradients between two colors. + +```csharp +using System; +using System.Collections.Generic; + +public class Program +{ + public static void Main() + { + // Define the parameters for the gradient + GradientGeneralType generalType = GradientGeneralType.Continuous; + GradientJoiningType joiningType = GradientJoiningType.No; + double hue = 200.0; // Base hue [0..360°] + double saturation = 80.0; // Saturation level [0..100%] + int numberOfColors = 10; // Number of colors in the gradient + + // Create the gradient + IEnumerable<(double position, RGB color)> gradient = GradientGenerator.CreateGradient(generalType, joiningType, hue, saturation, numberOfColors); + + // Print the gradient positions and colors + foreach (var (position, color) in gradient) + { + Console.WriteLine($"Position: {position:F2}, Color: R={color.R}, G={color.G}, B={color.B}"); + } + } +} +``` + ## Attribution The following projects were (partially) used as references for the implementation of the color spaces: diff --git a/TinyColorMap.ColorSpaces/TinyColorMap.ColorSpaces.csproj b/TinyColorMap.ColorSpaces/TinyColorMap.ColorSpaces.csproj index 8bbef91..98c41f3 100644 --- a/TinyColorMap.ColorSpaces/TinyColorMap.ColorSpaces.csproj +++ b/TinyColorMap.ColorSpaces/TinyColorMap.ColorSpaces.csproj @@ -19,4 +19,7 @@ ColorSpaceConverter.cs + + +