FastLED 3.10.8
Loading...
Searching...
No Matches

◆ rgb2hsv_approximate()

CHSV rgb2hsv_approximate ( const CRGB & rgb)

Recover approximate HSV values from RGB.

These values are approximate, not exact. Why is this "only" an approximation? Because 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.

Note
This function is a long-term work in progress; expect results to change slightly over time as this function is refined and improved.
Note
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
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.
See also
https://en.wikipedia.org/wiki/Bijection
Parameters
rgban RGB value to convert
Returns
the approximate HSV equivalent of the RGB value

Definition at line 560 of file hsv2rgb.cpp.hpp.

561 {
562 // These channels can exceed 255 while saturation is being undone. Keep
563 // the intermediate values wide until they are normalized below.
564 fl::u16 r = rgb.r;
565 fl::u16 g = rgb.g;
566 fl::u16 b = rgb.b;
567 fl::u8 h, s, v;
568
569 // find desaturation
570 fl::u8 desat = 255;
571 if( r < desat) desat = r;
572 if( g < desat) desat = g;
573 if( b < desat) desat = b;
574
575 // remove saturation from all channels
576 r -= desat;
577 g -= desat;
578 b -= desat;
579
580 //Serial.print("desat="); Serial.print(desat); Serial.println("");
581
582 //uint8_t orig_desat = sqrt16( desat * 256);
583 //Serial.print("orig_desat="); Serial.print(orig_desat); Serial.println("");
584
585 // saturation is opposite of desaturation
586 s = 255 - desat;
587 //Serial.print("s.1="); Serial.print(s); Serial.println("");
588
589 if( s != 255 ) {
590 // undo 'dimming' of saturation
591 s = 255 - fl::sqrt16( (255-s) * 256);
592 }
593 // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5
594 // if( s != 255 ) s = (255 - (256.0 * sqrt( (float)(255-s) / 256.0)));
595 //Serial.print("s.2="); Serial.print(s); Serial.println("");
596
597
598 // at least one channel is now zero
599 // if all three channels are zero, we had a
600 // shade of gray.
601 if( (r + g + b) == 0) {
602 // we pick hue zero for no special reason
603 return CHSV( 0, 0, 255 - s);
604 }
605
606 // scale all channels up to compensate for desaturation
607 if( s < 255) {
608 if( s == 0) s = 1;
609 fl::u32 scaleup = 65535 / (s);
610 r = ((fl::u32)(r) * scaleup) / 256;
611 g = ((fl::u32)(g) * scaleup) / 256;
612 b = ((fl::u32)(b) * scaleup) / 256;
613 }
614 //Serial.print("r.2="); Serial.print(r); Serial.println("");
615 //Serial.print("g.2="); Serial.print(g); Serial.println("");
616 //Serial.print("b.2="); Serial.print(b); Serial.println("");
617
618 fl::u16 total = r + g + b;
619
620 //Serial.print("total="); Serial.print(total); Serial.println("");
621
622 // Normalize the channels to the range expected by the hue approximation.
623 // This scales low totals up as before, and also scales high totals down
624 // instead of letting wide intermediates wrap when stored in u8 channels.
625 if( total != 255) {
626 if( total == 0) total = 1;
627 fl::u32 scaleup = 65535 / (total);
628 r = ((fl::u32)(r) * scaleup) / 256;
629 g = ((fl::u32)(g) * scaleup) / 256;
630 b = ((fl::u32)(b) * scaleup) / 256;
631 }
632 //Serial.print("r.3="); Serial.print(r); Serial.println("");
633 //Serial.print("g.3="); Serial.print(g); Serial.println("");
634 //Serial.print("b.3="); Serial.print(b); Serial.println("");
635
636 if( total > 255 ) {
637 v = 255;
638 } else {
639 v = qadd8(desat,total);
640 // undo 'dimming' of brightness
641 if( v != 255) v = fl::sqrt16( v * 256);
642 // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5
643 // if( v != 255) v = (256.0 * sqrt( (float)(v) / 256.0));
644
645 }
646
647 //Serial.print("v="); Serial.print(v); Serial.println("");
648
649
650#if 0
651
652 //#else
653 if( v != 255) {
654 // this part could probably use refinement/rethinking,
655 // (but it doesn't overflow & wrap anymore)
656 fl::u16 s16;
657 s16 = (s * 256);
658 s16 /= v;
659 //Serial.print("s16="); Serial.print(s16); Serial.println("");
660 if( s16 < 256) {
661 s = s16;
662 } else {
663 s = 255; // clamp to prevent overflow
664 }
665 }
666#endif
667
668 //Serial.print("s.3="); Serial.print(s); Serial.println("");
669
670
671 // since this wasn't a pure shade of gray,
672 // the interesting question is what hue is it
673
674
675
676 // start with which channel is highest
677 // (ties don't matter)
678 fl::u16 highest = r;
679 if( g > highest) highest = g;
680 if( b > highest) highest = b;
681
682 if( highest == r ) {
683 // Red is highest.
684 // Hue could be Purple/Pink-Red,Red-Orange,Orange-Yellow
685 if( g == 0 ) {
686 // if green is zero, we're in Purple/Pink-Red
687 h = (HUE_PURPLE + HUE_PINK) / 2;
688 h += scale8( qsub8(r, 128), FIXFRAC8(48,128));
689 } else if ( (r - g) > g) {
690 // if R-G > G then we're in Red-Orange
691 h = HUE_RED;
692 h += scale8( g, FIXFRAC8(32,85));
693 } else {
694 // R-G < G, we're in Orange-Yellow.
695 //
696 // This used to read `(g - 85) + (171 - r)`. Reaching this branch
697 // requires g >= r/2, and r is the largest channel, so r > 171 is
698 // the normal case here -- making (171 - r) negative. The sum was
699 // then truncated to u8 by qsub8() and wrapped to a large value,
700 // sending orange out into the greens: rgb(255,153,0) returned hue
701 // 122 instead of ~26. See issue #436.
702 //
703 // (255 - r) is the same measure of "how far r has fallen" but
704 // cannot go negative, and the sum (g - 85) + (255 - r) has a
705 // minimum of ~42 over this branch's own entry condition, so the
706 // wrap is structurally impossible rather than merely unlikely.
707 // Spans ~42..170, hence FIXFRAC8(32,170) to land the top of the
708 // range on HUE_YELLOW.
709 h = HUE_ORANGE;
710 h += scale8( qsub8((g - 85) + (255 - r), 4), FIXFRAC8(32,170));
711 }
712
713 } else if ( highest == g) {
714 // Green is highest
715 // Hue could be Yellow-Green, Green-Aqua
716 if( b == 0) {
717 // if Blue is zero, we're in Yellow-Green
718 // G = 171..255
719 // R = 171.. 0
720 h = HUE_YELLOW;
721 fl::u8 radj = scale8( qsub8(171,r), 47); //171..0 -> 0..171 -> 0..31
722 fl::u8 gadj = scale8( qsub8(g,171), 96); //171..255 -> 0..84 -> 0..31;
723 fl::u8 rgadj = radj + gadj;
724 fl::u8 hueadv = rgadj / 2;
725 h += hueadv;
726 //h += scale8( qadd8( 4, qadd8((g - 128), (128 - r))),
727 // FIXFRAC8(32,255)); //
728 } else {
729 // if Blue is nonzero we're in Green-Aqua
730 if( (g-b) > b) {
731 h = HUE_GREEN;
732 h += scale8( b, FIXFRAC8(32,85));
733 } else {
734 h = HUE_AQUA;
735 h += scale8( qsub8(b, 85), FIXFRAC8(8,42));
736 }
737 }
738
739 } else /* highest == b */ {
740 // Blue is highest
741 // Hue could be Aqua/Blue-Blue, Blue-Purple, Purple-Pink
742 if( r == 0) {
743 // if red is zero, we're in Aqua/Blue-Blue
744 h = HUE_AQUA + ((HUE_BLUE - HUE_AQUA) / 4);
745 h += scale8( qsub8(b, 128), FIXFRAC8(24,128));
746 } else if ( (b-r) > r) {
747 // B-R > R, we're in Blue-Purple
748 h = HUE_BLUE;
749 h += scale8( r, FIXFRAC8(32,85));
750 } else {
751 // B-R < R, we're in Purple-Pink
752 h = HUE_PURPLE;
753 h += scale8( qsub8(r, 85), FIXFRAC8(32,85));
754 }
755 }
756
757 h += 1;
758 return CHSV( h, s, v);
759}
@ HUE_BLUE
Blue (225°)
Definition hsv.h:129
@ HUE_RED
Red (0°)
Definition hsv.h:124
@ HUE_AQUA
Aqua (180°)
Definition hsv.h:128
@ HUE_PINK
Pink (315°)
Definition hsv.h:131
@ HUE_GREEN
Green (135°)
Definition hsv.h:127
@ HUE_YELLOW
Yellow (90°)
Definition hsv.h:126
@ HUE_ORANGE
Orange (45°)
Definition hsv.h:125
@ HUE_PURPLE
Purple (270°)
Definition hsv.h:130
fl::hsv8 CHSV
Definition chsv.h:11
#define FIXFRAC8(N, D)
Convert a fractional input into a constant.
unsigned char u8
Definition stdint.h:131
InputGamut g
Definition rgbw.h:125

References FIXFRAC8, FL_NO_EXCEPT, HUE_AQUA, HUE_BLUE, HUE_GREEN, HUE_ORANGE, HUE_PINK, HUE_PURPLE, HUE_RED, and HUE_YELLOW.