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 #ifndef UI_GFX_COLOR_ANALYSIS_H_
6 #define UI_GFX_COLOR_ANALYSIS_H_
7 
8 #include <stdint.h>
9 
10 #include "base/callback_forward.h"
11 #include "base/compiler_specific.h"
12 #include "base/memory/ref_counted.h"
13 #include "base/memory/ref_counted_memory.h"
14 #include "third_party/skia/include/core/SkColor.h"
15 #include "ui/gfx/geometry/matrix3_f.h"
16 #include "ui/gfx/gfx_export.h"
17 
18 class SkBitmap;
19 
20 namespace gfx {
21 class Rect;
22 }  // namespace gfx
23 
24 namespace color_utils {
25 
26 struct HSL;
27 
28 // This class exposes the sampling method to the caller, which allows
29 // stubbing out for things like unit tests. Might be useful to pass more
30 // arguments into the GetSample method in the future (such as which
31 // cluster is being worked on, etc.).
32 //
33 // Note: Samplers should be deterministic, as the same image may be analyzed
34 // twice with two sampler instances and the results displayed side-by-side
35 // to the user.
36 class GFX_EXPORT KMeanImageSampler {
37  public:
38   virtual int GetSample(int width, int height) = 0;
39 
40  protected:
41   KMeanImageSampler();
42   virtual ~KMeanImageSampler();
43 };
44 
45 // This sampler will pick pixels from an evenly spaced grid.
46 class GFX_EXPORT GridSampler : public KMeanImageSampler {
47   public:
48    GridSampler();
49    ~GridSampler() override;
50 
51    int GetSample(int width, int height) override;
52 
53   private:
54    // The number of times GetSample has been called.
55    int calls_;
56 };
57 
58 // Returns the color in an ARGB |image| that is closest in RGB-space to the
59 // provided |color|. Exported for testing.
60 GFX_EXPORT SkColor FindClosestColor(const uint8_t* image, int width, int height,
61                                     SkColor color);
62 
63 // Returns an SkColor that represents the calculated dominant color in the
64 // image. This uses a KMean clustering algorithm to find clusters of pixel
65 // colors in RGB space.
66 // |png|/|bitmap| represents the data of a png/bitmap encoded image.
67 // |lower_bound| represents the minimum bound of HSL values to allow.
68 // |upper_bound| represents the maximum bound of HSL values to allow.
69 // See color_utils::IsWithinHSLRange() for description of these bounds.
70 //
71 // RGB KMean Algorithm (N clusters, M iterations):
72 // 1.Pick N starting colors by randomly sampling the pixels. If you see a
73 //   color you already saw keep sampling. After a certain number of tries
74 //   just remove the cluster and continue with N = N-1 clusters (for an image
75 //   with just one color this should devolve to N=1). These colors are the
76 //   centers of your N clusters.
77 // 2.For each pixel in the image find the cluster that it is closest to in RGB
78 //   space. Add that pixel's color to that cluster (we keep a sum and a count
79 //   of all of the pixels added to the space, so just add it to the sum and
80 //   increment count).
81 // 3.Calculate the new cluster centroids by getting the average color of all of
82 //   the pixels in each cluster (dividing the sum by the count).
83 // 4.See if the new centroids are the same as the old centroids.
84 //     a) If this is the case for all N clusters than we have converged and
85 //        can move on.
86 //     b) If any centroid moved, repeat step 2 with the new centroids for up
87 //        to M iterations.
88 // 5.Once the clusters have converged or M iterations have been tried, sort
89 //   the clusters by weight (where weight is the number of pixels that make up
90 //   this cluster).
91 // 6.Going through the sorted list of clusters, pick the first cluster with the
92 //   largest weight that's centroid falls between |lower_bound| and
93 //   |upper_bound|. Return that color.
94 //   If no color fulfills that requirement return the color with the largest
95 //   weight regardless of whether or not it fulfills the equation above.
96 GFX_EXPORT SkColor
97     CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png,
98                              const HSL& lower_bound,
99                              const HSL& upper_bound,
100                              KMeanImageSampler* sampler);
101 // Computes a dominant color using the above algorithm and reasonable defaults
102 // for |lower_bound|, |upper_bound| and |sampler|.
103 GFX_EXPORT SkColor CalculateKMeanColorOfPNG(
104     scoped_refptr<base::RefCountedMemory> png);
105 
106 // Computes a dominant color for the first |height| rows of |bitmap| using the
107 // above algorithm and a reasonable default sampler. If |find_closest| is true,
108 // the returned color will be the closest color to the true K-mean color that
109 // actually appears in the image; if false, the true color is returned
110 // regardless of whether it actually appears.
111 GFX_EXPORT SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap,
112                                                int height,
113                                                const HSL& lower_bound,
114                                                const HSL& upper_bound,
115                                                bool find_closest);
116 
117 // Computes a dominant color using the above algorithm and reasonable defaults
118 // for |lower_bound|, |upper_bound| and |sampler|.
119 GFX_EXPORT SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap);
120 
121 // These enums specify general values to look for when calculating prominent
122 // colors from an image. For example, a "light vibrant" prominent color would
123 // tend to be brighter and more saturated. The best combination of color
124 // attributes depends on how you plan to apply the color.
125 enum class LumaRange {
126   ANY,
127   LIGHT,
128   NORMAL,
129   DARK,
130 };
131 
132 enum class SaturationRange {
133   ANY,
134   VIBRANT,
135   MUTED,
136 };
137 
138 struct ColorProfile {
139   ColorProfile() = default;
ColorProfileColorProfile140   ColorProfile(LumaRange l, SaturationRange s) : luma(l), saturation(s) {}
141 
142   LumaRange luma = LumaRange::DARK;
143   SaturationRange saturation = SaturationRange::MUTED;
144 };
145 
146 // A color value with an associated weight.
147 struct Swatch {
SwatchSwatch148   Swatch() : Swatch(SK_ColorTRANSPARENT, 0) {}
149 
SwatchSwatch150   Swatch(SkColor color, size_t population)
151       : color(color), population(population) {}
152 
153   SkColor color;
154 
155   // The population correlates to a count, so it should be 1 or greater.
156   size_t population;
157 
158   bool operator==(const Swatch& other) const {
159     return color == other.color && population == other.population;
160   }
161 };
162 
163 // Used to filter colors from swatches. Called with the candidate color and will
164 // return true if the color should be allowed.
165 using ColorSwatchFilter = base::RepeatingCallback<bool(const SkColor&)>;
166 
167 // The maximum number of pixels to consider when generating swatches.
168 GFX_EXPORT extern const int kMaxConsideredPixelsForSwatches;
169 
170 // Returns a vector of |Swatch| that represent the prominent colors of the
171 // bitmap within |region|. The |max_swatches| is the maximum number of swatches.
172 // For landscapes, good values are in the range 12-16. For images which are
173 // largely made up of people's faces then this value should be increased to
174 // 24-32. |filter| is an optional filter that can filter out unwanted colors.
175 // This is an implementation of the Android Palette API:
176 // https://developer.android.com/reference/android/support/v7/graphics/Palette
177 GFX_EXPORT std::vector<Swatch> CalculateColorSwatches(
178     const SkBitmap& bitmap,
179     size_t max_swatches,
180     const gfx::Rect& region,
181     base::Optional<ColorSwatchFilter> filter);
182 
183 // Returns a vector of RGB colors that represents the bitmap based on the
184 // |color_profiles| provided. For each value, if a value is succesfully
185 // calculated, the calculated value is fully opaque. For failure, the calculated
186 // value is transparent. |region| can be provided to select a specific area of
187 // the bitmap. |filter| is an optional filter that can filter out unwanted
188 // colors. If |filter| is not provided then we will filter out uninteresting
189 // colors.
190 GFX_EXPORT std::vector<Swatch> CalculateProminentColorsOfBitmap(
191     const SkBitmap& bitmap,
192     const std::vector<ColorProfile>& color_profiles,
193     gfx::Rect* region,
194     ColorSwatchFilter filter);
195 
196 // Compute color covariance matrix for the input bitmap.
197 GFX_EXPORT gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap);
198 
199 // Apply a color reduction transform defined by |color_transform| vector to
200 // |source_bitmap|. The result is put into |target_bitmap|, which is expected
201 // to be initialized to the required size and type (SkBitmap::kA8_Config).
202 // If |fit_to_range|, result is transfored linearly to fit 0-0xFF range.
203 // Otherwise, data is clipped.
204 // Returns true if the target has been computed.
205 GFX_EXPORT bool ApplyColorReduction(const SkBitmap& source_bitmap,
206                                    const gfx::Vector3dF& color_transform,
207                                    bool fit_to_range,
208                                    SkBitmap* target_bitmap);
209 
210 }  // namespace color_utils
211 
212 #endif  // UI_GFX_COLOR_ANALYSIS_H_
213