1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/common/themes/autogenerated_theme_util.h"
6 
7 #include "ui/gfx/color_utils.h"
8 
9 // Decreases the lightness of the given color.
DarkenColor(SkColor color,float change)10 SkColor DarkenColor(SkColor color, float change) {
11   color_utils::HSL hsl;
12   SkColorToHSL(color, &hsl);
13   hsl.l -= change;
14   if (hsl.l >= 0.0f)
15     return HSLToSkColor(hsl, 255);
16   return color;
17 }
18 
19 // Increases the lightness of |source| until it reaches |contrast_ratio| with
20 // |base| or reaches |white_contrast| with white. This avoids decreasing
21 // saturation, as the alternative contrast-guaranteeing functions in color_utils
22 // would do.
LightenUntilContrast(SkColor source,SkColor base,float contrast_ratio,float white_contrast)23 SkColor LightenUntilContrast(SkColor source,
24                              SkColor base,
25                              float contrast_ratio,
26                              float white_contrast) {
27   const float kBaseLuminance = color_utils::GetRelativeLuminance(base);
28   constexpr float kWhiteLuminance = 1.0f;
29 
30   color_utils::HSL hsl;
31   SkColorToHSL(source, &hsl);
32   float min_l = hsl.l;
33   float max_l = 1.0f;
34 
35   // Need only precision of 2 digits.
36   while (max_l - min_l > 0.01) {
37     hsl.l = min_l + (max_l - min_l) / 2;
38     float luminance = color_utils::GetRelativeLuminance(HSLToSkColor(hsl, 255));
39     if (color_utils::GetContrastRatio(kBaseLuminance, luminance) >=
40             contrast_ratio ||
41         (color_utils::GetContrastRatio(kWhiteLuminance, luminance) <
42          white_contrast)) {
43       max_l = hsl.l;
44     } else {
45       min_l = hsl.l;
46     }
47   }
48 
49   hsl.l = max_l;
50   return HSLToSkColor(hsl, 255);
51 }
52 
GetAutogeneratedThemeColors(SkColor color)53 AutogeneratedThemeColors GetAutogeneratedThemeColors(SkColor color) {
54   SkColor frame_color = color;
55   SkColor frame_text_color;
56   SkColor active_tab_color = color;
57   SkColor active_tab_text_color;
58 
59   constexpr float kDarkenStep = 0.03f;
60   constexpr float kMinWhiteContrast = 1.3f;
61   constexpr float kNoWhiteContrast = 0.0f;
62 
63   // Used to determine what we consider a very dark color.
64   constexpr float kMaxLuminosityForDark = 0.05f;
65 
66   // Increasingly darken frame color and calculate the rest until colors with
67   // sufficient contrast are found.
68   while (true) {
69     // Calculate frame color to have sufficient contrast with white or dark grey
70     // text.
71     frame_text_color = color_utils::GetColorWithMaxContrast(frame_color);
72     SkColor blend_target =
73         color_utils::GetColorWithMaxContrast(frame_text_color);
74     frame_color = color_utils::BlendForMinContrast(
75                       frame_color, frame_text_color, blend_target,
76                       kAutogeneratedThemeTextPreferredContrast)
77                       .color;
78 
79     // Generate active tab color so that it has enough contrast with the
80     // |frame_color| to avoid the isolation line in the tab strip.
81     active_tab_color = LightenUntilContrast(
82         frame_color, frame_color, kAutogeneratedThemeActiveTabMinContrast,
83         kNoWhiteContrast);
84 
85     // We want more contrast between frame and active tab for dark colors.
86     color_utils::HSL hsl;
87     SkColorToHSL(frame_color, &hsl);
88     float preferred_contrast =
89         hsl.l <= kMaxLuminosityForDark
90             ? kAutogeneratedThemeActiveTabPreferredContrastForDark
91             : kAutogeneratedThemeActiveTabPreferredContrast;
92 
93     // Try lightening the color to get more contrast with frame without getting
94     // too close to white.
95     active_tab_color = LightenUntilContrast(
96         active_tab_color, frame_color, preferred_contrast, kMinWhiteContrast);
97 
98     // If we didn't succeed in generating active tab color with minimum
99     // contrast with frame, then darken the frame color and try again.
100     if (color_utils::GetContrastRatio(frame_color, active_tab_color) <
101         kAutogeneratedThemeActiveTabMinContrast) {
102       frame_color = DarkenColor(frame_color, kDarkenStep);
103       continue;
104     }
105 
106     // Select active tab text color, if possible.
107     active_tab_text_color =
108         color_utils::GetColorWithMaxContrast(active_tab_color);
109 
110     if (!color_utils::IsDark(active_tab_color)) {
111       // If active tab is light color then continue lightening it until enough
112       // contrast with dark text is reached.
113       active_tab_text_color =
114           color_utils::GetColorWithMaxContrast(active_tab_color);
115       active_tab_color = LightenUntilContrast(
116           active_tab_color, active_tab_text_color,
117           kAutogeneratedThemeTextPreferredContrast, kNoWhiteContrast);
118       break;
119     }
120 
121     // If the active tab color is dark and has enough contrast with white text.
122     // Then we are all set.
123     if (color_utils::GetContrastRatio(active_tab_color, SK_ColorWHITE) >=
124         kAutogeneratedThemeTextPreferredContrast)
125       break;
126 
127     // If the active tab color is a dark color but the contrast with white is
128     // not enough then we should darken the active tab color to reach the
129     // contrast with white. But to keep the contrast with the frame we should
130     // also darken the frame color. Therefore, just darken the frame color and
131     // try again.
132     frame_color = DarkenColor(frame_color, kDarkenStep);
133   }
134   return {frame_color, frame_text_color, active_tab_color,
135           active_tab_text_color};
136 }
137