2828
2929using namespace yup ;
3030
31+ namespace
32+ {
33+ uint8 toByte (double value)
34+ {
35+ return static_cast <uint8> (roundToInt (jlimit (0.0 , 1.0 , value) * 255.0 ));
36+ }
37+
38+ Color colorFromFloat (double r, double g, double b)
39+ {
40+ return Color (255 , toByte (r), toByte (g), toByte (b));
41+ }
42+
43+ double blendChannelExpected (BlendMode mode, double backdrop, double source)
44+ {
45+ switch (mode)
46+ {
47+ case BlendMode::SrcOver:
48+ return source;
49+ case BlendMode::Screen:
50+ return 1.0 - (1.0 - backdrop) * (1.0 - source);
51+ case BlendMode::Overlay:
52+ return backdrop <= 0.5 ? 2.0 * backdrop * source : 1.0 - 2.0 * (1.0 - backdrop) * (1.0 - source);
53+ case BlendMode::Darken:
54+ return jmin (backdrop, source);
55+ case BlendMode::Lighten:
56+ return jmax (backdrop, source);
57+ case BlendMode::ColorDodge:
58+ return source >= 1.0 ? 1.0 : jmin (1.0 , backdrop / (1.0 - source));
59+ case BlendMode::ColorBurn:
60+ return source <= 0.0 ? 0.0 : 1.0 - jmin (1.0 , (1.0 - backdrop) / source);
61+ case BlendMode::HardLight:
62+ return source <= 0.5 ? 2.0 * backdrop * source : 1.0 - 2.0 * (1.0 - backdrop) * (1.0 - source);
63+ case BlendMode::SoftLight:
64+ {
65+ if (source <= 0.5 )
66+ return backdrop - (1.0 - 2.0 * source) * backdrop * (1.0 - backdrop);
67+
68+ const double d = backdrop <= 0.25 ? ((16.0 * backdrop - 12.0 ) * backdrop + 4.0 ) * backdrop : std::sqrt (backdrop);
69+ return backdrop + (2.0 * source - 1.0 ) * (d - backdrop);
70+ }
71+ case BlendMode::Difference:
72+ return std::abs (backdrop - source);
73+ case BlendMode::Exclusion:
74+ return backdrop + source - 2.0 * backdrop * source;
75+ case BlendMode::Multiply:
76+ return backdrop * source;
77+ case BlendMode::Hue:
78+ case BlendMode::Saturation:
79+ case BlendMode::Color:
80+ case BlendMode::Luminosity:
81+ return source;
82+ default :
83+ return source;
84+ }
85+ }
86+ } // namespace
87+
3188TEST (ColorTests, Default_Constructor)
3289{
3390 Color c;
@@ -1006,6 +1063,84 @@ TEST (ColorTests, BlendWith_Opacity)
10061063 EXPECT_EQ (blended.getARGB (), expected.getARGB ());
10071064}
10081065
1066+ TEST (ColorTests, BlendWith_SrcAlphaZero_NoChange)
1067+ {
1068+ const Color dest (200 , 10 , 20 , 30 );
1069+ const Color src (0 , 200 , 100 , 50 );
1070+ const auto blended = dest.blendedWith (src, BlendMode::SrcOver);
1071+ EXPECT_EQ (blended.getARGB (), dest.getARGB ());
1072+ }
1073+
1074+ TEST (ColorTests, BlendWith_DestAlphaZero_ReturnsSrc)
1075+ {
1076+ const Color dest (0 , 10 , 20 , 30 );
1077+ const Color src (100 , 40 , 50 , 60 );
1078+ const auto blended = dest.blendedWith (src, BlendMode::Screen);
1079+ EXPECT_EQ (blended.getARGB (), src.getARGB ());
1080+ }
1081+
1082+ TEST (ColorTests, BlendWith_ChannelModes)
1083+ {
1084+ const double backdropValue = 0.2 ;
1085+ const double sourceValue = 0.7 ;
1086+ const auto backdrop = colorFromFloat (backdropValue, backdropValue, backdropValue);
1087+ const auto source = colorFromFloat (sourceValue, sourceValue, sourceValue);
1088+
1089+ const BlendMode modes[] = {
1090+ BlendMode::SrcOver,
1091+ BlendMode::Screen,
1092+ BlendMode::Overlay,
1093+ BlendMode::Darken,
1094+ BlendMode::Lighten,
1095+ BlendMode::ColorDodge,
1096+ BlendMode::ColorBurn,
1097+ BlendMode::HardLight,
1098+ BlendMode::SoftLight,
1099+ BlendMode::Difference,
1100+ BlendMode::Exclusion,
1101+ BlendMode::Multiply
1102+ };
1103+
1104+ for (const auto mode : modes)
1105+ {
1106+ const auto blended = backdrop.blendedWith (source, mode);
1107+ const double expected = blendChannelExpected (mode, backdropValue, sourceValue);
1108+ EXPECT_NEAR (blended.getRedFloat (), expected, 1 .0f / 255 .0f );
1109+ EXPECT_NEAR (blended.getGreenFloat (), expected, 1 .0f / 255 .0f );
1110+ EXPECT_NEAR (blended.getBlueFloat (), expected, 1 .0f / 255 .0f );
1111+ }
1112+ }
1113+
1114+ TEST (ColorTests, BlendWith_HslModes)
1115+ {
1116+ const auto backdrop = Color::fromHSL (0 .1f , 0 .7f , 0 .4f );
1117+ const auto source = Color::fromHSL (0 .7f , 0 .3f , 0 .8f );
1118+
1119+ const auto hueResult = backdrop.blendedWith (source, BlendMode::Hue);
1120+ const auto [hueH, hueS, hueL] = hueResult.toHSL ();
1121+ EXPECT_NEAR (hueH, std::get<0 > (source.toHSL ()), 0 .02f );
1122+ EXPECT_NEAR (hueS, std::get<1 > (backdrop.toHSL ()), 0 .02f );
1123+ EXPECT_NEAR (hueL, std::get<2 > (backdrop.toHSL ()), 0 .02f );
1124+
1125+ const auto satResult = backdrop.blendedWith (source, BlendMode::Saturation);
1126+ const auto [satH, satS, satL] = satResult.toHSL ();
1127+ EXPECT_NEAR (satH, std::get<0 > (backdrop.toHSL ()), 0 .02f );
1128+ EXPECT_NEAR (satS, std::get<1 > (source.toHSL ()), 0 .02f );
1129+ EXPECT_NEAR (satL, std::get<2 > (backdrop.toHSL ()), 0 .02f );
1130+
1131+ const auto colorResult = backdrop.blendedWith (source, BlendMode::Color);
1132+ const auto [colH, colS, colL] = colorResult.toHSL ();
1133+ EXPECT_NEAR (colH, std::get<0 > (source.toHSL ()), 0 .02f );
1134+ EXPECT_NEAR (colS, std::get<1 > (source.toHSL ()), 0 .02f );
1135+ EXPECT_NEAR (colL, std::get<2 > (backdrop.toHSL ()), 0 .02f );
1136+
1137+ const auto lumResult = backdrop.blendedWith (source, BlendMode::Luminosity);
1138+ const auto [lumH, lumS, lumL] = lumResult.toHSL ();
1139+ EXPECT_NEAR (lumH, std::get<0 > (backdrop.toHSL ()), 0 .02f );
1140+ EXPECT_NEAR (lumS, std::get<1 > (backdrop.toHSL ()), 0 .02f );
1141+ EXPECT_NEAR (lumL, std::get<2 > (source.toHSL ()), 0 .02f );
1142+ }
1143+
10091144TEST (ColorTests, MixWith_ModifiesInPlace)
10101145{
10111146 Color base (255 , 20 , 40 , 60 );
@@ -1016,3 +1151,15 @@ TEST (ColorTests, MixWith_ModifiesInPlace)
10161151 const auto expected = base.mixedWith (other, 0 .35f , ColorSpace::SRGB);
10171152 EXPECT_EQ (copy.getARGB (), expected.getARGB ());
10181153}
1154+
1155+ TEST (ColorTests, MixWith_RgbUsesSrcOver)
1156+ {
1157+ const Color base (128 , 40 , 80 , 120 );
1158+ const Color other (200 , 200 , 20 , 40 );
1159+ const float amount = 0 .3f ;
1160+
1161+ auto rgbMix = base;
1162+ rgbMix.mixWith (other, amount, ColorSpace::RGB);
1163+ const auto expected = base.blendedWith (other.withMultipliedAlpha (amount), BlendMode::SrcOver);
1164+ EXPECT_EQ (rgbMix.getARGB (), expected.getARGB ());
1165+ }
0 commit comments