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.grandin@collabora.co.uk>
Tested-by: Jenkins
This commit is contained in:
Noel Grandin
2025-04-25 13:49:14 +02:00
parent 998ab8266a
commit c03d752c61
3 changed files with 32 additions and 19 deletions

View File

@@ -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.
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]
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)
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

21
basegfx/test/BColorModifierTest.cxx Executable file → Normal file
View File

@@ -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();
};

View File

@@ -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;