// g++ --std=c++11 test.cpp #include "test.h" #include "test.h" #include "FastLED.h" #include "fl/five_bit_hd_gamma.h" #include "assert.h" #include "math.h" #include #include #include #include #include #include #include "crgb.h" #include "fl/namespace.h" FASTLED_USING_NAMESPACE TEST_CASE("five_bit_bitshift") { const uint16_t test_data[][2][4] = { { // test case //r g b brightness {0, 0, 0, 0}, // input {0, 0, 0, 0}, // output }, { // 0 brightness brings all colors down to 0 {0xffff, 0xffff, 0xffff, 0}, {0, 0, 0, 0}, }, { // color values below 8 become 0 at max brightness {8, 7, 0, 255}, {1, 0, 0, 1}, }, { {0xffff, 0x00f0, 0x000f, 0x01}, {0x11, 0x00, 0x00, 0x01}, }, { {0x0100, 0x00f0, 0x000f, 0xff}, {0x08, 0x08, 0x00, 0x03}, }, { {0x2000, 0x1000, 0x0f00, 0x20}, {0x20, 0x10, 0x0f, 0x03}, }, { {0xffff, 0x8000, 0x4000, 0x40}, {0x81, 0x41, 0x20, 0x0f}, }, { {0xffff, 0x8000, 0x4000, 0x80}, {0x81, 0x41, 0x20, 0x1f}, }, { {0xffff, 0xffff, 0xffff, 0xff}, {0xff, 0xff, 0xff, 0x1f}, }, }; for (const auto& data : test_data) { CRGB out_color; uint8_t out_brightness; five_bit_bitshift(data[0][0], data[0][1], data[0][2], data[0][3], &out_color, &out_brightness); INFO("input red ", data[0][0], " green ", data[0][1], " blue ", data[0][2], " brightness ", data[0][3]); INFO("output red ", out_color.r, " green ", out_color.g, " blue ", out_color.b, " brightness ", out_brightness); CHECK_EQ(out_color.r, data[1][0]); CHECK_EQ(out_color.g, data[1][1]); CHECK_EQ(out_color.b, data[1][2]); CHECK_EQ(out_brightness, data[1][3]); } } TEST_CASE("__builtin_five_bit_hd_gamma_bitshift") { // NOTE: FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8 is defined for this test in CMakeLists.txt const uint8_t test_data[][2][4] = { { // test case //r g b brightness {0, 0, 0, 0}, // input {0, 0, 0, 0}, // output }, { // 0 brightness brings all colors down to 0 {255, 255, 255, 0}, {0, 0, 0, 0}, }, { {16, 16, 16, 16}, {0, 0, 0, 1}, }, { {64, 64, 64, 8}, {4, 4, 4, 1}, }, { {255, 127, 43, 1}, {17, 3, 0, 1}, }, { {255, 127, 43, 1}, {17, 3, 0, 1}, }, { {255, 127, 43, 64}, {129, 21, 1, 15}, }, { {255, 127, 43, 255}, {255, 42, 3, 31}, }, { {255, 255, 255, 255}, {255, 255, 255, 31}, }, }; for (const auto& data : test_data) { CRGB out_color; uint8_t out_brightness; __builtin_five_bit_hd_gamma_bitshift(CRGB(data[0][0], data[0][1], data[0][2]), CRGB(255, 255, 255), data[0][3], &out_color, &out_brightness); INFO("input red ", data[0][0], " green ", data[0][1], " blue ", data[0][2], " brightness ", data[0][3]); INFO("output red ", out_color.r, " green ", out_color.g, " blue ", out_color.b, " brightness ", out_brightness); CHECK_EQ(out_color.r, data[1][0]); CHECK_EQ(out_color.g, data[1][1]); CHECK_EQ(out_color.b, data[1][2]); CHECK_EQ(out_brightness, data[1][3]); } } #define CHECK_NEAR(a, b, c) CHECK_LT(abs(a - b), c) #define STRESS_TEST 1 #define PROBLEMATIC_TEST 0 // Testing allows upto 21% error between power output of WS2812 and APA102 in HD mode. // This is mostly due to the rounding errors for WS2812 when one of the channels is small // and the rest are fairly large. One component will get rounded down to 0, while in // apa mode it will bitshift to relevance. const static float TOLERANCE = 0.21; const static int NUM_TESTS = 10000; const static size_t MAX_FAILURES = 30; const static int CUTOFF = 11; // WS2812 will give bad results when one of the components is less than 10. struct Power { float power; float power_5bit; uint8_t power_5bit_u8; }; static float power_diff(Power power) { return abs(power.power - power.power_5bit); } static float power_rgb(CRGB color, uint8_t brightness) { color *= brightness; float out = color.r / 255.f + color.g / 255.f + color.b / 255.f; return out / 3.0f; } static float compute_power_5bit(CRGB color, uint8_t power_5bit, uint8_t brightness) { assert(power_5bit <= 31); float rgb_pow = power_rgb(color, brightness); float brightness_pow = (power_5bit) / 31.0f; float out = rgb_pow * brightness_pow; return out; } static float compute_power_apa102(CRGB color, uint8_t brightness, uint8_t* power_5bit) { uint16_t r16 = map8_to_16(color.r); uint16_t g16 = map8_to_16(color.g); uint16_t b16 = map8_to_16(color.b); CRGB out_colors; uint8_t v5 = 31; uint8_t post_brightness_scale = five_bit_bitshift(r16, g16, b16, brightness, &out_colors, power_5bit); float power = compute_power_5bit(out_colors, v5, post_brightness_scale); return power; } static float compute_power_ws2812(CRGB color, uint8_t brightness) { float power = power_rgb(color, brightness); return power; } static Power compute_power(uint8_t brightness8, CRGB color) { uint8_t power_5bit_u8; float power_5bit = compute_power_apa102(color, brightness8, &power_5bit_u8); float power_rgb = compute_power_ws2812(color, brightness8); return {power_rgb, power_5bit, power_5bit_u8}; } static void make_random(CRGB* color, uint8_t* brightness) { color->r = rand() % 256; color->g = rand() % 256; color->b = rand() % 256; *brightness = rand() % 256; } struct Data { CRGB color; uint8_t brightness; }; TEST_CASE("five_bit_hd_gamma_bitshift functionality") { SUBCASE("Sanity test for defines") { CHECK_EQ(FASTLED_HD_COLOR_MIXING, 1); } #if PROBLEMATIC_TEST SUBCASE("problematic test2") { // Failure, diff is 0.580777 brightness: 249 color: R: 103 G: 125 B: 236 power: 0 power_5bit: 31 CRGB color = {103, 125, 236}; uint8_t brightness = 249; problematic_test(color, brightness); FAIL("Problematic test failed"); } #endif #if STRESS_TEST SUBCASE("Randomized Power Matching Test for 5 bit power") { srand(0); // Seed the random number generator so we get consitent results. std::vector failures; for (int i = 0; i < NUM_TESTS; i++) { CRGB color; uint8_t brightness; make_random(&color, &brightness); // if one ore more of the compoents is less than 10 then skip. if (color.r < CUTOFF || color.g < CUTOFF || color.b < CUTOFF || brightness < CUTOFF) { // WS2812 does badly at this. continue; } Power result = compute_power(brightness, color); float diff = power_diff(result); if (diff > TOLERANCE) { failures.push_back({color, brightness}); while (failures.size() > MAX_FAILURES) { // failures.pop_back(); // select smallest power difference and remove it. auto it = std::min_element(failures.begin(), failures.end(), [](const Data& a, const Data& b) { Power p1 = compute_power(a.brightness, a.color); Power p2 = compute_power(b.brightness, b.color); return power_diff(p1) < power_diff(p2); }); failures.erase(it); } } } if (failures.size()) { // sort by the power difference std::sort(failures.begin(), failures.end(), [](const Data& a, const Data& b) { Power p1 = compute_power(a.brightness, a.color); Power p2 = compute_power(b.brightness, b.color); return abs(p1.power - p1.power_5bit) > abs(p2.power - p2.power_5bit); }); std::cout << "Failures:" << std::endl; for (auto& failure : failures) { Power p = compute_power(failure.brightness, failure.color); std::string color_str = "R: " + std::to_string(failure.color.r) + " G: " + std::to_string(failure.color.g) + " B: " + std::to_string(failure.color.b); std::cout << "Failure, diff is " << power_diff(p) << " brightness: " << int(failure.brightness) << " color: " << color_str << " power: " << p.power << " power_5bit: " << int(p.power_5bit_u8) << std::endl; } // FAIL("Failures found"); // make a oostream object std::ostringstream oss; oss << __FILE__ << ":" << __LINE__ << " Failures found"; FAIL(oss.str()); } } #endif }