1// Copyright (c) 2012 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 "ui/gfx/color_utils.h"
6
7#include <math.h>
8#if defined(OS_WIN)
9#include <windows.h>
10#endif
11
12#include <algorithm>
13
14#include "base/basictypes.h"
15#include "base/logging.h"
16#include "build/build_config.h"
17#if defined(OS_WIN)
18#include "skia/ext/skia_utils_win.h"
19#endif
20#include "third_party/skia/include/core/SkBitmap.h"
21
22namespace color_utils {
23
24
25// Helper functions -----------------------------------------------------------
26
27namespace {
28
29int calcHue(double temp1, double temp2, double hue) {
30  if (hue < 0.0)
31    ++hue;
32  else if (hue > 1.0)
33    --hue;
34
35  double result = temp1;
36  if (hue * 6.0 < 1.0)
37    result = temp1 + (temp2 - temp1) * hue * 6.0;
38  else if (hue * 2.0 < 1.0)
39    result = temp2;
40  else if (hue * 3.0 < 2.0)
41    result = temp1 + (temp2 - temp1) * (2.0 / 3.0 - hue) * 6.0;
42
43  // Scale the result from 0 - 255 and round off the value.
44  return static_cast<int>(result * 255 + .5);
45}
46
47// Next two functions' formulas from:
48// http://www.w3.org/TR/WCAG20/#relativeluminancedef
49// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
50
51double ConvertSRGB(double eight_bit_component) {
52  const double component = eight_bit_component / 255.0;
53  return (component <= 0.03928) ?
54      (component / 12.92) : pow((component + 0.055) / 1.055, 2.4);
55}
56
57SkColor LumaInvertColor(SkColor color) {
58  HSL hsl;
59  SkColorToHSL(color, &hsl);
60  hsl.l = 1.0 - hsl.l;
61  return HSLToSkColor(hsl, 255);
62}
63
64double ContrastRatio(double foreground_luminance, double background_luminance) {
65  DCHECK_GE(foreground_luminance, 0.0);
66  DCHECK_GE(background_luminance, 0.0);
67  foreground_luminance += 0.05;
68  background_luminance += 0.05;
69  return (foreground_luminance > background_luminance) ?
70      (foreground_luminance / background_luminance) :
71      (background_luminance / foreground_luminance);
72}
73
74}  // namespace
75
76
77// ----------------------------------------------------------------------------
78
79unsigned char GetLuminanceForColor(SkColor color) {
80  int luma = static_cast<int>((0.3 * SkColorGetR(color)) +
81                              (0.59 * SkColorGetG(color)) +
82                              (0.11 * SkColorGetB(color)));
83  return std::max(std::min(luma, 255), 0);
84}
85
86double RelativeLuminance(SkColor color) {
87  return (0.2126 * ConvertSRGB(SkColorGetR(color))) +
88         (0.7152 * ConvertSRGB(SkColorGetG(color))) +
89         (0.0722 * ConvertSRGB(SkColorGetB(color)));
90}
91
92void SkColorToHSL(SkColor c, HSL* hsl) {
93  double r = static_cast<double>(SkColorGetR(c)) / 255.0;
94  double g = static_cast<double>(SkColorGetG(c)) / 255.0;
95  double b = static_cast<double>(SkColorGetB(c)) / 255.0;
96  double vmax = std::max(std::max(r, g), b);
97  double vmin = std::min(std::min(r, g), b);
98  double delta = vmax - vmin;
99  hsl->l = (vmax + vmin) / 2;
100  if (SkColorGetR(c) == SkColorGetG(c) && SkColorGetR(c) == SkColorGetB(c)) {
101    hsl->h = hsl->s = 0;
102  } else {
103    double dr = (((vmax - r) / 6.0) + (delta / 2.0)) / delta;
104    double dg = (((vmax - g) / 6.0) + (delta / 2.0)) / delta;
105    double db = (((vmax - b) / 6.0) + (delta / 2.0)) / delta;
106    // We need to compare for the max value because comparing vmax to r, g, or b
107    // can sometimes result in values overflowing registers.
108    if (r >= g && r >= b)
109      hsl->h = db - dg;
110    else if (g >= r && g >= b)
111      hsl->h = (1.0 / 3.0) + dr - db;
112    else  // (b >= r && b >= g)
113      hsl->h = (2.0 / 3.0) + dg - dr;
114
115    if (hsl->h < 0.0)
116      ++hsl->h;
117    else if (hsl->h > 1.0)
118      --hsl->h;
119
120    hsl->s = delta / ((hsl->l < 0.5) ? (vmax + vmin) : (2 - vmax - vmin));
121  }
122}
123
124SkColor HSLToSkColor(const HSL& hsl, SkAlpha alpha) {
125  double hue = hsl.h;
126  double saturation = hsl.s;
127  double lightness = hsl.l;
128
129  // If there's no color, we don't care about hue and can do everything based on
130  // brightness.
131  if (!saturation) {
132    uint8 light;
133
134    if (lightness < 0)
135      light = 0;
136    else if (lightness >= 1.0)
137      light = 255;
138    else
139      light = SkDoubleToFixed(lightness) >> 8;
140
141    return SkColorSetARGB(alpha, light, light, light);
142  }
143
144  double temp2 = (lightness < 0.5) ?
145      (lightness * (1.0 + saturation)) :
146      (lightness + saturation - (lightness * saturation));
147  double temp1 = 2.0 * lightness - temp2;
148  return SkColorSetARGB(alpha,
149      calcHue(temp1, temp2, hue + 1.0 / 3.0),
150      calcHue(temp1, temp2, hue),
151      calcHue(temp1, temp2, hue - 1.0 / 3.0));
152}
153
154bool IsWithinHSLRange(const HSL& hsl,
155                      const HSL& lower_bound,
156                      const HSL& upper_bound) {
157  DCHECK(hsl.h >= 0 && hsl.h <= 1) << hsl.h;
158  DCHECK(hsl.s >= 0 && hsl.s <= 1) << hsl.s;
159  DCHECK(hsl.l >= 0 && hsl.l <= 1) << hsl.l;
160  DCHECK(lower_bound.h < 0 || upper_bound.h < 0 ||
161         (lower_bound.h <= 1 && upper_bound.h <= lower_bound.h + 1))
162      << "lower_bound.h: " << lower_bound.h
163      << ", upper_bound.h: " << upper_bound.h;
164  DCHECK(lower_bound.s < 0 || upper_bound.s < 0 ||
165         (lower_bound.s <= upper_bound.s && upper_bound.s <= 1))
166      << "lower_bound.s: " << lower_bound.s
167      << ", upper_bound.s: " << upper_bound.s;
168  DCHECK(lower_bound.l < 0 || upper_bound.l < 0 ||
169         (lower_bound.l <= upper_bound.l && upper_bound.l <= 1))
170      << "lower_bound.l: " << lower_bound.l
171      << ", upper_bound.l: " << upper_bound.l;
172
173  // If the upper hue is >1, the given hue bounds wrap around at 1.
174  bool matches_hue = upper_bound.h > 1
175                         ? hsl.h >= lower_bound.h || hsl.h <= upper_bound.h - 1
176                         : hsl.h >= lower_bound.h && hsl.h <= upper_bound.h;
177  return (upper_bound.h < 0 || lower_bound.h < 0 || matches_hue) &&
178         (upper_bound.s < 0 || lower_bound.s < 0 ||
179          (hsl.s >= lower_bound.s && hsl.s <= upper_bound.s)) &&
180         (upper_bound.l < 0 || lower_bound.l < 0 ||
181          (hsl.l >= lower_bound.l && hsl.l <= upper_bound.l));
182}
183
184SkColor HSLShift(SkColor color, const HSL& shift) {
185  HSL hsl;
186  int alpha = SkColorGetA(color);
187  SkColorToHSL(color, &hsl);
188
189  // Replace the hue with the tint's hue.
190  if (shift.h >= 0)
191    hsl.h = shift.h;
192
193  // Change the saturation.
194  if (shift.s >= 0) {
195    if (shift.s <= 0.5)
196      hsl.s *= shift.s * 2.0;
197    else
198      hsl.s += (1.0 - hsl.s) * ((shift.s - 0.5) * 2.0);
199  }
200
201  SkColor result = HSLToSkColor(hsl, alpha);
202
203  if (shift.l < 0)
204    return result;
205
206  // Lightness shifts in the style of popular image editors aren't actually
207  // represented in HSL - the L value does have some effect on saturation.
208  double r = static_cast<double>(SkColorGetR(result));
209  double g = static_cast<double>(SkColorGetG(result));
210  double b = static_cast<double>(SkColorGetB(result));
211  if (shift.l <= 0.5) {
212    r *= (shift.l * 2.0);
213    g *= (shift.l * 2.0);
214    b *= (shift.l * 2.0);
215  } else {
216    r += (255.0 - r) * ((shift.l - 0.5) * 2.0);
217    g += (255.0 - g) * ((shift.l - 0.5) * 2.0);
218    b += (255.0 - b) * ((shift.l - 0.5) * 2.0);
219  }
220  return SkColorSetARGB(alpha,
221                        static_cast<int>(r),
222                        static_cast<int>(g),
223                        static_cast<int>(b));
224}
225
226void BuildLumaHistogram(const SkBitmap& bitmap, int histogram[256]) {
227  DCHECK_EQ(kN32_SkColorType, bitmap.colorType());
228
229  SkAutoLockPixels bitmap_lock(bitmap);
230
231  int pixel_width = bitmap.width();
232  int pixel_height = bitmap.height();
233  for (int y = 0; y < pixel_height; ++y) {
234    for (int x = 0; x < pixel_width; ++x)
235      ++histogram[GetLuminanceForColor(bitmap.getColor(x, y))];
236  }
237}
238
239SkColor AlphaBlend(SkColor foreground, SkColor background, SkAlpha alpha) {
240  if (alpha == 0)
241    return background;
242  if (alpha == 255)
243    return foreground;
244
245  int f_alpha = SkColorGetA(foreground);
246  int b_alpha = SkColorGetA(background);
247
248  double normalizer = (f_alpha * alpha + b_alpha * (255 - alpha)) / 255.0;
249  if (normalizer == 0.0)
250    return SK_ColorTRANSPARENT;
251
252  double f_weight = f_alpha * alpha / normalizer;
253  double b_weight = b_alpha * (255 - alpha) / normalizer;
254
255  double r = (SkColorGetR(foreground) * f_weight +
256              SkColorGetR(background) * b_weight) / 255.0;
257  double g = (SkColorGetG(foreground) * f_weight +
258              SkColorGetG(background) * b_weight) / 255.0;
259  double b = (SkColorGetB(foreground) * f_weight +
260              SkColorGetB(background) * b_weight) / 255.0;
261
262  return SkColorSetARGB(static_cast<int>(normalizer),
263                        static_cast<int>(r),
264                        static_cast<int>(g),
265                        static_cast<int>(b));
266}
267
268SkColor BlendTowardOppositeLuminance(SkColor color, SkAlpha alpha) {
269  unsigned char background_luminance =
270      color_utils::GetLuminanceForColor(color);
271  const SkColor blend_color =
272      (background_luminance < 128) ? SK_ColorWHITE : SK_ColorBLACK;
273  return color_utils::AlphaBlend(blend_color, color, alpha);
274}
275
276SkColor GetReadableColor(SkColor foreground, SkColor background) {
277  const SkColor foreground2 = LumaInvertColor(foreground);
278  const double background_luminance = RelativeLuminance(background);
279  return (ContrastRatio(RelativeLuminance(foreground), background_luminance) >=
280          ContrastRatio(RelativeLuminance(foreground2), background_luminance)) ?
281      foreground : foreground2;
282}
283
284SkColor InvertColor(SkColor color) {
285  return SkColorSetARGB(
286      SkColorGetA(color),
287      255 - SkColorGetR(color),
288      255 - SkColorGetG(color),
289      255 - SkColorGetB(color));
290}
291
292SkColor GetSysSkColor(int which) {
293#if defined(OS_WIN)
294  return skia::COLORREFToSkColor(GetSysColor(which));
295#else
296  NOTIMPLEMENTED();
297  return SK_ColorLTGRAY;
298#endif
299}
300
301}  // namespace color_utils
302