diff options
Diffstat (limited to 'Библиотеки/FastLED-master/hsv2rgb.cpp')
-rw-r--r-- | Библиотеки/FastLED-master/hsv2rgb.cpp | 714 |
1 files changed, 714 insertions, 0 deletions
diff --git a/Библиотеки/FastLED-master/hsv2rgb.cpp b/Библиотеки/FastLED-master/hsv2rgb.cpp new file mode 100644 index 0000000..cdb576b --- /dev/null +++ b/Библиотеки/FastLED-master/hsv2rgb.cpp @@ -0,0 +1,714 @@ +#define FASTLED_INTERNAL +#include <stdint.h> + +#include "FastLED.h" + +FASTLED_NAMESPACE_BEGIN + +// Functions to convert HSV colors to RGB colors. +// +// The basically fall into two groups: spectra, and rainbows. +// Spectra and rainbows are not the same thing. Wikipedia has a good +// illustration here +// http://upload.wikimedia.org/wikipedia/commons/f/f6/Prism_compare_rainbow_01.png +// from this article +// http://en.wikipedia.org/wiki/Rainbow#Number_of_colours_in_spectrum_or_rainbow +// that shows a 'spectrum' and a 'rainbow' side by side. Among other +// differences, you'll see that a 'rainbow' has much more yellow than +// a plain spectrum. "Classic" LED color washes are spectrum based, and +// usually show very little yellow. +// +// Wikipedia's page on HSV color space, with pseudocode for conversion +// to RGB color space +// http://en.wikipedia.org/wiki/HSL_and_HSV +// Note that their conversion algorithm, which is (naturally) very popular +// is in the "maximum brightness at any given hue" style, vs the "uniform +// brightness for all hues" style. +// +// You can't have both; either purple is the same brightness as red, e.g +// red = #FF0000 and purple = #800080 -> same "total light" output +// OR purple is 'as bright as it can be', e.g. +// red = #FF0000 and purple = #FF00FF -> purple is much brighter than red. +// The colorspace conversions here try to keep the apparent brightness +// constant even as the hue varies. +// +// Adafruit's "Wheel" function, discussed here +// http://forums.adafruit.com/viewtopic.php?f=47&t=22483 +// is also of the "constant apparent brightness" variety. +// +// TODO: provide the 'maximum brightness no matter what' variation. +// +// See also some good, clear Arduino C code from Kasper Kamperman +// http://www.kasperkamperman.com/blog/arduino/arduino-programming-hsb-to-rgb/ +// which in turn was was based on Windows C code from "nico80" +// http://www.codeproject.com/Articles/9207/An-HSB-RGBA-colour-picker + + + + + +void hsv2rgb_raw_C (const struct CHSV & hsv, struct CRGB & rgb); +void hsv2rgb_raw_avr(const struct CHSV & hsv, struct CRGB & rgb); + +#if defined(__AVR__) && !defined( LIB8_ATTINY ) +void hsv2rgb_raw(const struct CHSV & hsv, struct CRGB & rgb) +{ + hsv2rgb_raw_avr( hsv, rgb); +} +#else +void hsv2rgb_raw(const struct CHSV & hsv, struct CRGB & rgb) +{ + hsv2rgb_raw_C( hsv, rgb); +} +#endif + + + +#define APPLY_DIMMING(X) (X) +#define HSV_SECTION_6 (0x20) +#define HSV_SECTION_3 (0x40) + +void hsv2rgb_raw_C (const struct CHSV & hsv, struct CRGB & rgb) +{ + // Convert hue, saturation and brightness ( HSV/HSB ) to RGB + // "Dimming" is used on saturation and brightness to make + // the output more visually linear. + + // Apply dimming curves + uint8_t value = APPLY_DIMMING( hsv.val); + uint8_t saturation = hsv.sat; + + // The brightness floor is minimum number that all of + // R, G, and B will be set to. + uint8_t invsat = APPLY_DIMMING( 255 - saturation); + uint8_t brightness_floor = (value * invsat) / 256; + + // The color amplitude is the maximum amount of R, G, and B + // that will be added on top of the brightness_floor to + // create the specific hue desired. + uint8_t color_amplitude = value - brightness_floor; + + // Figure out which section of the hue wheel we're in, + // and how far offset we are withing that section + uint8_t section = hsv.hue / HSV_SECTION_3; // 0..2 + uint8_t offset = hsv.hue % HSV_SECTION_3; // 0..63 + + uint8_t rampup = offset; // 0..63 + uint8_t rampdown = (HSV_SECTION_3 - 1) - offset; // 63..0 + + // We now scale rampup and rampdown to a 0-255 range -- at least + // in theory, but here's where architecture-specific decsions + // come in to play: + // To scale them up to 0-255, we'd want to multiply by 4. + // But in the very next step, we multiply the ramps by other + // values and then divide the resulting product by 256. + // So which is faster? + // ((ramp * 4) * othervalue) / 256 + // or + // ((ramp ) * othervalue) / 64 + // It depends on your processor architecture. + // On 8-bit AVR, the "/ 256" is just a one-cycle register move, + // but the "/ 64" might be a multicycle shift process. So on AVR + // it's faster do multiply the ramp values by four, and then + // divide by 256. + // On ARM, the "/ 256" and "/ 64" are one cycle each, so it's + // faster to NOT multiply the ramp values by four, and just to + // divide the resulting product by 64 (instead of 256). + // Moral of the story: trust your profiler, not your insticts. + + // Since there's an AVR assembly version elsewhere, we'll + // assume what we're on an architecture where any number of + // bit shifts has roughly the same cost, and we'll remove the + // redundant math at the source level: + + // // scale up to 255 range + // //rampup *= 4; // 0..252 + // //rampdown *= 4; // 0..252 + + // compute color-amplitude-scaled-down versions of rampup and rampdown + uint8_t rampup_amp_adj = (rampup * color_amplitude) / (256 / 4); + uint8_t rampdown_amp_adj = (rampdown * color_amplitude) / (256 / 4); + + // add brightness_floor offset to everything + uint8_t rampup_adj_with_floor = rampup_amp_adj + brightness_floor; + uint8_t rampdown_adj_with_floor = rampdown_amp_adj + brightness_floor; + + + if( section ) { + if( section == 1) { + // section 1: 0x40..0x7F + rgb.r = brightness_floor; + rgb.g = rampdown_adj_with_floor; + rgb.b = rampup_adj_with_floor; + } else { + // section 2; 0x80..0xBF + rgb.r = rampup_adj_with_floor; + rgb.g = brightness_floor; + rgb.b = rampdown_adj_with_floor; + } + } else { + // section 0: 0x00..0x3F + rgb.r = rampdown_adj_with_floor; + rgb.g = rampup_adj_with_floor; + rgb.b = brightness_floor; + } +} + + + +#if defined(__AVR__) && !defined( LIB8_ATTINY ) +void hsv2rgb_raw_avr(const struct CHSV & hsv, struct CRGB & rgb) +{ + uint8_t hue, saturation, value; + + hue = hsv.hue; + saturation = hsv.sat; + value = hsv.val; + + // Saturation more useful the other way around + saturation = 255 - saturation; + uint8_t invsat = APPLY_DIMMING( saturation ); + + // Apply dimming curves + value = APPLY_DIMMING( value ); + + // The brightness floor is minimum number that all of + // R, G, and B will be set to, which is value * invsat + uint8_t brightness_floor; + + asm volatile( + "mul %[value], %[invsat] \n" + "mov %[brightness_floor], r1 \n" + : [brightness_floor] "=r" (brightness_floor) + : [value] "r" (value), + [invsat] "r" (invsat) + : "r0", "r1" + ); + + // The color amplitude is the maximum amount of R, G, and B + // that will be added on top of the brightness_floor to + // create the specific hue desired. + uint8_t color_amplitude = value - brightness_floor; + + // Figure how far we are offset into the section of the + // color wheel that we're in + uint8_t offset = hsv.hue & (HSV_SECTION_3 - 1); // 0..63 + uint8_t rampup = offset * 4; // 0..252 + + + // compute color-amplitude-scaled-down versions of rampup and rampdown + uint8_t rampup_amp_adj; + uint8_t rampdown_amp_adj; + + asm volatile( + "mul %[rampup], %[color_amplitude] \n" + "mov %[rampup_amp_adj], r1 \n" + "com %[rampup] \n" + "mul %[rampup], %[color_amplitude] \n" + "mov %[rampdown_amp_adj], r1 \n" + : [rampup_amp_adj] "=&r" (rampup_amp_adj), + [rampdown_amp_adj] "=&r" (rampdown_amp_adj), + [rampup] "+r" (rampup) + : [color_amplitude] "r" (color_amplitude) + : "r0", "r1" + ); + + + // add brightness_floor offset to everything + uint8_t rampup_adj_with_floor = rampup_amp_adj + brightness_floor; + uint8_t rampdown_adj_with_floor = rampdown_amp_adj + brightness_floor; + + + // keep gcc from using "X" as the index register for storing + // results back in the return structure. AVR's X register can't + // do "std X+q, rnn", but the Y and Z registers can. + // if the pointer to 'rgb' is in X, gcc will add all kinds of crazy + // extra instructions. Simply killing X here seems to help it + // try Y or Z first. + asm volatile( "" : : : "r26", "r27" ); + + + if( hue & 0x80 ) { + // section 2: 0x80..0xBF + rgb.r = rampup_adj_with_floor; + rgb.g = brightness_floor; + rgb.b = rampdown_adj_with_floor; + } else { + if( hue & 0x40) { + // section 1: 0x40..0x7F + rgb.r = brightness_floor; + rgb.g = rampdown_adj_with_floor; + rgb.b = rampup_adj_with_floor; + } else { + // section 0: 0x00..0x3F + rgb.r = rampdown_adj_with_floor; + rgb.g = rampup_adj_with_floor; + rgb.b = brightness_floor; + } + } + + cleanup_R1(); +} +// End of AVR asm implementation + +#endif + +void hsv2rgb_spectrum( const CHSV& hsv, CRGB& rgb) +{ + CHSV hsv2(hsv); + hsv2.hue = scale8( hsv2.hue, 191); + hsv2rgb_raw(hsv2, rgb); +} + + +// Sometimes the compiler will do clever things to reduce +// code size that result in a net slowdown, if it thinks that +// a variable is not used in a certain location. +// This macro does its best to convince the compiler that +// the variable is used in this location, to help control +// code motion and de-duplication that would result in a slowdown. +#define FORCE_REFERENCE(var) asm volatile( "" : : "r" (var) ) + + +#define K255 255 +#define K171 171 +#define K170 170 +#define K85 85 + +void hsv2rgb_rainbow( const CHSV& hsv, CRGB& rgb) +{ + // Yellow has a higher inherent brightness than + // any other color; 'pure' yellow is perceived to + // be 93% as bright as white. In order to make + // yellow appear the correct relative brightness, + // it has to be rendered brighter than all other + // colors. + // Level Y1 is a moderate boost, the default. + // Level Y2 is a strong boost. + const uint8_t Y1 = 1; + const uint8_t Y2 = 0; + + // G2: Whether to divide all greens by two. + // Depends GREATLY on your particular LEDs + const uint8_t G2 = 0; + + // Gscale: what to scale green down by. + // Depends GREATLY on your particular LEDs + const uint8_t Gscale = 0; + + + uint8_t hue = hsv.hue; + uint8_t sat = hsv.sat; + uint8_t val = hsv.val; + + uint8_t offset = hue & 0x1F; // 0..31 + + // offset8 = offset * 8 + uint8_t offset8 = offset; + { +#if defined(__AVR__) + // Left to its own devices, gcc turns "x <<= 3" into a loop + // It's much faster and smaller to just do three single-bit shifts + // So this business is to force that. + offset8 <<= 1; + asm volatile(""); + offset8 <<= 1; + asm volatile(""); + offset8 <<= 1; +#else + // On ARM and other non-AVR platforms, we just shift 3. + offset8 <<= 3; +#endif + } + + uint8_t third = scale8( offset8, (256 / 3)); // max = 85 + + uint8_t r, g, b; + + if( ! (hue & 0x80) ) { + // 0XX + if( ! (hue & 0x40) ) { + // 00X + //section 0-1 + if( ! (hue & 0x20) ) { + // 000 + //case 0: // R -> O + r = K255 - third; + g = third; + b = 0; + FORCE_REFERENCE(b); + } else { + // 001 + //case 1: // O -> Y + if( Y1 ) { + r = K171; + g = K85 + third ; + b = 0; + FORCE_REFERENCE(b); + } + if( Y2 ) { + r = K170 + third; + //uint8_t twothirds = (third << 1); + uint8_t twothirds = scale8( offset8, ((256 * 2) / 3)); // max=170 + g = K85 + twothirds; + b = 0; + FORCE_REFERENCE(b); + } + } + } else { + //01X + // section 2-3 + if( ! (hue & 0x20) ) { + // 010 + //case 2: // Y -> G + if( Y1 ) { + //uint8_t twothirds = (third << 1); + uint8_t twothirds = scale8( offset8, ((256 * 2) / 3)); // max=170 + r = K171 - twothirds; + g = K170 + third; + b = 0; + FORCE_REFERENCE(b); + } + if( Y2 ) { + r = K255 - offset8; + g = K255; + b = 0; + FORCE_REFERENCE(b); + } + } else { + // 011 + // case 3: // G -> A + r = 0; + FORCE_REFERENCE(r); + g = K255 - third; + b = third; + } + } + } else { + // section 4-7 + // 1XX + if( ! (hue & 0x40) ) { + // 10X + if( ! ( hue & 0x20) ) { + // 100 + //case 4: // A -> B + r = 0; + FORCE_REFERENCE(r); + //uint8_t twothirds = (third << 1); + uint8_t twothirds = scale8( offset8, ((256 * 2) / 3)); // max=170 + g = K171 - twothirds; //K170? + b = K85 + twothirds; + + } else { + // 101 + //case 5: // B -> P + r = third; + g = 0; + FORCE_REFERENCE(g); + b = K255 - third; + + } + } else { + if( ! (hue & 0x20) ) { + // 110 + //case 6: // P -- K + r = K85 + third; + g = 0; + FORCE_REFERENCE(g); + b = K171 - third; + + } else { + // 111 + //case 7: // K -> R + r = K170 + third; + g = 0; + FORCE_REFERENCE(g); + b = K85 - third; + + } + } + } + + // This is one of the good places to scale the green down, + // although the client can scale green down as well. + if( G2 ) g = g >> 1; + if( Gscale ) g = scale8_video_LEAVING_R1_DIRTY( g, Gscale); + + // Scale down colors if we're desaturated at all + // and add the brightness_floor to r, g, and b. + if( sat != 255 ) { + if( sat == 0) { + r = 255; b = 255; g = 255; + } else { + //nscale8x3_video( r, g, b, sat); +#if (FASTLED_SCALE8_FIXED==1) + if( r ) r = scale8_LEAVING_R1_DIRTY( r, sat); + if( g ) g = scale8_LEAVING_R1_DIRTY( g, sat); + if( b ) b = scale8_LEAVING_R1_DIRTY( b, sat); +#else + if( r ) r = scale8_LEAVING_R1_DIRTY( r, sat) + 1; + if( g ) g = scale8_LEAVING_R1_DIRTY( g, sat) + 1; + if( b ) b = scale8_LEAVING_R1_DIRTY( b, sat) + 1; +#endif + cleanup_R1(); + + uint8_t desat = 255 - sat; + desat = scale8( desat, desat); + + uint8_t brightness_floor = desat; + r += brightness_floor; + g += brightness_floor; + b += brightness_floor; + } + } + + // Now scale everything down if we're at value < 255. + if( val != 255 ) { + + val = scale8_video_LEAVING_R1_DIRTY( val, val); + if( val == 0 ) { + r=0; g=0; b=0; + } else { + // nscale8x3_video( r, g, b, val); +#if (FASTLED_SCALE8_FIXED==1) + if( r ) r = scale8_LEAVING_R1_DIRTY( r, val); + if( g ) g = scale8_LEAVING_R1_DIRTY( g, val); + if( b ) b = scale8_LEAVING_R1_DIRTY( b, val); +#else + if( r ) r = scale8_LEAVING_R1_DIRTY( r, val) + 1; + if( g ) g = scale8_LEAVING_R1_DIRTY( g, val) + 1; + if( b ) b = scale8_LEAVING_R1_DIRTY( b, val) + 1; +#endif + cleanup_R1(); + } + } + + // Here we have the old AVR "missing std X+n" problem again + // It turns out that fixing it winds up costing more than + // not fixing it. + // To paraphrase Dr Bronner, profile! profile! profile! + //asm volatile( "" : : : "r26", "r27" ); + //asm volatile (" movw r30, r26 \n" : : : "r30", "r31"); + rgb.r = r; + rgb.g = g; + rgb.b = b; +} + + +void hsv2rgb_raw(const struct CHSV * phsv, struct CRGB * prgb, int numLeds) { + for(int i = 0; i < numLeds; i++) { + hsv2rgb_raw(phsv[i], prgb[i]); + } +} + +void hsv2rgb_rainbow( const struct CHSV* phsv, struct CRGB * prgb, int numLeds) { + for(int i = 0; i < numLeds; i++) { + hsv2rgb_rainbow(phsv[i], prgb[i]); + } +} + +void hsv2rgb_spectrum( const struct CHSV* phsv, struct CRGB * prgb, int numLeds) { + for(int i = 0; i < numLeds; i++) { + 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) +{ + uint8_t r = rgb.r; + uint8_t g = rgb.g; + uint8_t b = rgb.b; + uint8_t h, s, v; + + // find desaturation + uint8_t 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; + + //Serial.print("desat="); Serial.print(desat); Serial.println(""); + + //uint8_t orig_desat = sqrt16( desat * 256); + //Serial.print("orig_desat="); Serial.print(orig_desat); Serial.println(""); + + // saturation is opposite of desaturation + s = 255 - desat; + //Serial.print("s.1="); Serial.print(s); Serial.println(""); + + if( s != 255 ) { + // undo 'dimming' of saturation + 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))); + //Serial.print("s.2="); Serial.print(s); Serial.println(""); + + + // at least one channel is now zero + // if all three channels are zero, we had a + // shade of gray. + if( (r + g + b) == 0) { + // we pick hue zero for no special reason + return CHSV( 0, 0, 255 - s); + } + + // scale all channels up to compensate for desaturation + if( s < 255) { + if( s == 0) s = 1; + uint32_t scaleup = 65535 / (s); + r = ((uint32_t)(r) * scaleup) / 256; + g = ((uint32_t)(g) * scaleup) / 256; + b = ((uint32_t)(b) * scaleup) / 256; + } + //Serial.print("r.2="); Serial.print(r); Serial.println(""); + //Serial.print("g.2="); Serial.print(g); Serial.println(""); + //Serial.print("b.2="); Serial.print(b); Serial.println(""); + + uint16_t total = r + g + b; + + //Serial.print("total="); Serial.print(total); Serial.println(""); + + // scale all channels up to compensate for low values + if( total < 255) { + if( total == 0) total = 1; + uint32_t scaleup = 65535 / (total); + r = ((uint32_t)(r) * scaleup) / 256; + g = ((uint32_t)(g) * scaleup) / 256; + b = ((uint32_t)(b) * scaleup) / 256; + } + //Serial.print("r.3="); Serial.print(r); Serial.println(""); + //Serial.print("g.3="); Serial.print(g); Serial.println(""); + //Serial.print("b.3="); Serial.print(b); Serial.println(""); + + 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)); + + } + + //Serial.print("v="); Serial.print(v); Serial.println(""); + + +#if 0 + + //#else + if( v != 255) { + // this part could probably use refinement/rethinking, + // (but it doesn't overflow & wrap anymore) + uint16_t s16; + s16 = (s * 256); + s16 /= v; + //Serial.print("s16="); Serial.print(s16); Serial.println(""); + if( s16 < 256) { + s = s16; + } else { + s = 255; // clamp to prevent overflow + } + } +#endif + + //Serial.print("s.3="); Serial.print(s); Serial.println(""); + + + // since this wasn't a pure shade of gray, + // the interesting question is what hue is it + + + + // start with which channel is highest + // (ties don't matter) + uint8_t 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( qsub8(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 + // G = 171..255 + // R = 171.. 0 + h = HUE_YELLOW; + uint8_t radj = scale8( qsub8(171,r), 47); //171..0 -> 0..171 -> 0..31 + uint8_t gadj = scale8( qsub8(g,171), 96); //171..255 -> 0..84 -> 0..31; + uint8_t rgadj = radj + gadj; + uint8_t hueadv = rgadj / 2; + h += hueadv; + //h += scale8( qadd8( 4, qadd8((g - 128), (128 - r))), + // 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( qsub8(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( qsub8(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( qsub8(r, 85), FIXFRAC8(32,85)); + } + } + + h += 1; + return CHSV( h, s, v); +} + +// Examples that need work: +// 0,192,192 +// 192,64,64 +// 224,32,32 +// 252,0,126 +// 252,252,0 +// 252,252,126 + +FASTLED_NAMESPACE_END |