1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components/common/properties/color.h"
17
18 #include <cmath>
19 #include <regex>
20
21 #include "base/utils/linear_map.h"
22 #include "base/utils/string_utils.h"
23 #include "base/utils/utils.h"
24
25 namespace OHOS::Ace {
26 namespace {
27
28 constexpr uint32_t COLOR_ALPHA_OFFSET = 24;
29 constexpr uint32_t COLOR_STRING_SIZE_STANDARD = 8;
30 constexpr uint32_t COLOR_STRING_BASE = 16;
31 constexpr uint32_t RGB_SUB_MATCH_SIZE = 4;
32 constexpr uint32_t RGBA_SUB_MATCH_SIZE = 5;
33
34 const std::regex COLOR_WITH_MAGIC("#[0-9A-Fa-f]{6,8}");
35 const std::regex COLOR_WITH_MAGIC_MINI("#[0-9A-Fa-f]{3,4}");
36 const std::regex COLOR_WITH_RGB(R"(rgb\(([0-9]{1,3})\,([0-9]{1,3})\,([0-9]{1,3})\))", std::regex::icase);
37 const std::regex COLOR_WITH_RGBA(R"(rgba\(([0-9]{1,3})\,([0-9]{1,3})\,([0-9]{1,3})\,(\d+\.?\d*)\))", std::regex::icase);
38 constexpr double GAMMA_FACTOR = 2.2;
39 constexpr float MAX_ALPHA = 255.0f;
40 constexpr char HEX[] = "0123456789ABCDEF";
41 constexpr uint8_t BIT_LENGTH_INT32 = 8;
42 constexpr uint8_t MIN_RGB_VALUE = 0;
43 constexpr uint8_t MAX_RGB_VALUE = 255;
44 constexpr double MIN_RGBA_OPACITY = 0.0;
45 constexpr double MAX_RGBA_OPACITY = 1.0;
46
47 } // namespace
48
49 const Color Color::TRANSPARENT = Color(0x00000000);
50 const Color Color::WHITE = Color(0xffffffff);
51 const Color Color::BLACK = Color(0xff000000);
52 const Color Color::RED = Color(0xffff0000);
53 const Color Color::GREEN = Color(0xff00ff00);
54 const Color Color::BLUE = Color(0xff0000ff);
55 const Color Color::GRAY = Color(0xffc0c0c0);
56 const Color Color::FOREGROUND = Color(0x00000001); // foreground color and foreground color strategy identification
57
58 const LinearColor LinearColor::TRANSPARENT = LinearColor(0x00000000);
59 const LinearColor LinearColor::WHITE = LinearColor(0xffffffff);
60 const LinearColor LinearColor::BLACK = LinearColor(0xff000000);
61 const LinearColor LinearColor::RED = LinearColor(0xffff0000);
62 const LinearColor LinearColor::GREEN = LinearColor(0xff00ff00);
63 const LinearColor LinearColor::BLUE = LinearColor(0xff0000ff);
64 const LinearColor LinearColor::GRAY = LinearColor(0xffc0c0c0);
65
FromString(std::string colorStr,uint32_t maskAlpha,Color defaultColor)66 Color Color::FromString(std::string colorStr, uint32_t maskAlpha, Color defaultColor)
67 {
68 if (colorStr.empty()) {
69 // empty string, return transparent
70 return Color::TRANSPARENT;
71 }
72
73 // Remove all " ".
74 colorStr.erase(std::remove(colorStr.begin(), colorStr.end(), ' '), colorStr.end());
75
76 std::smatch matches;
77 // Regex match for #909090 or #90909090.
78 if (std::regex_match(colorStr, matches, COLOR_WITH_MAGIC)) {
79 colorStr.erase(0, 1);
80 auto value = stoul(colorStr, nullptr, COLOR_STRING_BASE);
81 if (colorStr.length() < COLOR_STRING_SIZE_STANDARD) {
82 // no alpha specified, set alpha to 0xff
83 value |= maskAlpha;
84 }
85 return Color(value);
86 }
87 // Regex match for #rgb or #rgba.
88 if (std::regex_match(colorStr, matches, COLOR_WITH_MAGIC_MINI)) {
89 colorStr.erase(0, 1);
90 std::string newColorStr;
91 // translate #rgb or #rgba to #rrggbb or #rrggbbaa
92 for (auto& c : colorStr) {
93 newColorStr += c;
94 newColorStr += c;
95 }
96 auto value = stoul(newColorStr, nullptr, COLOR_STRING_BASE);
97 if (newColorStr.length() < COLOR_STRING_SIZE_STANDARD) {
98 // no alpha specified, set alpha to 0xff
99 value |= maskAlpha;
100 }
101 return Color(value);
102 }
103 // Regex match for rgb(90,254,180).
104 if (std::regex_match(colorStr, matches, COLOR_WITH_RGB)) {
105 if (matches.size() == RGB_SUB_MATCH_SIZE) {
106 auto red = static_cast<uint8_t>(std::stoi(matches[1])); // red value.
107 auto green = static_cast<uint8_t>(std::stoi(matches[2])); // green value.
108 auto blue = static_cast<uint8_t>(std::stoi(matches[3])); // blue value.
109 return FromRGB(red, green, blue);
110 }
111 }
112 // Regex match for rgba(90,254,180,0.5).
113 if (std::regex_match(colorStr, matches, COLOR_WITH_RGBA)) {
114 if (matches.size() == RGBA_SUB_MATCH_SIZE) {
115 auto red = static_cast<uint8_t>(std::stoi(matches[1]));
116 auto green = static_cast<uint8_t>(std::stoi(matches[2]));
117 auto blue = static_cast<uint8_t>(std::stoi(matches[3]));
118 auto opacity = static_cast<double>(std::stod(matches[4]));
119 return FromRGBO(red, green, blue, opacity);
120 }
121 }
122 // match for special string
123 static const LinearMapNode<Color> colorTable[] = {
124 { "black", Color(0xff000000) },
125 { "blue", Color(0xff0000ff) },
126 { "gray", Color(0xffc0c0c0) },
127 { "green", Color(0xff00ff00) },
128 { "red", Color(0xffff0000) },
129 { "white", Color(0xffffffff) },
130 };
131 int64_t colorIndex = BinarySearchFindIndex(colorTable, ArraySize(colorTable), colorStr.c_str());
132 if (colorIndex != -1) {
133 return colorTable[colorIndex].value;
134 }
135
136 // parse uint32_t color string.
137 auto uint32Color = StringUtils::StringToUint(colorStr);
138 if (uint32Color > 0) {
139 Color value;
140 if (uint32Color >> COLOR_ALPHA_OFFSET == 0) {
141 value = Color(uint32Color).ChangeAlpha(MAX_ALPHA);
142 } else {
143 value = Color(uint32Color);
144 }
145 return value;
146 }
147
148 // Default color.
149 return defaultColor;
150 }
151
ParseColorString(std::string colorStr,Color & color,uint32_t maskAlpha)152 bool Color::ParseColorString(std::string colorStr, Color& color, uint32_t maskAlpha)
153 {
154 if (colorStr.empty()) {
155 return false;
156 }
157
158 // Remove all " ".
159 colorStr.erase(std::remove(colorStr.begin(), colorStr.end(), ' '), colorStr.end());
160
161 return (MatchColorWithMagic(colorStr, maskAlpha, color) || MatchColorWithMagicMini(colorStr, maskAlpha, color) ||
162 MatchColorWithRGB(colorStr, color) || MatchColorWithRGBA(colorStr, color) ||
163 MatchColorSpecialString(colorStr, color) || ParseUintColorString(colorStr, color));
164 }
165
ColorToString() const166 std::string Color::ColorToString() const
167 {
168 std::string colorStr;
169 int count = 0;
170 uint32_t value = GetValue();
171 while (count++ < BIT_LENGTH_INT32) {
172 colorStr = HEX[(value & 0xf)] + colorStr;
173 value >>= 4;
174 }
175 colorStr = "#" + colorStr;
176 return colorStr;
177 }
178
179 // for example str = #FFFFFFFF
ColorFromString(const std::string & str)180 Color Color::ColorFromString(const std::string& str)
181 {
182 static const int32_t colorStrLen = 9;
183 static const int32_t offset = 4;
184
185 if (str.length() != colorStrLen || str.find('#') != 0) {
186 LOGE("UITree |ERROR| invalid %{public}s", str.c_str());
187 return Color::BLACK;
188 }
189
190 std::string colorStr = str.substr(1, colorStrLen - 1);
191 uint32_t value = 0;
192 for (const auto& it : colorStr) {
193 value <<= offset;
194 value += it < 'A' ? it - '0' : it - '7';
195 }
196
197 return Color(value);
198 }
199
FromARGB(uint8_t alpha,uint8_t red,uint8_t green,uint8_t blue)200 Color Color::FromARGB(uint8_t alpha, uint8_t red, uint8_t green, uint8_t blue)
201 {
202 ColorParam colorValue {
203 #if BIG_ENDIANNESS
204 .argb = { .alpha = alpha, .red = red, .green = green, .blue = blue }
205 #else
206 .argb = { .blue = blue, .green = green, .red = red, .alpha = alpha }
207 #endif
208 };
209 return Color(colorValue);
210 }
211
FromRGBO(uint8_t red,uint8_t green,uint8_t blue,double opacity)212 Color Color::FromRGBO(uint8_t red, uint8_t green, uint8_t blue, double opacity)
213 {
214 return FromARGB(static_cast<uint8_t>(round(opacity * 0xff)) & 0xff, red, green, blue);
215 }
216
FromRGB(uint8_t red,uint8_t green,uint8_t blue)217 Color Color::FromRGB(uint8_t red, uint8_t green, uint8_t blue)
218 {
219 return FromARGB(0xff, red, green, blue);
220 }
221
BlendColor(const Color & overlayColor) const222 Color Color::BlendColor(const Color& overlayColor) const
223 {
224 if (GetValue() == Color::TRANSPARENT.GetValue()) {
225 return overlayColor;
226 }
227 if (GetAlpha() < static_cast<uint8_t>(MAX_ALPHA)) {
228 return BlendColorWithAlpha(overlayColor);
229 }
230 auto alphaRate = static_cast<float>(overlayColor.GetAlpha()) / MAX_ALPHA;
231 auto newRed = static_cast<uint8_t>(GetRed() * (1.0f - alphaRate) + overlayColor.GetRed() * alphaRate);
232 auto newGreen = static_cast<uint8_t>(GetGreen() * (1.0f - alphaRate) + overlayColor.GetGreen() * alphaRate);
233 auto newBlue = static_cast<uint8_t>(GetBlue() * (1.0f - alphaRate) + overlayColor.GetBlue() * alphaRate);
234 return Color::FromRGB(newRed, newGreen, newBlue);
235 }
236
CalculateBlend(float alphaLeft,float alphaRight,float valueLeft,float valueRight) const237 float Color::CalculateBlend(float alphaLeft, float alphaRight, float valueLeft, float valueRight) const
238 {
239 return (valueLeft * alphaLeft * (1.0 - alphaRight) + valueRight * alphaRight) /
240 (alphaLeft + alphaRight - alphaLeft * alphaRight);
241 }
242
BlendColorWithAlpha(const Color & overlayColor) const243 Color Color::BlendColorWithAlpha(const Color& overlayColor) const
244 {
245 float alphaA = GetAlpha() / 255.0;
246 float alphaB = overlayColor.GetAlpha() / 255.0;
247 float blendAlpha = alphaA + alphaB - alphaA * alphaB;
248 float blendRed = CalculateBlend(alphaA, alphaB, GetRed() / 255.0, overlayColor.GetRed() / 255.0);
249 float blendGreen = CalculateBlend(alphaA, alphaB, GetGreen() / 255.0, overlayColor.GetGreen() / 255.0);
250 float blendBlue = CalculateBlend(alphaA, alphaB, GetBlue() / 255.0, overlayColor.GetBlue() / 255.0);
251
252 return Color::FromARGB(blendAlpha * 255, blendRed * 255, blendGreen * 255, blendBlue * 255);
253 }
254
LineColorTransition(const Color & startColor,const Color & endColor,double percent)255 const Color Color::LineColorTransition(const Color& startColor, const Color& endColor, double percent)
256 {
257 uint8_t red = 0;
258 uint8_t green = 0;
259 uint8_t blue = 0;
260 uint8_t alpha = 0;
261
262 red = static_cast<uint8_t>((endColor.GetRed()- startColor.GetRed()) * percent) + startColor.GetRed();
263 green = static_cast<uint8_t>((endColor.GetGreen() - startColor.GetGreen()) * percent) + startColor.GetGreen();
264 blue = static_cast<uint8_t>((endColor.GetBlue() - startColor.GetBlue()) * percent) + startColor.GetBlue();
265 alpha = static_cast<uint8_t>((endColor.GetAlpha() - startColor.GetAlpha()) * percent) + startColor.GetAlpha();
266
267 return Color::FromARGB(alpha, red, green, blue);
268 }
269
BlendOpacity(double opacityRatio) const270 Color Color::BlendOpacity(double opacityRatio) const
271 {
272 int32_t alpha = static_cast<int32_t>(GetAlpha() * opacityRatio);
273 alpha = std::clamp(alpha, 0, UINT8_MAX);
274 return Color::FromARGB(alpha, GetRed(), GetGreen(), GetBlue());
275 }
276
ChangeOpacity(double opacity) const277 Color Color::ChangeOpacity(double opacity) const
278 {
279 return Color::FromRGBO(GetRed(), GetGreen(), GetBlue(), opacity);
280 }
281
ChangeAlpha(uint8_t alpha) const282 Color Color::ChangeAlpha(uint8_t alpha) const
283 {
284 return Color::FromARGB(alpha, GetRed(), GetGreen(), GetBlue());
285 }
286
operator +(const Color & color) const287 Color Color::operator+(const Color& color) const
288 {
289 // convert first color from ARGB to linear
290 double firstLinearRed = 0.0;
291 double firstLinearGreen = 0.0;
292 double firstLinearBlue = 0.0;
293 ConvertGammaToLinear(*this, firstLinearRed, firstLinearGreen, firstLinearBlue);
294
295 // convert second color from ARGB to linear
296 double secondLinearRed = 0.0;
297 double secondLinearGreen = 0.0;
298 double secondLinearBlue = 0.0;
299 ConvertGammaToLinear(color, secondLinearRed, secondLinearGreen, secondLinearBlue);
300
301 // get linear result and convert to gamma
302 return ConvertLinearToGamma(GetAlpha() + color.GetAlpha(), firstLinearRed + secondLinearRed,
303 firstLinearGreen + secondLinearGreen, firstLinearBlue + secondLinearBlue);
304 }
305
operator -(const Color & color) const306 Color Color::operator-(const Color& color) const
307 {
308 // convert first color from ARGB to linear
309 double firstLinearRed = 0.0;
310 double firstLinearGreen = 0.0;
311 double firstLinearBlue = 0.0;
312 ConvertGammaToLinear(*this, firstLinearRed, firstLinearGreen, firstLinearBlue);
313
314 // convert second color from ARGB to linear
315 double secondLinearRed = 0.0;
316 double secondLinearGreen = 0.0;
317 double secondLinearBlue = 0.0;
318 ConvertGammaToLinear(color, secondLinearRed, secondLinearGreen, secondLinearBlue);
319
320 // get linear result and convert to gamma
321 return ConvertLinearToGamma(GetAlpha() - color.GetAlpha(), firstLinearRed - secondLinearRed,
322 firstLinearGreen - secondLinearGreen, firstLinearBlue - secondLinearBlue);
323 }
324
operator *(double value) const325 Color Color::operator*(double value) const
326 {
327 // convert color from ARGB to linear
328 double linearRed = 0.0;
329 double linearGreen = 0.0;
330 double linearBlue = 0.0;
331 ConvertGammaToLinear(*this, linearRed, linearGreen, linearBlue);
332
333 // get linear result and convert to gamma
334 return ConvertLinearToGamma(GetAlpha() * value, linearRed * value, linearGreen * value, linearBlue * value);
335 }
336
operator /(double value) const337 Color Color::operator/(double value) const
338 {
339 if (NearZero(value)) {
340 return *this;
341 }
342 // convert color from ARGB to linear
343 double linearRed = 0.0;
344 double linearGreen = 0.0;
345 double LinearBlue = 0.0;
346 ConvertGammaToLinear(*this, linearRed, linearGreen, LinearBlue);
347
348 // get linear result and convert to gamma
349 return ConvertLinearToGamma(GetAlpha() / value, linearRed / value, linearGreen / value, LinearBlue / value);
350 }
351
ConvertGammaToLinear(uint8_t value)352 double Color::ConvertGammaToLinear(uint8_t value)
353 {
354 return std::pow(value, GAMMA_FACTOR);
355 }
356
ConvertLinearToGamma(double value)357 uint8_t Color::ConvertLinearToGamma(double value)
358 {
359 return std::clamp(static_cast<int32_t>(round(std::pow(value, 1.0 / GAMMA_FACTOR))), 0, UINT8_MAX);
360 }
361
ConvertGammaToLinear(const Color & gammaColor,double & linearRed,double & linearGreen,double & linearBlue)362 void Color::ConvertGammaToLinear(const Color& gammaColor, double& linearRed, double& linearGreen, double& linearBlue)
363 {
364 linearRed = ConvertGammaToLinear(gammaColor.GetRed());
365 linearGreen = ConvertGammaToLinear(gammaColor.GetGreen());
366 linearBlue = ConvertGammaToLinear(gammaColor.GetBlue());
367 }
368
ConvertLinearToGamma(double alpha,double linearRed,double linearGreen,double linearBlue)369 Color Color::ConvertLinearToGamma(double alpha, double linearRed, double linearGreen, double linearBlue)
370 {
371 uint8_t gammaRed = ConvertLinearToGamma(linearRed);
372 uint8_t gammaGreen = ConvertLinearToGamma(linearGreen);
373 uint8_t gammaBlue = ConvertLinearToGamma(linearBlue);
374 uint8_t gammaAlpha = std::clamp(static_cast<int32_t>(round(alpha)), 0, UINT8_MAX);
375
376 return FromARGB(gammaAlpha, gammaRed, gammaGreen, gammaBlue);
377 }
378
MatchColorWithMagic(std::string & colorStr,uint32_t maskAlpha,Color & color)379 bool Color::MatchColorWithMagic(std::string& colorStr, uint32_t maskAlpha, Color& color)
380 {
381 std::smatch matches;
382 // Regex match for #909090 or #90909090.
383 if (std::regex_match(colorStr, matches, COLOR_WITH_MAGIC)) {
384 colorStr.erase(0, 1);
385 auto value = stoul(colorStr, nullptr, COLOR_STRING_BASE);
386 if (colorStr.length() < COLOR_STRING_SIZE_STANDARD) {
387 // no alpha specified, set alpha to 0xff
388 value |= maskAlpha;
389 }
390 color = Color(value);
391 return true;
392 }
393
394 return false;
395 }
396
MatchColorWithMagicMini(std::string & colorStr,uint32_t maskAlpha,Color & color)397 bool Color::MatchColorWithMagicMini(std::string& colorStr, uint32_t maskAlpha, Color& color)
398 {
399 std::smatch matches;
400 if (std::regex_match(colorStr, matches, COLOR_WITH_MAGIC_MINI)) {
401 colorStr.erase(0, 1);
402 std::string newColorStr;
403 // translate #rgb or #rgba to #rrggbb or #rrggbbaa
404 for (auto& c : colorStr) {
405 newColorStr += c;
406 newColorStr += c;
407 }
408 auto value = stoul(newColorStr, nullptr, COLOR_STRING_BASE);
409 if (newColorStr.length() < COLOR_STRING_SIZE_STANDARD) {
410 // no alpha specified, set alpha to 0xff
411 value |= maskAlpha;
412 }
413 color = Color(value);
414 return true;
415 }
416
417 return false;
418 }
419
MatchColorWithRGB(const std::string & colorStr,Color & color)420 bool Color::MatchColorWithRGB(const std::string& colorStr, Color& color)
421 {
422 std::smatch matches;
423 if (std::regex_match(colorStr, matches, COLOR_WITH_RGB)) {
424 if (matches.size() == RGB_SUB_MATCH_SIZE) {
425 auto redInt = std::stoi(matches[1]);
426 auto greenInt = std::stoi(matches[2]);
427 auto blueInt = std::stoi(matches[3]);
428 if (!IsRGBValid(redInt) || !IsRGBValid(greenInt) || !IsRGBValid(blueInt)) {
429 return false;
430 }
431
432 auto red = static_cast<uint8_t>(redInt);
433 auto green = static_cast<uint8_t>(greenInt);
434 auto blue = static_cast<uint8_t>(blueInt);
435 color = FromRGB(red, green, blue);
436 return true;
437 }
438 }
439 return false;
440 }
441
MatchColorWithRGBA(const std::string & colorStr,Color & color)442 bool Color::MatchColorWithRGBA(const std::string& colorStr, Color& color)
443 {
444 std::smatch matches;
445 if (std::regex_match(colorStr, matches, COLOR_WITH_RGBA)) {
446 if (matches.size() == RGBA_SUB_MATCH_SIZE) {
447 auto redInt = std::stoi(matches[1]);
448 auto greenInt = std::stoi(matches[2]);
449 auto blueInt = std::stoi(matches[3]);
450 auto opacityDouble = std::stod(matches[4]);
451 if (!IsRGBValid(redInt) || !IsRGBValid(greenInt) || !IsRGBValid(blueInt) ||
452 !IsOpacityValid(opacityDouble)) {
453 return false;
454 }
455
456 auto red = static_cast<uint8_t>(redInt);
457 auto green = static_cast<uint8_t>(greenInt);
458 auto blue = static_cast<uint8_t>(blueInt);
459 auto opacity = static_cast<double>(opacityDouble);
460
461 color = FromRGBO(red, green, blue, opacity);
462 return true;
463 }
464 }
465
466 return false;
467 }
468
MatchColorSpecialString(const std::string & colorStr,Color & color)469 bool Color::MatchColorSpecialString(const std::string& colorStr, Color& color)
470 {
471 static const LinearMapNode<Color> colorTable[] = {
472 { "black", Color(0xff000000) },
473 { "blue", Color(0xff0000ff) },
474 { "gray", Color(0xffc0c0c0) },
475 { "green", Color(0xff00ff00) },
476 { "red", Color(0xffff0000) },
477 { "transparent", Color(0x00000000) },
478 { "white", Color(0xffffffff) },
479 };
480
481 int64_t colorIndex = BinarySearchFindIndex(colorTable, ArraySize(colorTable), colorStr.c_str());
482 if (colorIndex != -1) {
483 color = colorTable[colorIndex].value;
484 return true;
485 }
486
487 return false;
488 }
489
ParseUintColorString(const std::string & colorStr,Color & color)490 bool Color::ParseUintColorString(const std::string& colorStr, Color& color)
491 {
492 auto uint32Color = StringUtils::StringToUint(colorStr);
493 if (uint32Color > 0) {
494 if (uint32Color >> COLOR_ALPHA_OFFSET == 0) {
495 color = Color(uint32Color).ChangeAlpha(MAX_ALPHA);
496 } else {
497 color = Color(uint32Color);
498 }
499 return true;
500 }
501
502 return false;
503 }
504
IsRGBValid(int value)505 bool Color::IsRGBValid(int value)
506 {
507 return value >= MIN_RGB_VALUE && value <= MAX_RGB_VALUE;
508 }
509
IsOpacityValid(double value)510 bool Color::IsOpacityValid(double value)
511 {
512 return value >= MIN_RGBA_OPACITY && value <= MAX_RGBA_OPACITY;
513 }
514
515 } // namespace OHOS::Ace
516