basegfx/source/color/bcolormodifier.cxx | 30 +++++++++++------------------- basegfx/test/BColorModifierTest.cxx | 21 +++++++++++++++++++++ include/basegfx/color/bcolormodifier.hxx | 2 +- 3 files changed, 33 insertions(+), 20 deletions(-)
New commits: commit c03d752c614b3b19ed89a510d7791bc46604a452 Author: Noel Grandin <noel.gran...@collabora.co.uk> AuthorDate: Fri Apr 25 13:49:14 2025 +0200 Commit: Noel Grandin <noel.gran...@collabora.co.uk> CommitDate: Fri Apr 25 17:47:09 2025 +0200 improve fast_pow in BColorModifier_gamma And add tests to demonstrate accuracy. This is taken from https://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/ and results in a maximum error of 3% over our input range. Change-Id: I1568dabb552d52a3eff085fd327ed3c1c246edcc Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184623 Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> Tested-by: Jenkins diff --git a/basegfx/source/color/bcolormodifier.cxx b/basegfx/source/color/bcolormodifier.cxx index 360a41db9285..44da250acdbf 100644 --- a/basegfx/source/color/bcolormodifier.cxx +++ b/basegfx/source/color/bcolormodifier.cxx @@ -18,6 +18,7 @@ */ #include <sal/config.h> +#include <sal/mathconf.h> #include <algorithm> #include <float.h> #include <basegfx/color/bcolormodifier.hxx> @@ -325,28 +326,19 @@ namespace basegfx } /** - A fast and approximate std::pow(), good enough for gamma calculations. - - std::pow() is basically implemented using log's: - pow(a,b) = x^(logx(a) * b) - So we need a fast log and fast exponent - it doesn't matter what x is so we use 2. - pow(a,b) = 2^(log2(a) * b) - The trick is that a floating point number is already in a log style format: - a = M * 2^E - Taking the log of both sides gives: - log2(a) = log2(M) + E - or more simply: - log2(a) ~= E - In other words if we take the floating point representation of a number, - and extract the Exponent we've got something that's a good starting point as its log. - And then we can do: - pow(a,b) = 2^(E * b) + A fast and approximate std::pow(), good enough for gamma calculations, + given that the parameter a is in the range [0,1), and b is in the range (0, 0.1] + + Google for "optimised power approximation". The below function is the result + of reducing various bit-twiddling tricks into fewer operations. */ static double fast_pow(double a, double b) { - int a_exp; - std::frexp(a, &a_exp); - return std::exp2(a_exp * b); + sal_math_Double u; + u.value = a; + u.w32_parts.msw = static_cast<int>(b * (static_cast<int>(u.w32_parts.msw) - 1072632447) + 1072632447); + u.w32_parts.lsw = 0; + return u.value; } ::basegfx::BColor BColorModifier_gamma::getModifiedColor(const ::basegfx::BColor& aSourceColor) const diff --git a/basegfx/test/BColorModifierTest.cxx b/basegfx/test/BColorModifierTest.cxx old mode 100755 new mode 100644 index 4a84c3662a6b..12c5af660138 --- a/basegfx/test/BColorModifierTest.cxx +++ b/basegfx/test/BColorModifierTest.cxx @@ -395,6 +395,26 @@ public: CPPUNIT_ASSERT(aBColorModifier->operator==(*aBColorModifier2)); } + // Verify that our shortcut gamma calculation produces reasonably accurate results + void testGamma() + { + for (int i = 1; i < 10; i++) + { + BColorModifier_gamma g(i); + for (int j = 0; j < 100; j++) + { + double inputRed = j / 100.0; + // this is the "slow but correct" gamma calculation + double expectedOutputRed = std::pow(inputRed, 1 / double(i)); + BColor col = g.getModifiedColor(BColor(inputRed, 0, 0)); + auto msg = OString("col is " + OString::number(inputRed) + " and gamma is " + + OString::number(i)); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(msg.getStr(), expectedOutputRed, col.getRed(), + 0.029); + } + } + } + CPPUNIT_TEST_SUITE(bcolormodifier); CPPUNIT_TEST(testGray); CPPUNIT_TEST(testInvert); @@ -407,6 +427,7 @@ public: CPPUNIT_TEST(testMatrixShift); CPPUNIT_TEST(testIdentityMatrix); CPPUNIT_TEST(testBlackAndWhite); + CPPUNIT_TEST(testGamma); CPPUNIT_TEST_SUITE_END(); }; diff --git a/include/basegfx/color/bcolormodifier.hxx b/include/basegfx/color/bcolormodifier.hxx index e74d6b43726e..f6052067767f 100644 --- a/include/basegfx/color/bcolormodifier.hxx +++ b/include/basegfx/color/bcolormodifier.hxx @@ -351,7 +351,7 @@ namespace basegfx col(r,g,b) = clamp(pow(col(r,g,b), 1.0 / gamma), 0.0, 1.0) */ - class SAL_WARN_UNUSED UNLESS_MERGELIBS(BASEGFX_DLLPUBLIC) BColorModifier_gamma final : public BColorModifier + class SAL_WARN_UNUSED BASEGFX_DLLPUBLIC BColorModifier_gamma final : public BColorModifier { private: double mfValue;