FastLED 3.10.6
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 559 of file hsv2rgb.cpp.hpp.

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

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