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:
@@ -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
21
basegfx/test/BColorModifierTest.cxx
Executable file → Normal 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();
|
||||
};
|
||||
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user