|  | #include "Test.h" | 
|  | #include "SkColor.h" | 
|  |  | 
|  | #define ASSERT(x) REPORTER_ASSERT(r, x) | 
|  |  | 
|  | static uint8_t double_to_u8(double d) { | 
|  | SkASSERT(d >= 0); | 
|  | SkASSERT(d < 256); | 
|  | return uint8_t(d); | 
|  | } | 
|  |  | 
|  | // All algorithms we're testing have this interface. | 
|  | // We want a single channel blend, src over dst, assuming src is premultiplied by srcAlpha. | 
|  | typedef uint8_t(*Blend)(uint8_t dst, uint8_t src, uint8_t srcAlpha); | 
|  |  | 
|  | // This is our golden algorithm. | 
|  | static uint8_t blend_double_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | SkASSERT(src <= srcAlpha); | 
|  | return double_to_u8(0.5 + src + dst * (255.0 - srcAlpha) / 255.0); | 
|  | } | 
|  |  | 
|  | static uint8_t abs_diff(uint8_t a, uint8_t b) { | 
|  | const int diff = a - b; | 
|  | return diff > 0 ? diff : -diff; | 
|  | } | 
|  |  | 
|  | static void test(skiatest::Reporter* r, int maxDiff, Blend algorithm, | 
|  | uint8_t dst, uint8_t src, uint8_t alpha) { | 
|  | const uint8_t golden = blend_double_round(dst, src, alpha); | 
|  | const uint8_t  blend =          algorithm(dst, src, alpha); | 
|  | if (abs_diff(blend, golden) > maxDiff) { | 
|  | SkDebugf("dst %02x, src %02x, alpha %02x, |%02x - %02x| > %d\n", | 
|  | dst, src, alpha, blend, golden, maxDiff); | 
|  | ASSERT(abs_diff(blend, golden) <= maxDiff); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Exhaustively compare an algorithm against our golden, for a given alpha. | 
|  | static void test_alpha(skiatest::Reporter* r, uint8_t alpha, int maxDiff, Blend algorithm) { | 
|  | SkASSERT(maxDiff >= 0); | 
|  |  | 
|  | for (unsigned src = 0; src <= alpha; src++) { | 
|  | for (unsigned dst = 0; dst < 256; dst++) { | 
|  | test(r, maxDiff, algorithm, dst, src, alpha); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Exhaustively compare an algorithm against our golden, for a given dst. | 
|  | static void test_dst(skiatest::Reporter* r, uint8_t dst, int maxDiff, Blend algorithm) { | 
|  | SkASSERT(maxDiff >= 0); | 
|  |  | 
|  | for (unsigned alpha = 0; alpha < 256; alpha++) { | 
|  | for (unsigned src = 0; src <= alpha; src++) { | 
|  | test(r, maxDiff, algorithm, dst, src, alpha); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static uint8_t blend_double_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | return double_to_u8(src + dst * (255.0 - srcAlpha) / 255.0); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_float_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | return double_to_u8(src + dst * (255.0f - srcAlpha) / 255.0f); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_float_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | return double_to_u8(0.5f + src + dst * (255.0f - srcAlpha) / 255.0f); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_255_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint16_t invAlpha = 255 - srcAlpha; | 
|  | const uint16_t product = dst * invAlpha; | 
|  | return src + (product >> 8); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_255_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint16_t invAlpha = 255 - srcAlpha; | 
|  | const uint16_t product = dst * invAlpha + 128; | 
|  | return src + (product >> 8); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_256_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint16_t invAlpha = 256 - (srcAlpha + (srcAlpha >> 7)); | 
|  | const uint16_t product = dst * invAlpha; | 
|  | return src + (product >> 8); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_256_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint16_t invAlpha = 256 - (srcAlpha + (srcAlpha >> 7)); | 
|  | const uint16_t product = dst * invAlpha + 128; | 
|  | return src + (product >> 8); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_256_round_alt(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint8_t invAlpha8 = 255 - srcAlpha; | 
|  | const uint16_t invAlpha = invAlpha8 + (invAlpha8 >> 7); | 
|  | const uint16_t product = dst * invAlpha + 128; | 
|  | return src + (product >> 8); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_256_plus1_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint16_t invAlpha = 256 - (srcAlpha + 1); | 
|  | const uint16_t product = dst * invAlpha; | 
|  | return src + (product >> 8); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_256_plus1_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint16_t invAlpha = 256 - (srcAlpha + 1); | 
|  | const uint16_t product = dst * invAlpha + 128; | 
|  | return src + (product >> 8); | 
|  | } | 
|  |  | 
|  | static uint8_t blend_perfect(uint8_t dst, uint8_t src, uint8_t srcAlpha) { | 
|  | const uint8_t invAlpha = 255 - srcAlpha; | 
|  | const uint16_t product = dst * invAlpha + 128; | 
|  | return src + ((product + (product >> 8)) >> 8); | 
|  | } | 
|  |  | 
|  |  | 
|  | // We want 0 diff whenever src is fully transparent. | 
|  | DEF_TEST(Blend_alpha_0x00, r) { | 
|  | const uint8_t alpha = 0x00; | 
|  |  | 
|  | // GOOD | 
|  | test_alpha(r, alpha, 0, blend_256_round); | 
|  | test_alpha(r, alpha, 0, blend_256_round_alt); | 
|  | test_alpha(r, alpha, 0, blend_256_trunc); | 
|  | test_alpha(r, alpha, 0, blend_double_trunc); | 
|  | test_alpha(r, alpha, 0, blend_float_round); | 
|  | test_alpha(r, alpha, 0, blend_float_trunc); | 
|  | test_alpha(r, alpha, 0, blend_perfect); | 
|  |  | 
|  | // BAD | 
|  | test_alpha(r, alpha, 1, blend_255_round); | 
|  | test_alpha(r, alpha, 1, blend_255_trunc); | 
|  | test_alpha(r, alpha, 1, blend_256_plus1_round); | 
|  | test_alpha(r, alpha, 1, blend_256_plus1_trunc); | 
|  | } | 
|  |  | 
|  | // We want 0 diff whenever dst is 0. | 
|  | DEF_TEST(Blend_dst_0x00, r) { | 
|  | const uint8_t dst = 0x00; | 
|  |  | 
|  | // GOOD | 
|  | test_dst(r, dst, 0, blend_255_round); | 
|  | test_dst(r, dst, 0, blend_255_trunc); | 
|  | test_dst(r, dst, 0, blend_256_plus1_round); | 
|  | test_dst(r, dst, 0, blend_256_plus1_trunc); | 
|  | test_dst(r, dst, 0, blend_256_round); | 
|  | test_dst(r, dst, 0, blend_256_round_alt); | 
|  | test_dst(r, dst, 0, blend_256_trunc); | 
|  | test_dst(r, dst, 0, blend_double_trunc); | 
|  | test_dst(r, dst, 0, blend_float_round); | 
|  | test_dst(r, dst, 0, blend_float_trunc); | 
|  | test_dst(r, dst, 0, blend_perfect); | 
|  |  | 
|  | // BAD | 
|  | } | 
|  |  | 
|  | // We want 0 diff whenever src is fully opaque. | 
|  | DEF_TEST(Blend_alpha_0xFF, r) { | 
|  | const uint8_t alpha = 0xFF; | 
|  |  | 
|  | // GOOD | 
|  | test_alpha(r, alpha, 0, blend_255_round); | 
|  | test_alpha(r, alpha, 0, blend_255_trunc); | 
|  | test_alpha(r, alpha, 0, blend_256_plus1_round); | 
|  | test_alpha(r, alpha, 0, blend_256_plus1_trunc); | 
|  | test_alpha(r, alpha, 0, blend_256_round); | 
|  | test_alpha(r, alpha, 0, blend_256_round_alt); | 
|  | test_alpha(r, alpha, 0, blend_256_trunc); | 
|  | test_alpha(r, alpha, 0, blend_double_trunc); | 
|  | test_alpha(r, alpha, 0, blend_float_round); | 
|  | test_alpha(r, alpha, 0, blend_float_trunc); | 
|  | test_alpha(r, alpha, 0, blend_perfect); | 
|  |  | 
|  | // BAD | 
|  | } | 
|  |  | 
|  | // We want 0 diff whenever dst is 0xFF. | 
|  | DEF_TEST(Blend_dst_0xFF, r) { | 
|  | const uint8_t dst = 0xFF; | 
|  |  | 
|  | // GOOD | 
|  | test_dst(r, dst, 0, blend_256_round); | 
|  | test_dst(r, dst, 0, blend_256_round_alt); | 
|  | test_dst(r, dst, 0, blend_double_trunc); | 
|  | test_dst(r, dst, 0, blend_float_round); | 
|  | test_dst(r, dst, 0, blend_float_trunc); | 
|  | test_dst(r, dst, 0, blend_perfect); | 
|  |  | 
|  | // BAD | 
|  | test_dst(r, dst, 1, blend_255_round); | 
|  | test_dst(r, dst, 1, blend_255_trunc); | 
|  | test_dst(r, dst, 1, blend_256_plus1_round); | 
|  | test_dst(r, dst, 1, blend_256_plus1_trunc); | 
|  | test_dst(r, dst, 1, blend_256_trunc); | 
|  | } | 
|  |  | 
|  | // We'd like diff <= 1 everywhere. | 
|  | DEF_TEST(Blend_alpha_Exhaustive, r) { | 
|  | for (unsigned alpha = 0; alpha < 256; alpha++) { | 
|  | // PERFECT | 
|  | test_alpha(r, alpha, 0, blend_float_round); | 
|  | test_alpha(r, alpha, 0, blend_perfect); | 
|  |  | 
|  | // GOOD | 
|  | test_alpha(r, alpha, 1, blend_255_round); | 
|  | test_alpha(r, alpha, 1, blend_256_plus1_round); | 
|  | test_alpha(r, alpha, 1, blend_256_round); | 
|  | test_alpha(r, alpha, 1, blend_256_round_alt); | 
|  | test_alpha(r, alpha, 1, blend_256_trunc); | 
|  | test_alpha(r, alpha, 1, blend_double_trunc); | 
|  | test_alpha(r, alpha, 1, blend_float_trunc); | 
|  |  | 
|  | // BAD | 
|  | test_alpha(r, alpha, 2, blend_255_trunc); | 
|  | test_alpha(r, alpha, 2, blend_256_plus1_trunc); | 
|  | } | 
|  | } | 
|  |  | 
|  | // We'd like diff <= 1 everywhere. | 
|  | DEF_TEST(Blend_dst_Exhaustive, r) { | 
|  | for (unsigned dst = 0; dst < 256; dst++) { | 
|  | // PERFECT | 
|  | test_dst(r, dst, 0, blend_float_round); | 
|  | test_dst(r, dst, 0, blend_perfect); | 
|  |  | 
|  | // GOOD | 
|  | test_dst(r, dst, 1, blend_255_round); | 
|  | test_dst(r, dst, 1, blend_256_plus1_round); | 
|  | test_dst(r, dst, 1, blend_256_round); | 
|  | test_dst(r, dst, 1, blend_256_round_alt); | 
|  | test_dst(r, dst, 1, blend_256_trunc); | 
|  | test_dst(r, dst, 1, blend_double_trunc); | 
|  | test_dst(r, dst, 1, blend_float_trunc); | 
|  |  | 
|  | // BAD | 
|  | test_dst(r, dst, 2, blend_255_trunc); | 
|  | test_dst(r, dst, 2, blend_256_plus1_trunc); | 
|  | } | 
|  | } | 
|  | // Overall summary: | 
|  | // PERFECT | 
|  | //  blend_double_round | 
|  | //  blend_float_round | 
|  | //  blend_perfect | 
|  | // GOOD ENOUGH | 
|  | //  blend_double_trunc | 
|  | //  blend_float_trunc | 
|  | //  blend_256_round | 
|  | //  blend_256_round_alt | 
|  | // NOT GOOD ENOUGH | 
|  | //  all others | 
|  | // | 
|  | //  Algorithms that make sense to use in Skia: blend_256_round, blend_256_round_alt, blend_perfect |