sTodo-m5paper-client/libraries/FastLED/tests/test_apa102_hd.cpp
2025-06-30 20:47:33 +02:00

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
}