diff options
author | Daniel Garcia <danielgarcia@gmail.com> | 2015-01-05 09:49:26 +0300 |
---|---|---|
committer | Daniel Garcia <danielgarcia@gmail.com> | 2015-01-05 09:49:26 +0300 |
commit | e9eb8f627f3fb12190074173bae3750a961e69d3 (patch) | |
tree | 826532787a9b927c9f4264b956c3254f4f9dc7cf | |
parent | 781f2ba320b09cbc32b3ec19de7f4d7181561e78 (diff) | |
parent | ed027b5c61cc2de15b7c0f010d279665a3c8c5d4 (diff) |
Merge branch 'FastLED3.1' of https://github.com/FastLED/FastLED into FastLED3.1
-rw-r--r-- | hsv2rgb.cpp | 132 | ||||
-rw-r--r-- | hsv2rgb.h | 38 | ||||
-rw-r--r-- | lib8tion.h | 52 |
3 files changed, 222 insertions, 0 deletions
diff --git a/hsv2rgb.cpp b/hsv2rgb.cpp index 8a6d0575..ab037e82 100644 --- a/hsv2rgb.cpp +++ b/hsv2rgb.cpp @@ -474,3 +474,135 @@ void hsv2rgb_spectrum( const struct CHSV* phsv, struct CRGB * prgb, int numLeds) hsv2rgb_spectrum(phsv[i], prgb[i]); } } + + + +#define FIXFRAC8(N,D) (((N)*256)/(D)) + +// This function is only an approximation, and it is not +// nearly as fast as the normal HSV-to-RGB conversion. +// See extended notes in the .h file. +CHSV rgb2hsv_approximate( const CRGB& rgb) +{ + byte r = rgb.r; + byte g = rgb.g; + byte b = rgb.b; + byte h, s, v; + + // find desaturation + byte desat = 255; + if( r < desat) desat = r; + if( g < desat) desat = g; + if( b < desat) desat = b; + + // remove saturation from all channels + r -= desat; + g -= desat; + b -= desat; + + // at least one channel is now zero + + // if all three channels are zero, we had a + // shade of gray. + + uint16_t total = r + g + b; + + if( total == 0) { + // we pick hue zero for no special reason + h = 0; + return CHSV( h, s, v); + } + + // since this wasn't a pure shade of gray, + // the interesting question is what hue is it + + // scale all channels up to a total of 255 + if( total != 255) { + uint32_t scaleup = 65535 / (total); + r = ((uint32_t)(r) * scaleup) / 256; + g = ((uint32_t)(g) * scaleup) / 256; + b = ((uint32_t)(b) * scaleup) / 256; + } + + if( total > 255 ) { + v = 255; + } else { + v = qadd8(desat,total); + // undo 'dimming' of brightness + if( v != 255) v = sqrt16( v * 256); + // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5 + // if( v != 255) v = (256.0 * sqrt( (float)(v) / 256.0)); + + } + + // saturation is opposite of desaturation + s = 255 - desat; + if( v != 255) s = (s * 256) / v; + + // undo 'dimming' of saturation + if( s != 255 ) s = 255 - sqrt16( (255-s) * 256); + // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5 + // if( s != 255 ) s = (255 - (256.0 * sqrt( (float)(255-s) / 256.0))); + + // start with which channel is highest + // (ties don't matter) + byte highest = r; + if( g > highest) highest = g; + if( b > highest) highest = b; + + if( highest == r ) { + // Red is highest. + // Hue could be Purple/Pink-Red,Red-Orange,Orange-Yellow + if( g == 0 ) { + // if green is zero, we're in Purple/Pink-Red + h = (HUE_PURPLE + HUE_PINK) / 2; + h += scale8( r - 128, FIXFRAC8(48,128)); + } else if ( (r - g) > g) { + // if R-G > G then we're in Red-Orange + h = HUE_RED; + h += scale8( g, FIXFRAC8(32,85)); + } else { + // R-G < G, we're in Orange-Yellow + h = HUE_ORANGE; + h += scale8( qsub8((g - 85) + (171 - r), 4), FIXFRAC8(32,85)); //221 + } + + } else if ( highest == g) { + // Green is highest + // Hue could be Yellow-Green, Green-Aqua + if( b == 0) { + // if Blue is zero, we're in Yellow-Green + h = HUE_YELLOW; + h += scale8( qadd8( qadd8((g - 128), (128 - r)), 4), FIXFRAC8(32,255)); // + } else { + // if Blue is nonzero we're in Green-Aqua + if( (g-b) > b) { + h = HUE_GREEN; + h += scale8( b, FIXFRAC8(32,85)); + } else { + h = HUE_AQUA; + h += scale8( b - 85, FIXFRAC8(8,42)); + } + } + + } else /* highest == b */ { + // Blue is highest + // Hue could be Aqua/Blue-Blue, Blue-Purple, Purple-Pink + if( r == 0) { + // if red is zero, we're in Aqua/Blue-Blue + h = HUE_AQUA + ((HUE_BLUE - HUE_AQUA) / 4); + h += scale8( b - 128, FIXFRAC8(24,128)); + } else if ( (b-r) > r) { + // B-R > R, we're in Blue-Purple + h = HUE_BLUE; + h += scale8( r, FIXFRAC8(32,85)); + } else { + // B-R < R, we're in Purple-Pink + h = HUE_PURPLE; + h += scale8( r - 85, FIXFRAC8(32,85)); + } + } + + h += 1; + return CHSV( h, s, v); +} @@ -45,4 +45,42 @@ void hsv2rgb_raw(const struct CHSV& hsv, struct CRGB & rgb); void hsv2rgb_raw(const struct CHSV* phsv, struct CRGB * prgb, int numLeds); #define HUE_MAX 191 + +// rgb2hsv_approximate - recover _approximate_ HSV values from RGB. +// +// NOTE 1: This function is a long-term work in process; expect +// results to change slightly over time as this function is +// refined and improved. +// +// NOTE 2: This function is most accurate when the input is an +// RGB color that came from a fully-saturated HSV color to start +// with. E.g. CHSV( hue, 255, 255) -> CRGB -> CHSV will give +// best results. +// +// NOTE 3: This function is not nearly as fast as HSV-to-RGB. +// It is provided for those situations when the need for this +// function cannot be avoided, or when extremely high performance +// is not needed. +// +// NOTE 4: Why is this 'only' an "approximation"? +// Not all RGB colors have HSV equivalents! For example, there +// is no HSV value that will ever convert to RGB(255,255,0) using +// the code provided in this library. So if you try to +// convert RGB(255,255,0) 'back' to HSV, you'll necessarily get +// only an approximation. Emphasis has been placed on getting +// the 'hue' as close as usefully possible, but even that's a bit +// of a challenge. The 8-bit HSV and 8-bit RGB color spaces +// are not a "bijection". +// +// Nevertheless, this function does a pretty good job, particularly +// at recovering the 'hue' from fully saturated RGB colors that +// originally came from HSV rainbow colors. So if you start +// with CHSV(hue_in,255,255), and convert that to RGB, and then +// convert it back to HSV using this function, the resulting output +// hue will either exactly the same, or very close (+/-1). +// The more desaturated the original RGB color is, the rougher the +// approximation, and the less accurate the results. +// +CHSV rgb2hsv_approximate( const CRGB& rgb); + #endif @@ -524,6 +524,58 @@ LIB8STATIC int8_t avg7( int8_t i, int8_t j) #endif } +// mod8: Calculate the remainder of one unsigned 8-bit +// value divided by anoter, aka A % M. +// Implemented by repeated subtraction, which is +// very compact, and very fast if A is 'probably' +// less than M. If A is a large multiple of M, +// the loop has to execute multiple times. However, +// even in that case, the loop is only two +// instructions long on AVR, i.e., quick. +LIB8STATIC uint8_t mod8( uint8_t a, uint8_t m) +{ +#if defined(__AVR__) + asm volatile ( + "L_%=: sub %[a],%[m] \n\t" + " brcc L_%= \n\t" + " add %[a],%[m] \n\t" + : [a] "+r" (a) + : [m] "r" (m) + ); +#else + while( a >= m) a -= m; +#endif + return a; +} + +// addmod8: Add two numbers, and calculate the modulo +// of the sum and a third number, M. +// In other words, it returns (A+B) % M. +// It is designed as a compact mechanism for +// incrementing a 'mode' switch and wrapping +// around back to 'mode 0' when the switch +// goes past the end of the available range. +// e.g. if you have seven modes, this switches +// to the next one and wraps around if needed: +// mode = addmod8( mode, 1, 7); +// See 'mod8' for notes on performance. +LIB8STATIC uint8_t addmod8( uint8_t a, uint8_t b, uint8_t m) +{ +#if defined(__AVR__) + asm volatile ( + " add %[a],%[b] \n\t" + "L_%=: sub %[a],%[m] \n\t" + " brcc L_%= \n\t" + " add %[a],%[m] \n\t" + : [a] "+r" (a) + : [b] "r" (b), [m] "r" (m) + ); +#else + a += b; + while( a >= m) a -= m; +#endif + return a; +} // scale8: scale one byte by a second one, which is treated as |