From 795d696eca4f975cbd1cb50440a7acdb482016c7 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 26 Feb 2023 13:10:59 +0000 Subject: [PATCH] Add GammaToLinear and LinearToGamma (#60) --- CHANGELOG.md | 2 + X10D.Tests/src/Math/MathUtilityTests.cs | 40 +++++++ X10D/src/Math/MathUtility.cs | 135 ++++++++++++++++++++++++ 3 files changed, 177 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 695b70f..13fa098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ - X10D: Added `Color.GetClosestConsoleColor()` - X10D: Added `DateTime.GetIso8601WeekOfYear()` and `DateTimeOffset.GetIso8601WeekOfYear()` - X10D: Added `DirectoryInfo.Clear()` +- X10D: Added `double.LinearToGamma([gamma])` and `float.LinearToGamma([gamma])` (#60) +- X10D: Added `double.GammaToLinear([gamma])` and `float.GammaToLinear([gamma])` (#60) - X10D: Added `GreatestCommonFactor` for built-in integer types - X10D: Added `IEnumerable.CountWhereNot(Func)` - X10D: Added `IEnumerable.FirstWhereNot(Func)` diff --git a/X10D.Tests/src/Math/MathUtilityTests.cs b/X10D.Tests/src/Math/MathUtilityTests.cs index 7ee327f..9a3fc92 100644 --- a/X10D.Tests/src/Math/MathUtilityTests.cs +++ b/X10D.Tests/src/Math/MathUtilityTests.cs @@ -7,6 +7,26 @@ namespace X10D.Tests.Math; [TestClass] public class MathUtilityTests { + [TestMethod] + public void GammaToLinear_ShouldReturnQuarter_GivenQuarterAndGamma1() + { + double doubleResult = MathUtility.GammaToLinear(0.25, 1.0); + float floatResult = MathUtility.GammaToLinear(0.25f, 1.0f); + + Assert.AreEqual(0.25, doubleResult); + Assert.AreEqual(0.25f, floatResult); + } + + [TestMethod] + public void GammaToLinear_ShouldReturn1_Given1AndDefaultGamma() + { + double doubleResult = MathUtility.GammaToLinear(1.0); + float floatResult = MathUtility.GammaToLinear(1.0f); + + Assert.AreEqual(1.0, doubleResult); + Assert.AreEqual(1.0f, floatResult); + } + [TestMethod] public void InverseLerp_ShouldReturn0_5_Given0_5_0_1() { @@ -43,4 +63,24 @@ public class MathUtilityTests Assert.AreEqual(0.0, doubleResult, 1e-6); Assert.AreEqual(0.0f, floatResult, 1e-6f); } + + [TestMethod] + public void LinearToGamma_ShouldReturnQuarter_GivenQuarterAndGamma1() + { + double doubleResult = MathUtility.LinearToGamma(0.25, 1.0); + float floatResult = MathUtility.LinearToGamma(0.25f, 1.0f); + + Assert.AreEqual(0.25, doubleResult); + Assert.AreEqual(0.25f, floatResult); + } + + [TestMethod] + public void LinearToGamma_ShouldReturn1_Given1AndDefaultGamma() + { + double doubleResult = MathUtility.LinearToGamma(1.0); + float floatResult = MathUtility.LinearToGamma(1.0f); + + Assert.AreEqual(1.0, doubleResult); + Assert.AreEqual(1.0f, floatResult); + } } diff --git a/X10D/src/Math/MathUtility.cs b/X10D/src/Math/MathUtility.cs index b6b2c41..ce3565b 100644 --- a/X10D/src/Math/MathUtility.cs +++ b/X10D/src/Math/MathUtility.cs @@ -8,6 +8,75 @@ namespace X10D.Math; /// public static class MathUtility { + private const double DefaultGamma = 2.2; + private const float DefaultGammaF = 2.2f; + + /// + /// Converts a gamma-encoded value to a linear value using a gamma value of 2.2. + /// + /// The gamma-encoded value to convert. Expected range is [0, 1]. + /// The linear value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static float GammaToLinear(float value) + { + return GammaToLinear(value, DefaultGammaF); + } + + /// + /// Converts a gamma-encoded value to a linear value using the specified gamma value. + /// + /// The gamma-encoded value to convert. Expected range is [0, 1]. + /// The gamma value to use for decoding. + /// The linear value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static float GammaToLinear(float value, float gamma) + { + return MathF.Pow(value, 1.0f / gamma); + } + + /// + /// Converts a gamma-encoded value to a linear value using a gamma value of 2.2. + /// + /// The gamma-encoded value to convert. Expected range is [0, 1]. + /// The linear value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static double GammaToLinear(double value) + { + return GammaToLinear(value, DefaultGamma); + } + + /// + /// Converts a gamma-encoded value to a linear value using the specified gamma value. + /// + /// The gamma-encoded value to convert. Expected range is [0, 1]. + /// The gamma value to use for decoding. + /// The linear value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static double GammaToLinear(double value, double gamma) + { + return System.Math.Pow(value, 1.0 / gamma); + } + /// /// Returns the linear interpolation inverse of a value, such that it determines where a value lies between two other /// values. @@ -100,6 +169,72 @@ public static class MathUtility return ((1.0 - alpha) * value) + (alpha * target); } + /// + /// Converts a linear value to a gamma-encoded value using a gamma value of 2.2. + /// + /// The linear value to convert. Expected range is [0, 1]. + /// The gamma-encoded value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static float LinearToGamma(float value) + { + return LinearToGamma(value, DefaultGammaF); + } + + /// + /// Converts a linear value to a gamma-encoded value using the specified gamma value. + /// + /// The linear value to convert. Expected range is [0, 1]. + /// The gamma value to use for encoding. + /// The gamma-encoded value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static float LinearToGamma(float value, float gamma) + { + return MathF.Pow(value, 1.0f / gamma); + } + + /// + /// Converts a linear value to a gamma-encoded value using a gamma value of 2.2. + /// + /// The linear value to convert. Expected range is [0, 1]. + /// The gamma-encoded value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static double LinearToGamma(double value) + { + return LinearToGamma(value, DefaultGamma); + } + + /// + /// Converts a linear value to a gamma-encoded value using the specified gamma value. + /// + /// The linear value to convert. Expected range is [0, 1]. + /// The gamma value to use for encoding. + /// The gamma-encoded value. + [Pure] +#if NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#endif + public static double LinearToGamma(double value, double gamma) + { + return System.Math.Pow(value, 1.0 / gamma); + } + /// /// Converts a value from being a percentage of one range, to being the same percentage in a new range. ///