Files

393 lines
17 KiB
C++

// g++ --std=c++11 test.cpp
#include "test.h"
#include "fl/hsv16.h"
#include "fl/math.h"
#include "lib8tion/intmap.h"
using namespace fl;
TEST_CASE("RGB to HSV16 to RGB") {
SUBCASE("Primary Colors - Good Conversion") {
// Test colors that convert well with HSV16
// Pure red - perfect conversion - expect exact match
const int red_tolerance = 0; // Reduced from 1 to 0 - try for perfect
CRGB red(255, 0, 0);
HSV16 hsv_red(red);
CRGB red_result = hsv_red.ToRGB();
CHECK_CLOSE(red_result.r, red.r, red_tolerance);
CHECK_CLOSE(red_result.g, red.g, red_tolerance);
CHECK_CLOSE(red_result.b, red.b, red_tolerance);
// Pure green - try for even better accuracy
const int green_tolerance = 0; // Reduced from 2 to 0 - try for perfect!
CRGB green(0, 255, 0);
HSV16 hsv_green(green);
CRGB green_result = hsv_green.ToRGB();
CHECK_CLOSE(green_result.r, green.r, green_tolerance);
CHECK_CLOSE(green_result.g, green.g, green_tolerance);
CHECK_CLOSE(green_result.b, green.b, green_tolerance);
// Pure blue - try for even better accuracy
const int blue_tolerance = 0; // Reduced from 2 to 0 - try for perfect!
CRGB blue(0, 0, 255);
HSV16 hsv_blue(blue);
CRGB blue_result = hsv_blue.ToRGB();
CHECK_CLOSE(blue_result.r, blue.r, blue_tolerance);
CHECK_CLOSE(blue_result.g, blue.g, blue_tolerance);
CHECK_CLOSE(blue_result.b, blue.b, blue_tolerance);
// Test black - perfect conversion expected
CRGB black(0, 0, 0);
HSV16 hsv_black(black);
CRGB black_result = hsv_black.ToRGB();
CHECK(black_result.r == black.r);
CHECK(black_result.g == black.g);
CHECK(black_result.b == black.b);
}
SUBCASE("White and Grayscale - Good Conversion") {
// Test white - try for perfect accuracy
const int white_tolerance = 0; // Reduced from 1 to 0 - try for perfect!
CRGB white(255, 255, 255);
HSV16 hsv_white(white);
CRGB white_result = hsv_white.ToRGB();
CHECK_CLOSE(white_result.r, white.r, white_tolerance);
CHECK_CLOSE(white_result.g, white.g, white_tolerance);
CHECK_CLOSE(white_result.b, white.b, white_tolerance);
// Test various shades of gray - some require tolerance 1
const int gray_tolerance =
1; // Gray128 and Gray200 are off by exactly 1
CRGB gray50(50, 50, 50);
HSV16 hsv_gray50(gray50);
CRGB gray50_result = hsv_gray50.ToRGB();
CHECK_CLOSE(gray50_result.r, gray50.r,
0); // Gray50 is perfect - use tolerance 0
CHECK_CLOSE(gray50_result.g, gray50.g, 0);
CHECK_CLOSE(gray50_result.b, gray50.b, 0);
CRGB gray128(128, 128, 128);
HSV16 hsv_gray128(gray128);
CRGB gray128_result = hsv_gray128.ToRGB();
CHECK_CLOSE(gray128_result.r, gray128.r,
gray_tolerance); // Gray128 needs tolerance 1
CHECK_CLOSE(gray128_result.g, gray128.g, gray_tolerance);
CHECK_CLOSE(gray128_result.b, gray128.b, gray_tolerance);
CRGB gray200(200, 200, 200);
HSV16 hsv_gray200(gray200);
CRGB gray200_result = hsv_gray200.ToRGB();
CHECK_CLOSE(gray200_result.r, gray200.r,
gray_tolerance); // Gray200 needs tolerance 1
CHECK_CLOSE(gray200_result.g, gray200.g, gray_tolerance);
CHECK_CLOSE(gray200_result.b, gray200.b, gray_tolerance);
}
SUBCASE("HSV16 Constructor Values") {
// Test direct HSV16 construction with known values
const int direct_construction_tolerance =
0; // Reduced from 2 to 0 - try for perfect!
HSV16 hsv_red_direct(0, 65535, 65535); // Red: H=0, S=max, V=max
CRGB red_direct_result = hsv_red_direct.ToRGB();
CHECK(red_direct_result.r >=
255 - direct_construction_tolerance); // Should be close to 255
CHECK(red_direct_result.g <=
direct_construction_tolerance); // Should be close to 0
CHECK(red_direct_result.b <=
direct_construction_tolerance); // Should be close to 0
HSV16 hsv_green_direct(21845, 65535,
65535); // Green: H=1/3*65535, S=max, V=max
CRGB green_direct_result = hsv_green_direct.ToRGB();
CHECK(green_direct_result.r <=
direct_construction_tolerance); // Should be close to 0
CHECK(green_direct_result.g >=
255 - direct_construction_tolerance); // Should be close to 255
CHECK(green_direct_result.b <=
direct_construction_tolerance); // Should be close to 0
HSV16 hsv_blue_direct(43690, 65535,
65535); // Blue: H=2/3*65535, S=max, V=max
CRGB blue_direct_result = hsv_blue_direct.ToRGB();
CHECK(blue_direct_result.r <=
direct_construction_tolerance); // Should be close to 0
CHECK(blue_direct_result.g <=
direct_construction_tolerance); // Should be close to 0
CHECK(blue_direct_result.b >=
255 - direct_construction_tolerance); // Should be close to 255
// Test zero saturation (should produce grayscale)
const int grayscale_direct_tolerance = 0; // Keep at 0 - already perfect
HSV16 hsv_gray_direct(32768, 0,
32768); // Any hue, no saturation, half value
CRGB gray_direct_result = hsv_gray_direct.ToRGB();
CHECK_CLOSE(gray_direct_result.r, gray_direct_result.g,
grayscale_direct_tolerance);
CHECK_CLOSE(gray_direct_result.g, gray_direct_result.b,
grayscale_direct_tolerance);
CHECK(gray_direct_result.r >=
128 - 1); // Reduced from 2 to 1 - even tighter
CHECK(gray_direct_result.r <= 128 + 1);
}
SUBCASE("Secondary Colors - Good Conversion") {
// These secondary colors should now convert accurately with the fixed
// HSV16 implementation
// Yellow should preserve both red and green components
const int yellow_tolerance =
0; // Reduced from 1 to 0 - perfect conversion!
CRGB yellow(255, 255, 0);
HSV16 hsv_yellow(yellow);
CRGB yellow_result = hsv_yellow.ToRGB();
CHECK_CLOSE(yellow_result.r, yellow.r,
yellow_tolerance); // Red should be preserved
CHECK_CLOSE(yellow_result.g, yellow.g,
yellow_tolerance); // Green should be preserved
CHECK_CLOSE(yellow_result.b, yellow.b,
yellow_tolerance); // Blue should stay 0
// Cyan should preserve both green and blue components
const int cyan_tolerance =
0; // Reduced from 1 to 0 - perfect conversion!
CRGB cyan(0, 255, 255);
HSV16 hsv_cyan(cyan);
CRGB cyan_result = hsv_cyan.ToRGB();
CHECK_CLOSE(cyan_result.r, cyan.r, cyan_tolerance); // Red should stay 0
CHECK_CLOSE(cyan_result.g, cyan.g,
cyan_tolerance); // Green should be preserved
CHECK_CLOSE(cyan_result.b, cyan.b,
cyan_tolerance); // Blue should be preserved
// Magenta should preserve both red and blue components
const int magenta_tolerance =
0; // Reduced from 1 to 0 - perfect conversion!
CRGB magenta(255, 0, 255);
HSV16 hsv_magenta(magenta);
CRGB magenta_result = hsv_magenta.ToRGB();
CHECK_CLOSE(magenta_result.r, magenta.r,
magenta_tolerance); // Red should be preserved
CHECK_CLOSE(magenta_result.g, magenta.g,
magenta_tolerance); // Green should stay 0
CHECK_CLOSE(magenta_result.b, magenta.b,
magenta_tolerance); // Blue should be preserved
}
SUBCASE("Low-Value Problematic Colors") {
// Test very dark colors that are known to be problematic for HSV
// conversion These colors often reveal quantization and rounding issues
// Very dark red - near black but not black
const int dark_primary_tolerance = 0; // Try for perfect conversion
CRGB dark_red(10, 0, 0);
HSV16 hsv_dark_red(dark_red);
CRGB dark_red_result = hsv_dark_red.ToRGB();
CHECK_CLOSE(dark_red_result.r, dark_red.r, dark_primary_tolerance);
CHECK_CLOSE(dark_red_result.g, dark_red.g, dark_primary_tolerance);
CHECK_CLOSE(dark_red_result.b, dark_red.b, dark_primary_tolerance);
// Very dark green
CRGB dark_green(0, 10, 0);
HSV16 hsv_dark_green(dark_green);
CRGB dark_green_result = hsv_dark_green.ToRGB();
CHECK_CLOSE(dark_green_result.r, dark_green.r, dark_primary_tolerance);
CHECK_CLOSE(dark_green_result.g, dark_green.g, dark_primary_tolerance);
CHECK_CLOSE(dark_green_result.b, dark_green.b, dark_primary_tolerance);
// Very dark blue
CRGB dark_blue(0, 0, 10);
HSV16 hsv_dark_blue(dark_blue);
CRGB dark_blue_result = hsv_dark_blue.ToRGB();
CHECK_CLOSE(dark_blue_result.r, dark_blue.r, dark_primary_tolerance);
CHECK_CLOSE(dark_blue_result.g, dark_blue.g, dark_primary_tolerance);
CHECK_CLOSE(dark_blue_result.b, dark_blue.b, dark_primary_tolerance);
// Barely visible gray - single digit values
const int barely_visible_tolerance = 0; // Try for perfect conversion
CRGB barely_gray1(1, 1, 1);
HSV16 hsv_barely_gray1(barely_gray1);
CRGB barely_gray1_result = hsv_barely_gray1.ToRGB();
CHECK_CLOSE(barely_gray1_result.r, barely_gray1.r,
barely_visible_tolerance);
CHECK_CLOSE(barely_gray1_result.g, barely_gray1.g,
barely_visible_tolerance);
CHECK_CLOSE(barely_gray1_result.b, barely_gray1.b,
barely_visible_tolerance);
CRGB barely_gray5(5, 5, 5);
HSV16 hsv_barely_gray5(barely_gray5);
CRGB barely_gray5_result = hsv_barely_gray5.ToRGB();
CHECK_CLOSE(barely_gray5_result.r, barely_gray5.r,
barely_visible_tolerance);
CHECK_CLOSE(barely_gray5_result.g, barely_gray5.g,
barely_visible_tolerance);
CHECK_CLOSE(barely_gray5_result.b, barely_gray5.b,
barely_visible_tolerance);
// Low saturation, low value - muddy browns/grays
const int muddy_tolerance = 1; // These may need tolerance 1
CRGB muddy_brown(15, 10, 8);
HSV16 hsv_muddy_brown(muddy_brown);
CRGB muddy_brown_result = hsv_muddy_brown.ToRGB();
CHECK_CLOSE(muddy_brown_result.r, muddy_brown.r, muddy_tolerance);
CHECK_CLOSE(muddy_brown_result.g, muddy_brown.g, muddy_tolerance);
CHECK_CLOSE(muddy_brown_result.b, muddy_brown.b, muddy_tolerance);
// Edge case: slightly unequal very dark values
CRGB dark_unequal(3, 2, 1);
HSV16 hsv_dark_unequal(dark_unequal);
CRGB dark_unequal_result = hsv_dark_unequal.ToRGB();
CHECK_CLOSE(dark_unequal_result.r, dark_unequal.r, muddy_tolerance);
CHECK_CLOSE(dark_unequal_result.g, dark_unequal.g, muddy_tolerance);
CHECK_CLOSE(dark_unequal_result.b, dark_unequal.b, muddy_tolerance);
// Very dark but colorful - low value, high saturation
CRGB dark_saturated_red(20, 1, 1);
HSV16 hsv_dark_saturated_red(dark_saturated_red);
CRGB dark_saturated_red_result = hsv_dark_saturated_red.ToRGB();
CHECK_CLOSE(dark_saturated_red_result.r, dark_saturated_red.r,
dark_primary_tolerance);
CHECK_CLOSE(dark_saturated_red_result.g, dark_saturated_red.g,
dark_primary_tolerance);
CHECK_CLOSE(dark_saturated_red_result.b, dark_saturated_red.b,
dark_primary_tolerance);
}
}
TEST_CASE("Exhaustive round trip") {
const int step = 4;
for (int r = 0; r < 256; r+=step) {
for (int g = 0; g < 256; g+=step) {
for (int b = 0; b < 256; b+=step) {
CRGB rgb(r, g, b);
HSV16 hsv(rgb);
CRGB rgb_result = hsv.ToRGB();
REQUIRE_CLOSE(rgb_result.r, rgb.r, 1);
REQUIRE_CLOSE(rgb_result.g, rgb.g, 1);
REQUIRE_CLOSE(rgb_result.b, rgb.b, 1);
}
}
}
}
#define TEST_VIDEO_RGB_HUE_PRESERVATION(color, hue_tolerance) \
do { \
HSV16 hsv_original(color); \
uint16_t original_hue = hsv_original.h; \
\
CRGB video_result = hsv_original.colorBoost(); \
HSV16 hsv_video_result(video_result); \
uint16_t result_hue = hsv_video_result.h; \
/* Special handling for hue around 0 (red) - check for wraparound */ \
uint16_t hue_diff = (original_hue > result_hue) \
? (original_hue - result_hue) \
: (result_hue - original_hue); \
/* Also check wraparound case (difference near 65535) */ \
uint16_t hue_diff_wraparound = 65535 - hue_diff; \
uint16_t min_hue_diff = \
(hue_diff < hue_diff_wraparound) ? hue_diff : hue_diff_wraparound; \
\
uint8_t hue_diff_8bit = map16_to_8(min_hue_diff); \
\
CHECK_LE(hue_diff_8bit, hue_tolerance); \
} while(0)
TEST_CASE("colorBoost() preserves hue - easy cases") {
// Helper function to test colorBoost() hue preservation
// Test that colorBoost() preserves the hue while applying gamma
// correction to saturation. Each color uses a fine-grained tolerance based
// on empirically observed maximum hue differences.
SUBCASE("Orange - Low hue error") {
// Test with a vibrant orange color - wraparound helped reduce tolerance
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 128, 0), 0);
}
SUBCASE("Blue-Green - Moderate hue error") {
// Test with a blue-green color - exactly 14 units max error observed
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(0, 200, 150), 0);
}
SUBCASE("Purple - Very low hue error") {
// Test with a purple color - exactly 4 units max error observed
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(180, 50, 200), 0);
}
SUBCASE("Warm Yellow - Highest hue error case") {
// Test with a warm yellow color - this is the worst case with exactly
// 47 units max error (empirically determined as the absolute worst case
// across all test colors)
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 220, 80), 0);
}
SUBCASE("Bright Red - Wraparound case") {
// Test edge case: Very saturated red (hue around 0) - handle wraparound
// Special case due to hue wraparound at 0/65535 boundary
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 30, 30), 0);
}
}
TEST_CASE("colorBoost() preserves hue - hard cases") {
SUBCASE("Low Saturation Colors - Hue Instability") {
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(130, 128, 125), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(100, 98, 102), 3);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(85, 87, 83), 0);
}
SUBCASE("Very Dark Colors - Low Value Instability") {
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(15, 10, 8), 1);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(12, 8, 20), 1);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(8, 15, 12), 1);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(20, 12, 8), 1);
}
SUBCASE("Hue Boundary Colors - Transition Regions") {
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 64, 0), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(192, 255, 0), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(0, 255, 128), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(0, 128, 255), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(128, 0, 255), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 0, 128), 0);
}
SUBCASE("Medium Saturation, Medium Value - Gamma Sensitive") {
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(180, 120, 60), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(120, 180, 90), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(90, 120, 180), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(180, 90, 150), 0);
}
SUBCASE("Single Component Dominant - Extreme Ratios") {
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(250, 10, 5), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(8, 240, 12), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(15, 8, 245), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(200, 200, 8), 0);
}
SUBCASE("Pastel Colors - High Value, Low Saturation") {
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 200, 200), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(200, 255, 200), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(200, 200, 255), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 255, 200), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(255, 200, 255), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(200, 255, 255), 0);
}
SUBCASE("Problematic RGB Combinations - Known Difficult Cases") {
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(77, 150, 200), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(200, 150, 77), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(150, 77, 200), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(33, 66, 99), 0);
TEST_VIDEO_RGB_HUE_PRESERVATION(CRGB(99, 33, 66), 0);
}
}