sharp_yuv: use 14b fixed-point precision for gamma

Output is <.1% difference in size, randomly.

Speed is 30-50% faster (-m 0 -sharp_yuv).
It also gives the exact same output on ARM and x86, because floats
are no longer used.

Change-Id: Id0f0aa748cc4fc0b82bac1fc5ca954775a0a1b7c
This commit is contained in:
Pascal Massimino 2018-03-23 20:17:22 +01:00
parent b2db361ca6
commit e03f0ec319

View File

@ -170,29 +170,33 @@ typedef uint16_t fixed_y_t; // unsigned type with extra SFIX precision for W
#if defined(USE_GAMMA_COMPRESSION) #if defined(USE_GAMMA_COMPRESSION)
// float variant of gamma-correction
// We use tables of different size and precision for the Rec709 / BT2020 // We use tables of different size and precision for the Rec709 / BT2020
// transfer function. // transfer function.
#define kGammaF (1./0.45) #define kGammaF (1./0.45)
static float kGammaToLinearTabF[MAX_Y_T + 1]; // size scales with Y_FIX static uint32_t kLinearToGammaTabS[kGammaTabSize + 2];
static float kLinearToGammaTabF[kGammaTabSize + 2]; #define GAMMA_TO_LINEAR_BITS 14
static volatile int kGammaTablesFOk = 0; static uint32_t kGammaToLinearTabS[MAX_Y_T + 1]; // size scales with Y_FIX
static volatile int kGammaTablesSOk = 0;
static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) { static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesS(void) {
if (!kGammaTablesFOk) { assert(2 * GAMMA_TO_LINEAR_BITS < 32); // we use uint32_t intermediate values
if (!kGammaTablesSOk) {
int v; int v;
const double norm = 1. / MAX_Y_T; const double norm = 1. / MAX_Y_T;
const double scale = 1. / kGammaTabSize; const double scale = 1. / kGammaTabSize;
const double a = 0.09929682680944; const double a = 0.09929682680944;
const double thresh = 0.018053968510807; const double thresh = 0.018053968510807;
const double final_scale = 1 << GAMMA_TO_LINEAR_BITS;
for (v = 0; v <= MAX_Y_T; ++v) { for (v = 0; v <= MAX_Y_T; ++v) {
const double g = norm * v; const double g = norm * v;
double value;
if (g <= thresh * 4.5) { if (g <= thresh * 4.5) {
kGammaToLinearTabF[v] = (float)(g / 4.5); value = g / 4.5;
} else { } else {
const double a_rec = 1. / (1. + a); const double a_rec = 1. / (1. + a);
kGammaToLinearTabF[v] = (float)pow(a_rec * (g + a), kGammaF); value = pow(a_rec * (g + a), kGammaF);
} }
kGammaToLinearTabS[v] = (uint32_t)(value * final_scale + .5);
} }
for (v = 0; v <= kGammaTabSize; ++v) { for (v = 0; v <= kGammaTabSize; ++v) {
const double g = scale * v; const double g = scale * v;
@ -202,37 +206,44 @@ static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) {
} else { } else {
value = (1. + a) * pow(g, 1. / kGammaF) - a; value = (1. + a) * pow(g, 1. / kGammaF) - a;
} }
kLinearToGammaTabF[v] = (float)(MAX_Y_T * value); // we already incorporate the 1/2 rounding constant here
kLinearToGammaTabS[v] =
(uint32_t)(MAX_Y_T * value) + (1 << GAMMA_TO_LINEAR_BITS >> 1);
} }
// to prevent small rounding errors to cause read-overflow: // to prevent small rounding errors to cause read-overflow:
kLinearToGammaTabF[kGammaTabSize + 1] = kLinearToGammaTabF[kGammaTabSize]; kLinearToGammaTabS[kGammaTabSize + 1] = kLinearToGammaTabS[kGammaTabSize];
kGammaTablesFOk = 1; kGammaTablesSOk = 1;
} }
} }
static WEBP_INLINE float GammaToLinearF(int v) { // return value has a fixed-point precision of GAMMA_TO_LINEAR_BITS
return kGammaToLinearTabF[v]; static WEBP_INLINE uint32_t GammaToLinearS(int v) {
return kGammaToLinearTabS[v];
} }
static WEBP_INLINE int LinearToGammaF(float value) { static WEBP_INLINE uint32_t LinearToGammaS(uint32_t value) {
const float v = value * kGammaTabSize; // 'value' is in GAMMA_TO_LINEAR_BITS fractional precision
const int tab_pos = (int)v; const uint32_t v = value * kGammaTabSize;
const float x = v - (float)tab_pos; // fractional part const uint32_t tab_pos = v >> GAMMA_TO_LINEAR_BITS;
const float v0 = kLinearToGammaTabF[tab_pos + 0]; // fractional part, in GAMMA_TO_LINEAR_BITS fixed-point precision
const float v1 = kLinearToGammaTabF[tab_pos + 1]; const uint32_t x = v - (tab_pos << GAMMA_TO_LINEAR_BITS); // fractional part
const float y = v1 * x + v0 * (1.f - x); // interpolate // v0 / v1 are in GAMMA_TO_LINEAR_BITS fixed-point precision (range [0..1])
return (int)(y + .5); const uint32_t v0 = kLinearToGammaTabS[tab_pos + 0];
const uint32_t v1 = kLinearToGammaTabS[tab_pos + 1];
// Final interpolation. Note that rounding is already included.
const uint32_t v2 = (v1 - v0) * x; // note: v1 >= v0.
const uint32_t result = v0 + (v2 >> GAMMA_TO_LINEAR_BITS);
return result;
} }
#else #else
static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) {} static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesS(void) {}
static WEBP_INLINE float GammaToLinearF(int v) { static WEBP_INLINE uint32_t GammaToLinearS(int v) {
const float norm = 1.f / MAX_Y_T; return (v << GAMMA_TO_LINEAR_BITS) / MAX_Y_T;
return norm * v;
} }
static WEBP_INLINE int LinearToGammaF(float value) { static WEBP_INLINE uint32_t LinearToGammaS(uint32_t value) {
return (int)(MAX_Y_T * value + .5); return (MAX_Y_T * value) >> GAMMA_TO_LINEAR_BITS;
} }
#endif // USE_GAMMA_COMPRESSION #endif // USE_GAMMA_COMPRESSION
@ -254,26 +265,22 @@ static int RGBToGray(int r, int g, int b) {
return (luma >> YUV_FIX); return (luma >> YUV_FIX);
} }
static float RGBToGrayF(float r, float g, float b) { static uint32_t ScaleDown(int a, int b, int c, int d) {
return (float)(0.2126 * r + 0.7152 * g + 0.0722 * b); const uint32_t A = GammaToLinearS(a);
} const uint32_t B = GammaToLinearS(b);
const uint32_t C = GammaToLinearS(c);
static int ScaleDown(int a, int b, int c, int d) { const uint32_t D = GammaToLinearS(d);
const float A = GammaToLinearF(a); return LinearToGammaS((A + B + C + D + 2) >> 2);
const float B = GammaToLinearF(b);
const float C = GammaToLinearF(c);
const float D = GammaToLinearF(d);
return LinearToGammaF(0.25f * (A + B + C + D));
} }
static WEBP_INLINE void UpdateW(const fixed_y_t* src, fixed_y_t* dst, int w) { static WEBP_INLINE void UpdateW(const fixed_y_t* src, fixed_y_t* dst, int w) {
int i; int i;
for (i = 0; i < w; ++i) { for (i = 0; i < w; ++i) {
const float R = GammaToLinearF(src[0 * w + i]); const uint32_t R = GammaToLinearS(src[0 * w + i]);
const float G = GammaToLinearF(src[1 * w + i]); const uint32_t G = GammaToLinearS(src[1 * w + i]);
const float B = GammaToLinearF(src[2 * w + i]); const uint32_t B = GammaToLinearS(src[2 * w + i]);
const float Y = RGBToGrayF(R, G, B); const uint32_t Y = RGBToGray(R, G, B);
dst[i] = (fixed_y_t)LinearToGammaF(Y); dst[i] = (fixed_y_t)LinearToGammaS(Y);
} }
} }
@ -863,7 +870,7 @@ static int ImportYUVAFromRGBA(const uint8_t* r_ptr,
} }
if (use_iterative_conversion) { if (use_iterative_conversion) {
InitGammaTablesF(); InitGammaTablesS();
if (!PreprocessARGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, picture)) { if (!PreprocessARGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, picture)) {
return 0; return 0;
} }