|
22 | 22 | namespace yup |
23 | 23 | { |
24 | 24 |
|
25 | | -//============================================================================== |
26 | | - |
27 | 25 | namespace |
28 | 26 | { |
| 27 | + |
| 28 | +//============================================================================== |
| 29 | + |
29 | 30 | int hexCharToInt (yup_wchar c) noexcept |
30 | 31 | { |
31 | 32 | return CharacterFunctions::getHexDigitValue (c); |
@@ -197,6 +198,7 @@ Color parseNamedColor (const String& name) |
197 | 198 | } |
198 | 199 |
|
199 | 200 | //============================================================================== |
| 201 | + |
200 | 202 | using DoubleRgb = std::array<double, 3>; |
201 | 203 | using IntRgb = std::array<int, 3>; |
202 | 204 |
|
@@ -375,40 +377,11 @@ void hsluvGetBounds (double l, std::array<HsluvBounds, 6>& bounds) noexcept |
375 | 377 | } |
376 | 378 | } |
377 | 379 |
|
378 | | -double hsluvIntersectLineLine (const HsluvBounds& line1, const HsluvBounds& line2) noexcept |
379 | | -{ |
380 | | - return (line1.b - line2.b) / (line2.a - line1.a); |
381 | | -} |
382 | | - |
383 | | -double hsluvDistanceFromPoleSquared (double x, double y) noexcept |
384 | | -{ |
385 | | - return x * x + y * y; |
386 | | -} |
387 | | - |
388 | 380 | double hsluvRayLengthUntilIntersect (double theta, const HsluvBounds& line) noexcept |
389 | 381 | { |
390 | 382 | return line.b / (std::sin (theta) - line.a * std::cos (theta)); |
391 | 383 | } |
392 | 384 |
|
393 | | -double hsluvMaxSafeChromaForL (double l) noexcept |
394 | | -{ |
395 | | - double minLenSquared = std::numeric_limits<double>::max(); |
396 | | - std::array<HsluvBounds, 6> bounds; |
397 | | - |
398 | | - hsluvGetBounds (l, bounds); |
399 | | - for (const auto& bound : bounds) |
400 | | - { |
401 | | - const HsluvBounds line2 = { -1.0 / bound.a, 0.0 }; |
402 | | - const double x = hsluvIntersectLineLine (bound, line2); |
403 | | - const double distance = hsluvDistanceFromPoleSquared (x, bound.b + x * bound.a); |
404 | | - |
405 | | - if (distance < minLenSquared) |
406 | | - minLenSquared = distance; |
407 | | - } |
408 | | - |
409 | | - return std::sqrt (minLenSquared); |
410 | | -} |
411 | | - |
412 | 385 | double hsluvMaxChromaForLH (double l, double h) noexcept |
413 | 386 | { |
414 | 387 | double minLen = std::numeric_limits<double>::max(); |
@@ -500,6 +473,7 @@ DoubleRgb hsluvFromRgb (double r, double g, double b) noexcept |
500 | 473 | } |
501 | 474 |
|
502 | 475 | //============================================================================== |
| 476 | + |
503 | 477 | constexpr int spectralSampleCount = 38; |
504 | 478 | constexpr double spectralGamma = 2.4; |
505 | 479 | constexpr double spectralEpsilon = 0.00000001; |
@@ -646,24 +620,265 @@ IntRgb spectralMix (const DoubleRgb& lrgb1, const DoubleRgb& lrgb2, double t) no |
646 | 620 |
|
647 | 621 | return xyzToSrgb (reflectanceToXyz (reflectance)); |
648 | 622 | } |
| 623 | + |
| 624 | +//============================================================================== |
| 625 | + |
| 626 | +struct DoubleHsl |
| 627 | +{ |
| 628 | + double h = 0.0; |
| 629 | + double s = 0.0; |
| 630 | + double l = 0.0; |
| 631 | +}; |
| 632 | + |
| 633 | +DoubleHsl rgbToHsl (const DoubleRgb& rgb) noexcept |
| 634 | +{ |
| 635 | + const double maxValue = jmax (rgb[0], rgb[1], rgb[2]); |
| 636 | + const double minValue = jmin (rgb[0], rgb[1], rgb[2]); |
| 637 | + const double l = (maxValue + minValue) * 0.5; |
| 638 | + |
| 639 | + if (maxValue == minValue) |
| 640 | + return { 0.0, 0.0, l }; |
| 641 | + |
| 642 | + const double d = maxValue - minValue; |
| 643 | + const double s = l > 0.5 ? d / (2.0 - maxValue - minValue) : d / (maxValue + minValue); |
| 644 | + double h = 0.0; |
| 645 | + |
| 646 | + if (maxValue == rgb[0]) |
| 647 | + h = (rgb[1] - rgb[2]) / d + (rgb[1] < rgb[2] ? 6.0 : 0.0); |
| 648 | + else if (maxValue == rgb[1]) |
| 649 | + h = (rgb[2] - rgb[0]) / d + 2.0; |
| 650 | + else |
| 651 | + h = (rgb[0] - rgb[1]) / d + 4.0; |
| 652 | + |
| 653 | + h /= 6.0; |
| 654 | + return { h, s, l }; |
| 655 | +} |
| 656 | + |
| 657 | +DoubleRgb hslToRgb (const DoubleHsl& hsl) noexcept |
| 658 | +{ |
| 659 | + auto hueToRgb = [] (double p, double q, double t) |
| 660 | + { |
| 661 | + if (t < 0.0) |
| 662 | + t += 1.0; |
| 663 | + if (t > 1.0) |
| 664 | + t -= 1.0; |
| 665 | + if (t < 1.0 / 6.0) |
| 666 | + return p + (q - p) * 6.0 * t; |
| 667 | + if (t < 1.0 / 2.0) |
| 668 | + return q; |
| 669 | + if (t < 2.0 / 3.0) |
| 670 | + return p + (q - p) * (2.0 / 3.0 - t) * 6.0; |
| 671 | + return p; |
| 672 | + }; |
| 673 | + |
| 674 | + double r = hsl.l; |
| 675 | + double g = hsl.l; |
| 676 | + double b = hsl.l; |
| 677 | + |
| 678 | + if (hsl.s != 0.0) |
| 679 | + { |
| 680 | + const double q = hsl.l < 0.5 ? hsl.l * (1.0 + hsl.s) : hsl.l + hsl.s - hsl.l * hsl.s; |
| 681 | + const double p = 2.0 * hsl.l - q; |
| 682 | + r = hueToRgb (p, q, hsl.h + 1.0 / 3.0); |
| 683 | + g = hueToRgb (p, q, hsl.h); |
| 684 | + b = hueToRgb (p, q, hsl.h - 1.0 / 3.0); |
| 685 | + } |
| 686 | + |
| 687 | + return { r, g, b }; |
| 688 | +} |
| 689 | + |
| 690 | +double blendChannel (BlendMode mode, double backdrop, double source) noexcept |
| 691 | +{ |
| 692 | + switch (mode) |
| 693 | + { |
| 694 | + case BlendMode::SrcOver: |
| 695 | + return source; |
| 696 | + case BlendMode::Screen: |
| 697 | + return 1.0 - (1.0 - backdrop) * (1.0 - source); |
| 698 | + case BlendMode::Overlay: |
| 699 | + return backdrop <= 0.5 ? 2.0 * backdrop * source : 1.0 - 2.0 * (1.0 - backdrop) * (1.0 - source); |
| 700 | + case BlendMode::Darken: |
| 701 | + return jmin (backdrop, source); |
| 702 | + case BlendMode::Lighten: |
| 703 | + return jmax (backdrop, source); |
| 704 | + case BlendMode::ColorDodge: |
| 705 | + return source >= 1.0 ? 1.0 : jmin (1.0, backdrop / (1.0 - source)); |
| 706 | + case BlendMode::ColorBurn: |
| 707 | + return source <= 0.0 ? 0.0 : 1.0 - jmin (1.0, (1.0 - backdrop) / source); |
| 708 | + case BlendMode::HardLight: |
| 709 | + return source <= 0.5 ? 2.0 * backdrop * source : 1.0 - 2.0 * (1.0 - backdrop) * (1.0 - source); |
| 710 | + case BlendMode::SoftLight: |
| 711 | + { |
| 712 | + if (source <= 0.5) |
| 713 | + return backdrop - (1.0 - 2.0 * source) * backdrop * (1.0 - backdrop); |
| 714 | + |
| 715 | + const double d = backdrop <= 0.25 ? ((16.0 * backdrop - 12.0) * backdrop + 4.0) * backdrop : std::sqrt (backdrop); |
| 716 | + return backdrop + (2.0 * source - 1.0) * (d - backdrop); |
| 717 | + } |
| 718 | + case BlendMode::Difference: |
| 719 | + return std::abs (backdrop - source); |
| 720 | + case BlendMode::Exclusion: |
| 721 | + return backdrop + source - 2.0 * backdrop * source; |
| 722 | + case BlendMode::Multiply: |
| 723 | + return backdrop * source; |
| 724 | + case BlendMode::Hue: |
| 725 | + case BlendMode::Saturation: |
| 726 | + case BlendMode::Color: |
| 727 | + case BlendMode::Luminosity: |
| 728 | + return source; |
| 729 | + default: |
| 730 | + return source; |
| 731 | + } |
| 732 | +} |
| 733 | + |
| 734 | +DoubleRgb blendRgb (BlendMode mode, const DoubleRgb& backdrop, const DoubleRgb& source) noexcept |
| 735 | +{ |
| 736 | + if (mode == BlendMode::Hue || mode == BlendMode::Saturation || mode == BlendMode::Color || mode == BlendMode::Luminosity) |
| 737 | + { |
| 738 | + const auto hslBackdrop = rgbToHsl (backdrop); |
| 739 | + const auto hslSource = rgbToHsl (source); |
| 740 | + DoubleHsl blended = hslBackdrop; |
| 741 | + |
| 742 | + if (mode == BlendMode::Hue) |
| 743 | + blended.h = hslSource.h; |
| 744 | + else if (mode == BlendMode::Saturation) |
| 745 | + blended.s = hslSource.s; |
| 746 | + else if (mode == BlendMode::Color) |
| 747 | + { |
| 748 | + blended.h = hslSource.h; |
| 749 | + blended.s = hslSource.s; |
| 750 | + } |
| 751 | + else if (mode == BlendMode::Luminosity) |
| 752 | + blended.l = hslSource.l; |
| 753 | + |
| 754 | + return hslToRgb (blended); |
| 755 | + } |
| 756 | + |
| 757 | + return { |
| 758 | + blendChannel (mode, backdrop[0], source[0]), |
| 759 | + blendChannel (mode, backdrop[1], source[1]), |
| 760 | + blendChannel (mode, backdrop[2], source[2]) |
| 761 | + }; |
| 762 | +} |
649 | 763 | } // namespace |
650 | 764 |
|
651 | 765 | //============================================================================== |
652 | 766 |
|
653 | | -Color Color::mixedWith (Color other, float amount) const noexcept |
| 767 | +Color& Color::mixWith (Color other, float amount, ColorSpace space) noexcept |
654 | 768 | { |
655 | 769 | const float clamped = jlimit (0.0f, 1.0f, amount); |
| 770 | + |
| 771 | + if (space == ColorSpace::RGB) |
| 772 | + { |
| 773 | + if (clamped <= 0.0f) |
| 774 | + return *this; |
| 775 | + |
| 776 | + if (clamped >= 1.0f) |
| 777 | + { |
| 778 | + *this = other; |
| 779 | + return *this; |
| 780 | + } |
| 781 | + |
| 782 | + return blendWith (other.withMultipliedAlpha (clamped), BlendMode::SrcOver); |
| 783 | + } |
| 784 | + |
| 785 | + if (space == ColorSpace::SRGB) |
| 786 | + { |
| 787 | + const double ra = getRedFloat(); |
| 788 | + const double ga = getGreenFloat(); |
| 789 | + const double ba = getBlueFloat(); |
| 790 | + const double rb = other.getRedFloat(); |
| 791 | + const double gb = other.getGreenFloat(); |
| 792 | + const double bb = other.getBlueFloat(); |
| 793 | + |
| 794 | + const float outA = getAlphaFloat() + (other.getAlphaFloat() - getAlphaFloat()) * clamped; |
| 795 | + const float outR = static_cast<float> (ra + (rb - ra) * clamped); |
| 796 | + const float outG = static_cast<float> (ga + (gb - ga) * clamped); |
| 797 | + const float outB = static_cast<float> (ba + (bb - ba) * clamped); |
| 798 | + |
| 799 | + a = normalizedToComponent (outA); |
| 800 | + r = normalizedToComponent (outR); |
| 801 | + g = normalizedToComponent (outG); |
| 802 | + b = normalizedToComponent (outB); |
| 803 | + return *this; |
| 804 | + } |
| 805 | + |
656 | 806 | const auto mixed = spectralMix (srgbToLinear (*this), srgbToLinear (other), static_cast<double> (clamped)); |
657 | 807 | const float alpha = getAlphaFloat() + (other.getAlphaFloat() - getAlphaFloat()) * clamped; |
658 | 808 |
|
659 | | - return { |
660 | | - normalizedToComponent (alpha), |
661 | | - static_cast<uint8> (mixed[0]), |
662 | | - static_cast<uint8> (mixed[1]), |
663 | | - static_cast<uint8> (mixed[2]) |
| 809 | + a = normalizedToComponent (alpha); |
| 810 | + r = static_cast<uint8> (mixed[0]); |
| 811 | + g = static_cast<uint8> (mixed[1]); |
| 812 | + b = static_cast<uint8> (mixed[2]); |
| 813 | + return *this; |
| 814 | +} |
| 815 | + |
| 816 | +Color Color::mixedWith (Color other, float amount, ColorSpace space) const noexcept |
| 817 | +{ |
| 818 | + Color result (*this); |
| 819 | + result.mixWith (other, amount, space); |
| 820 | + return result; |
| 821 | +} |
| 822 | + |
| 823 | +//============================================================================== |
| 824 | + |
| 825 | +Color& Color::blendWith (Color src, BlendMode mode) noexcept |
| 826 | +{ |
| 827 | + const double srcAlpha = src.getAlphaFloat(); |
| 828 | + const double destAlpha = getAlphaFloat(); |
| 829 | + |
| 830 | + if (destAlpha <= 0.0) |
| 831 | + { |
| 832 | + *this = src; |
| 833 | + return *this; |
| 834 | + } |
| 835 | + |
| 836 | + if (srcAlpha <= 0.0) |
| 837 | + return *this; |
| 838 | + |
| 839 | + const DoubleRgb dest = { getRedFloat(), getGreenFloat(), getBlueFloat() }; |
| 840 | + const DoubleRgb source = { src.getRedFloat(), src.getGreenFloat(), src.getBlueFloat() }; |
| 841 | + const DoubleRgb blended = blendRgb (mode, dest, source); |
| 842 | + |
| 843 | + const double outAlpha = srcAlpha + destAlpha - srcAlpha * destAlpha; |
| 844 | + if (outAlpha <= 0.0) |
| 845 | + { |
| 846 | + a = 0; |
| 847 | + r = 0; |
| 848 | + g = 0; |
| 849 | + b = 0; |
| 850 | + return *this; |
| 851 | + } |
| 852 | + |
| 853 | + auto compositeChannel = [srcAlpha, destAlpha, outAlpha] (double cb, double cs, double bb) |
| 854 | + { |
| 855 | + const double co = (1.0 - srcAlpha) * cb * destAlpha |
| 856 | + + (1.0 - destAlpha) * cs * srcAlpha |
| 857 | + + destAlpha * srcAlpha * bb; |
| 858 | + return jlimit (0.0, 1.0, co / outAlpha); |
664 | 859 | }; |
| 860 | + |
| 861 | + const float outR = static_cast<float> (compositeChannel (dest[0], source[0], blended[0])); |
| 862 | + const float outG = static_cast<float> (compositeChannel (dest[1], source[1], blended[1])); |
| 863 | + const float outB = static_cast<float> (compositeChannel (dest[2], source[2], blended[2])); |
| 864 | + const float outA = static_cast<float> (jlimit (0.0, 1.0, outAlpha)); |
| 865 | + |
| 866 | + r = normalizedToComponent (outR); |
| 867 | + g = normalizedToComponent (outG); |
| 868 | + b = normalizedToComponent (outB); |
| 869 | + a = normalizedToComponent (outA); |
| 870 | + return *this; |
665 | 871 | } |
666 | 872 |
|
| 873 | +Color Color::blendedWith (Color src, BlendMode mode) const noexcept |
| 874 | +{ |
| 875 | + Color result (*this); |
| 876 | + result.blendWith (src, mode); |
| 877 | + return result; |
| 878 | +} |
| 879 | + |
| 880 | +//============================================================================== |
| 881 | + |
667 | 882 | std::tuple<float, float, float> Color::toHSLuv() const noexcept |
668 | 883 | { |
669 | 884 | const auto hsluv = hsluvFromRgb (getRedFloat(), getGreenFloat(), getBlueFloat()); |
|
0 commit comments