283 lines
8.2 KiB
C++
283 lines
8.2 KiB
C++
|
|
// 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 <ctime>
|
|
#include <cstdlib>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
#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<Data> 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
|
|
|
|
}
|
|
|