#include "test.h" #include "FastLED.h" #include "hsv2rgb.h" #include #include #include #include FASTLED_USING_NAMESPACE struct ErrorStats { float average; float median; float max; float min; std::vector errors; void calculate() { if (errors.empty()) { average = median = max = min = 0.0f; return; } // Sort for median calculation std::sort(errors.begin(), errors.end()); // Calculate average float sum = std::accumulate(errors.begin(), errors.end(), 0.0f); average = sum / errors.size(); // Calculate median size_t mid = errors.size() / 2; if (errors.size() % 2 == 0) { median = (errors[mid - 1] + errors[mid]) / 2.0f; } else { median = errors[mid]; } // Min and max min = errors.front(); max = errors.back(); } void print(const char* function_name) const { printf("%s Error Statistics:\n", function_name); printf(" Average: %.6f\n", average); printf(" Median: %.6f\n", median); printf(" Min: %.6f\n", min); printf(" Max: %.6f\n", max); printf(" Samples: %zu\n", errors.size()); printf("\n"); } }; // Calculate euclidean distance between two RGB colors static float calculateRGBError(const CRGB& original, const CRGB& converted) { float dr = float(original.r) - float(converted.r); float dg = float(original.g) - float(converted.g); float db = float(original.b) - float(converted.b); return sqrtf(dr*dr + dg*dg + db*db); } // Test a specific conversion function with RGB -> HSV -> RGB round trip template static ErrorStats testConversionFunction(ConversionFunc hsv2rgb_func, const char* func_name) { (void)func_name; // Suppress unused parameter warning ErrorStats stats; // Test a comprehensive set of RGB colors // We'll test every 8th value to get good coverage without taking too long const int step = 8; for (int r = 0; r < 256; r += step) { for (int g = 0; g < 256; g += step) { for (int b = 0; b < 256; b += step) { // Original RGB color CRGB original_rgb(r, g, b); // Convert RGB -> HSV CHSV hsv = rgb2hsv_approximate(original_rgb); // Convert HSV -> RGB using the test function CRGB converted_rgb; hsv2rgb_func(hsv, converted_rgb); // Calculate error float error = calculateRGBError(original_rgb, converted_rgb); stats.errors.push_back(error); } } } stats.calculate(); return stats; } TEST_CASE("HSV to RGB Conversion Accuracy Comparison") { printf("\n=== HSV to RGB Conversion Accuracy Test ===\n"); printf("Testing RGB -> HSV -> RGB round-trip accuracy\n"); printf("Sampling every 8th RGB value for comprehensive coverage\n\n"); // Test all three conversion functions ErrorStats rainbow_stats = testConversionFunction( [](const CHSV& hsv, CRGB& rgb) { hsv2rgb_rainbow(hsv, rgb); }, "hsv2rgb_rainbow" ); ErrorStats spectrum_stats = testConversionFunction( [](const CHSV& hsv, CRGB& rgb) { hsv2rgb_spectrum(hsv, rgb); }, "hsv2rgb_spectrum" ); ErrorStats fullspectrum_stats = testConversionFunction( [](const CHSV& hsv, CRGB& rgb) { hsv2rgb_fullspectrum(hsv, rgb); }, "hsv2rgb_fullspectrum" ); // Print results rainbow_stats.print("hsv2rgb_rainbow"); spectrum_stats.print("hsv2rgb_spectrum"); fullspectrum_stats.print("hsv2rgb_fullspectrum"); // Print comparison printf("=== Error Comparison ===\n"); printf("Function Average Median Min Max\n"); printf("hsv2rgb_rainbow %.6f %.6f %.6f %.6f\n", rainbow_stats.average, rainbow_stats.median, rainbow_stats.min, rainbow_stats.max); printf("hsv2rgb_spectrum %.6f %.6f %.6f %.6f\n", spectrum_stats.average, spectrum_stats.median, spectrum_stats.min, spectrum_stats.max); printf("hsv2rgb_fullspectrum%.6f %.6f %.6f %.6f\n", fullspectrum_stats.average, fullspectrum_stats.median, fullspectrum_stats.min, fullspectrum_stats.max); printf("\n"); // Find the best performing function for each metric std::vector> avg_results = { {rainbow_stats.average, "rainbow"}, {spectrum_stats.average, "spectrum"}, {fullspectrum_stats.average, "fullspectrum"} }; std::sort(avg_results.begin(), avg_results.end()); std::vector> median_results = { {rainbow_stats.median, "rainbow"}, {spectrum_stats.median, "spectrum"}, {fullspectrum_stats.median, "fullspectrum"} }; std::sort(median_results.begin(), median_results.end()); std::vector> max_results = { {rainbow_stats.max, "rainbow"}, {spectrum_stats.max, "spectrum"}, {fullspectrum_stats.max, "fullspectrum"} }; std::sort(max_results.begin(), max_results.end()); printf("=== Best Performance Rankings ===\n"); printf("Lowest Average Error: %s (%.6f)\n", avg_results[0].second, avg_results[0].first); printf("Lowest Median Error: %s (%.6f)\n", median_results[0].second, median_results[0].first); printf("Lowest Max Error: %s (%.6f)\n", max_results[0].second, max_results[0].first); printf("\n"); // Basic sanity checks - errors should be reasonable for RGB->HSV->RGB round-trip // Note: RGB->HSV->RGB conversion is inherently lossy due to the approximation function CHECK_LT(rainbow_stats.average, 150.0f); // Average error should be reasonable CHECK_LT(spectrum_stats.average, 150.0f); CHECK_LT(fullspectrum_stats.average, 150.0f); // Max error can exceed single RGB channel distance due to euclidean distance calculation CHECK_LT(rainbow_stats.max, 500.0f); // Max error should be reasonable CHECK_LT(spectrum_stats.max, 500.0f); CHECK_LT(fullspectrum_stats.max, 500.0f); CHECK_GE(rainbow_stats.min, 0.0f); // Min error should be non-negative CHECK_GE(spectrum_stats.min, 0.0f); CHECK_GE(fullspectrum_stats.min, 0.0f); // Verify rainbow has the best (lowest) average error CHECK_LT(rainbow_stats.average, spectrum_stats.average); CHECK_LT(rainbow_stats.average, fullspectrum_stats.average); } TEST_CASE("HSV to RGB Conversion - Specific Color Tests") { printf("\n=== Specific Color Conversion Tests ===\n"); // Test some specific colors known to be challenging struct TestColor { CRGB rgb; const char* name; }; std::vector test_colors = { {{255, 0, 0}, "Pure Red"}, {{0, 255, 0}, "Pure Green"}, {{0, 0, 255}, "Pure Blue"}, {{255, 255, 0}, "Yellow"}, {{255, 0, 255}, "Magenta"}, {{0, 255, 255}, "Cyan"}, {{255, 255, 255}, "White"}, {{0, 0, 0}, "Black"}, {{128, 128, 128}, "Gray"}, {{255, 128, 0}, "Orange"}, {{128, 0, 255}, "Purple"}, {{255, 192, 203}, "Pink"} }; printf("Color Original RGB Rainbow RGB Spectrum RGB FullSpectrum RGB\n"); printf("------------- ----------- ----------- ------------ ----------------\n"); for (const auto& test : test_colors) { CHSV hsv = rgb2hsv_approximate(test.rgb); CRGB rainbow_rgb, spectrum_rgb, fullspectrum_rgb; hsv2rgb_rainbow(hsv, rainbow_rgb); hsv2rgb_spectrum(hsv, spectrum_rgb); hsv2rgb_fullspectrum(hsv, fullspectrum_rgb); printf("%-15s (%3d,%3d,%3d) (%3d,%3d,%3d) (%3d,%3d,%3d) (%3d,%3d,%3d)\n", test.name, test.rgb.r, test.rgb.g, test.rgb.b, rainbow_rgb.r, rainbow_rgb.g, rainbow_rgb.b, spectrum_rgb.r, spectrum_rgb.g, spectrum_rgb.b, fullspectrum_rgb.r, fullspectrum_rgb.g, fullspectrum_rgb.b); } printf("\n"); } TEST_CASE("HSV to RGB Conversion - Hue Sweep Test") { printf("\n=== Hue Sweep Conversion Test ===\n"); printf("Testing full hue range at maximum saturation and brightness\n"); printf("Hue Rainbow RGB Spectrum RGB FullSpectrum RGB\n"); printf("---- ----------- ------------ ----------------\n"); // Test hue sweep at full saturation and brightness for (int hue = 0; hue < 256; hue += 1) { CHSV hsv(hue, 255, 255); CRGB rainbow_rgb, spectrum_rgb, fullspectrum_rgb; hsv2rgb_rainbow(hsv, rainbow_rgb); hsv2rgb_spectrum(hsv, spectrum_rgb); hsv2rgb_fullspectrum(hsv, fullspectrum_rgb); printf("%3d (%3d,%3d,%3d) (%3d,%3d,%3d) (%3d,%3d,%3d)\n", hue, rainbow_rgb.r, rainbow_rgb.g, rainbow_rgb.b, spectrum_rgb.r, spectrum_rgb.g, spectrum_rgb.b, fullspectrum_rgb.r, fullspectrum_rgb.g, fullspectrum_rgb.b); } printf("\n"); }