Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/gfx/color_analysis.h" | 5 #include "ui/gfx/color_analysis.h" |
| 6 | 6 |
| 7 #include <limits.h> | 7 #include <limits.h> |
| 8 #include <stdint.h> | 8 #include <stdint.h> |
| 9 | 9 |
| 10 #include <algorithm> | 10 #include <algorithm> |
| 11 #include <cmath> | |
| 11 #include <limits> | 12 #include <limits> |
| 13 #include <map> | |
| 12 #include <memory> | 14 #include <memory> |
| 15 #include <queue> | |
| 13 #include <vector> | 16 #include <vector> |
| 14 | 17 |
| 15 #include "base/logging.h" | 18 #include "base/logging.h" |
| 16 #include "third_party/skia/include/core/SkBitmap.h" | 19 #include "third_party/skia/include/core/SkBitmap.h" |
| 17 #include "third_party/skia/include/core/SkUnPreMultiply.h" | 20 #include "third_party/skia/include/core/SkUnPreMultiply.h" |
| 18 #include "ui/gfx/codec/png_codec.h" | 21 #include "ui/gfx/codec/png_codec.h" |
| 22 #include "ui/gfx/color_palette.h" | |
| 19 #include "ui/gfx/color_utils.h" | 23 #include "ui/gfx/color_utils.h" |
| 24 #include "ui/gfx/range/range.h" | |
| 20 | 25 |
| 21 namespace color_utils { | 26 namespace color_utils { |
| 22 namespace { | 27 namespace { |
| 23 | 28 |
| 24 // RGBA KMean Constants | 29 // RGBA KMean Constants |
| 25 const uint32_t kNumberOfClusters = 4; | 30 const uint32_t kNumberOfClusters = 4; |
| 26 const int kNumberOfIterations = 50; | 31 const int kNumberOfIterations = 50; |
| 27 | 32 |
| 28 const HSL kDefaultLowerHSLBound = {-1, -1, 0.15}; | 33 const HSL kDefaultLowerHSLBound = {-1, -1, 0.15}; |
| 29 const HSL kDefaultUpperHSLBound = {-1, -1, 0.85}; | 34 const HSL kDefaultUpperHSLBound = {-1, -1, 0.85}; |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 136 // approximately 10 microseconds for a 16x16 icon on an Intel Core i5. | 141 // approximately 10 microseconds for a 16x16 icon on an Intel Core i5. |
| 137 void UnPreMultiply(const SkBitmap& bitmap, uint32_t* buffer, int buffer_size) { | 142 void UnPreMultiply(const SkBitmap& bitmap, uint32_t* buffer, int buffer_size) { |
| 138 SkAutoLockPixels auto_lock(bitmap); | 143 SkAutoLockPixels auto_lock(bitmap); |
| 139 uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels()); | 144 uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels()); |
| 140 uint32_t* out = buffer; | 145 uint32_t* out = buffer; |
| 141 int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size); | 146 int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size); |
| 142 for (int i = 0; i < pixel_count; ++i) | 147 for (int i = 0; i < pixel_count; ++i) |
| 143 *out++ = SkUnPreMultiply::PMColorToColor(*in++); | 148 *out++ = SkUnPreMultiply::PMColorToColor(*in++); |
| 144 } | 149 } |
| 145 | 150 |
| 151 // Prominent color utilities --------------------------------------------------- | |
|
Nico
2017/02/17 21:37:05
kind of looks long enough that it could go into it
Evan Stade
2017/02/17 23:23:18
I like having all the moving parts to this algorit
| |
| 152 | |
| 153 // A color value with an associated weight (equivalent to the popularity of that | |
| 154 // color). | |
| 155 struct WeightedColor { | |
| 156 SkColor color; | |
| 157 int weight; | |
|
sky
2017/02/17 21:42:19
Initialize color and weight?
sky
2017/02/17 21:42:19
Document what the range for this is. Always positi
Evan Stade
2017/02/17 23:23:18
done
Evan Stade
2017/02/17 23:23:18
changed it to use an explicit constructor
| |
| 158 }; | |
| 159 | |
| 160 // A |ColorBox| represents a region in a color space (an ordered set of colors). | |
|
sky
2017/02/17 21:42:19
Did you consider the name OrderedColorSet? IMO tha
Evan Stade
2017/02/17 23:23:18
no, I didn't consider that name. I drew the names
sky
2017/02/18 00:01:34
I actually think 'box' is misleading in this conte
| |
| 161 // It is a range in the ordered set, with a low index and a high index. The | |
| 162 // diversity (volume) of the box is computed by looking at the range of color | |
| 163 // values it spans, where r, g, and b components are considered separately. | |
| 164 class ColorBox { | |
| 165 public: | |
| 166 explicit ColorBox(std::vector<SkColor>* color_space) | |
| 167 : ColorBox(color_space, gfx::Range(0, color_space->size() - 1)) {} | |
|
sky
2017/02/17 21:42:20
DCHECK color_space !empty. That said, CanSplit() s
Evan Stade
2017/02/17 23:23:18
An empty color_space would trip the second DCHECK
| |
| 168 ColorBox(const ColorBox& other) = default; | |
| 169 ColorBox& operator=(const ColorBox& other) = default; | |
| 170 ~ColorBox() {} | |
| 171 | |
| 172 // Can't split if there's only one color in the box. | |
| 173 bool CanSplit() const { return !color_range_.is_empty(); } | |
| 174 | |
| 175 // Splits |this| in two and returns the other half. | |
| 176 ColorBox Split() { | |
|
sky
2017/02/17 21:42:19
This also sorts. Maybe SortAndSplit?
Evan Stade
2017/02/17 23:26:26
oops, missed this one. It sorts in an unpredictabl
| |
| 177 // Calculate which component has the largest range... | |
| 178 int r_dimension = max_r_ - min_r_; | |
|
sky
2017/02/17 21:42:19
As you have min/max as uint8_t, why did you use in
Evan Stade
2017/02/17 23:23:18
updated these all to uint8_t. int8_t could overflo
| |
| 179 int g_dimension = max_g_ - min_g_; | |
| 180 int b_dimension = max_b_ - min_b_; | |
| 181 int long_dimension = std::max({r_dimension, g_dimension, b_dimension}); | |
| 182 enum ColorChannel { | |
| 183 RED, | |
| 184 GREEN, | |
| 185 BLUE, | |
| 186 }; | |
|
Nico
2017/02/17 21:37:05
optional nit: Since you don't use the name, I'd do
Evan Stade
2017/02/17 23:23:18
I like it, done.
| |
| 187 ColorChannel channel = long_dimension == r_dimension | |
| 188 ? RED | |
| 189 : long_dimension == g_dimension ? GREEN : BLUE; | |
| 190 | |
| 191 // ... and sort along that axis. | |
| 192 auto sort_function = [channel](SkColor a, SkColor b) { | |
| 193 switch (channel) { | |
| 194 case RED: | |
| 195 return SkColorGetR(a) < SkColorGetR(b); | |
| 196 case GREEN: | |
| 197 return SkColorGetG(a) < SkColorGetG(b); | |
| 198 case BLUE: | |
| 199 return SkColorGetB(a) < SkColorGetB(b); | |
| 200 } | |
| 201 NOTREACHED(); | |
| 202 return SkColorGetB(a) < SkColorGetB(b); | |
| 203 }; | |
| 204 // Just the portion of |color_space_| that's covered by this box should be | |
| 205 // sorted. | |
| 206 std::sort(color_space_->begin() + color_range_.start(), | |
| 207 color_space_->begin() + color_range_.end(), sort_function); | |
| 208 | |
| 209 // Split at the first color value that's not less than the median. | |
|
Nico
2017/02/17 21:37:06
You mean "mean" not "median" here, right? Else you
Evan Stade
2017/02/17 23:23:18
hmm, I copied this terminology without thinking mu
| |
| 210 uint32_t split_point = color_range_.end(); | |
| 211 for (uint32_t i = color_range_.start() + 1; i < color_range_.end(); ++i) { | |
| 212 bool past_median = false; | |
| 213 switch (channel) { | |
| 214 case RED: | |
| 215 past_median = | |
| 216 SkColorGetR(color_space_->at(i)) > (min_r_ + max_r_) / 2; | |
|
sky
2017/02/17 21:42:19
optional: at -> (*color_space_)[i]
Evan Stade
2017/02/17 23:23:18
Done.
| |
| 217 break; | |
| 218 case GREEN: | |
| 219 past_median = | |
| 220 SkColorGetG(color_space_->at(i)) > (min_g_ + max_g_) / 2; | |
| 221 break; | |
| 222 case BLUE: | |
| 223 past_median = | |
| 224 SkColorGetB(color_space_->at(i)) > (min_b_ + max_b_) / 2; | |
| 225 break; | |
| 226 } | |
| 227 if (past_median) { | |
| 228 split_point = i; | |
| 229 break; | |
| 230 } | |
| 231 } | |
| 232 | |
| 233 // Break off half and return it. | |
| 234 gfx::Range other_range = color_range_; | |
|
sky
2017/02/17 21:42:19
Does this do the right thing with 1 color? If ther
Evan Stade
2017/02/17 23:23:18
it's an error to call this function in that case (
| |
| 235 other_range.set_end(split_point - 1); | |
| 236 ColorBox other_box(color_space_, other_range); | |
| 237 | |
| 238 // Keep the other half in |this| and recalculate our color bounds. | |
| 239 color_range_.set_start(split_point); | |
| 240 RecomputeBounds(); | |
| 241 return other_box; | |
| 242 } | |
| 243 | |
| 244 // Returns the average color of this box, weighted by its popularity in | |
| 245 // |color_counts|. | |
| 246 WeightedColor GetWeightedAverageColor( | |
| 247 const std::map<SkColor, int>& color_counts) const { | |
| 248 int sum_r = 0; | |
|
Nico
2017/02/17 21:37:05
For a 4096x4096 all-red image I think this will ov
Evan Stade
2017/02/17 23:23:18
that's bad. I've changed this to uint64_t.
I do e
| |
| 249 int sum_g = 0; | |
| 250 int sum_b = 0; | |
| 251 int total_count_in_box = 0; | |
| 252 | |
| 253 for (uint32_t i = color_range_.start(); i <= color_range_.end(); ++i) { | |
| 254 const SkColor color = color_space_->at(i); | |
|
sky
2017/02/17 21:42:19
If you're going to use const (which I like) use it
Evan Stade
2017/02/17 23:23:18
Done.
| |
| 255 auto color_count_iter = color_counts.find(color); | |
| 256 DCHECK(color_count_iter != color_counts.end()); | |
| 257 const int color_count = color_count_iter->second; | |
| 258 | |
| 259 total_count_in_box += color_count; | |
| 260 sum_r += color_count * SkColorGetR(color); | |
| 261 sum_g += color_count * SkColorGetG(color); | |
| 262 sum_b += color_count * SkColorGetB(color); | |
| 263 } | |
| 264 | |
| 265 WeightedColor weighted_color; | |
| 266 weighted_color.weight = total_count_in_box; | |
| 267 weighted_color.color = SkColorSetRGB( | |
| 268 std::round(static_cast<float>(sum_r) / total_count_in_box), | |
| 269 std::round(static_cast<float>(sum_g) / total_count_in_box), | |
| 270 std::round(static_cast<float>(sum_b) / total_count_in_box)); | |
| 271 return weighted_color; | |
| 272 } | |
| 273 | |
| 274 // Comparisons are done by volume. | |
|
sky
2017/02/17 21:42:19
Using operator < to mean compare by volume is obsc
Evan Stade
2017/02/17 23:23:18
done. (If there's a more elegant way to do this, p
| |
| 275 bool operator<(const ColorBox& other) const { | |
| 276 return volume_ < other.volume_; | |
| 277 } | |
| 278 | |
| 279 private: | |
| 280 ColorBox(std::vector<SkColor>* color_space, const gfx::Range& color_range) | |
| 281 : color_space_(color_space), color_range_(color_range) { | |
| 282 RecomputeBounds(); | |
| 283 } | |
| 284 | |
| 285 void RecomputeBounds() { | |
|
sky
2017/02/17 21:42:19
optional: There are no bounds in here, and the nam
Evan Stade
2017/02/17 23:23:18
min_* and max_* describe upper and lower bounds fo
| |
| 286 DCHECK(!color_range_.is_reversed()); | |
| 287 DCHECK_LT(color_range_.end(), color_space_->size()); | |
| 288 | |
| 289 min_r_ = 0xFF; | |
| 290 min_g_ = 0xFF; | |
| 291 min_b_ = 0xFF; | |
| 292 max_r_ = 0; | |
| 293 max_g_ = 0; | |
| 294 max_b_ = 0; | |
| 295 | |
| 296 for (uint32_t i = color_range_.start(); i < color_range_.end(); ++i) { | |
| 297 SkColor color = color_space_->at(i); | |
| 298 min_r_ = std::min<uint8_t>(SkColorGetR(color), min_r_); | |
| 299 min_g_ = std::min<uint8_t>(SkColorGetG(color), min_g_); | |
| 300 min_b_ = std::min<uint8_t>(SkColorGetB(color), min_b_); | |
| 301 max_r_ = std::max<uint8_t>(SkColorGetR(color), max_r_); | |
| 302 max_g_ = std::max<uint8_t>(SkColorGetG(color), max_g_); | |
| 303 max_b_ = std::max<uint8_t>(SkColorGetB(color), max_b_); | |
| 304 } | |
| 305 | |
| 306 volume_ = | |
| 307 (max_r_ - min_r_ + 1) * (max_g_ - min_g_ + 1) * (max_b_ - min_b_ + 1); | |
| 308 } | |
| 309 | |
| 310 // The set of colors of which this box captures a subset. This vector is not | |
| 311 // owned but may be modified during the split operation. | |
| 312 std::vector<SkColor>* color_space_; | |
| 313 | |
| 314 // The range of indexes into |color_space_| that are part of this box. | |
|
sky
2017/02/17 21:42:19
Why did you make this inclusive vs exclusive? Incl
Evan Stade
2017/02/17 23:23:18
changed. In fact I think there was a bug in the st
| |
| 315 gfx::Range color_range_; | |
| 316 | |
| 317 // Cached min and max color component values for the colors in this box. | |
| 318 uint8_t min_r_ = 0; | |
| 319 uint8_t min_g_ = 0; | |
| 320 uint8_t min_b_ = 0; | |
| 321 uint8_t max_r_ = 0; | |
| 322 uint8_t max_g_ = 0; | |
| 323 uint8_t max_b_ = 0; | |
| 324 | |
| 325 // Cached volume value, which is the product of the range of each color | |
| 326 // component. | |
| 327 int volume_ = 0; | |
| 328 }; | |
| 329 | |
| 330 // Some color values should be ignored for the purposes of determining prominent | |
| 331 // colors. | |
| 332 bool IsInterestingColor(SkColor color) { | |
| 333 float average_channel_value = | |
| 334 (SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3.0f; | |
| 335 // If a color is too close to white or black, ignore it. | |
| 336 if (average_channel_value >= 237 || average_channel_value <= 22) | |
| 337 return false; | |
| 338 | |
| 339 // Also rule out skin tones. | |
| 340 HSL hsl; | |
| 341 SkColorToHSL(color, &hsl); | |
| 342 return !(hsl.h >= 0.028f && hsl.h <= 0.10f && hsl.s <= 0.82f); | |
| 343 } | |
| 344 | |
| 345 // This algorithm is a port of Android's Palette API. Compare to package | |
| 346 // android.support.v7.graphics and see that code for additional high-level | |
| 347 // explanation of this algorithm. There are some minor differences: | |
| 348 // * This code doesn't exclude the same color from being used for | |
| 349 // different color profiles. | |
| 350 // * This code doesn't try to heuristically derive missing colors from | |
| 351 // existing colors. | |
| 352 SkColor CalculateProminentColorOfBuffer(uint8_t* decoded_data, | |
| 353 int img_width, | |
| 354 int img_height, | |
| 355 const HSL& lower_bound, | |
| 356 const HSL& upper_bound, | |
| 357 const HSL& goal) { | |
| 358 DCHECK_GT(img_width, 0); | |
| 359 DCHECK_GT(img_height, 0); | |
| 360 std::map<SkColor, int> color_counts; | |
| 361 | |
| 362 // First extract all colors. | |
| 363 for (int i = 0; i < img_width * img_height; ++i) { | |
| 364 // TODO(port): This code assumes the CPU architecture is little-endian. | |
|
sky
2017/02/17 21:42:19
Can you DCHECK on the image type? That it's 32bit?
Evan Stade
2017/02/17 23:23:18
I was kind of just blindly copying this bit. I've
| |
| 365 uint8_t b = decoded_data[i * 4]; | |
| 366 uint8_t g = decoded_data[i * 4 + 1]; | |
| 367 uint8_t r = decoded_data[i * 4 + 2]; | |
| 368 uint8_t a = decoded_data[i * 4 + 3]; | |
| 369 if (a == SK_AlphaTRANSPARENT) | |
| 370 continue; | |
| 371 | |
| 372 SkColor pixel = SkColorSetRGB(r, g, b); | |
| 373 color_counts[pixel]++; | |
| 374 } | |
| 375 | |
| 376 // Now throw out some uninteresting colors. | |
| 377 std::vector<SkColor> interesting_colors; | |
| 378 for (auto color_count : color_counts) { | |
| 379 SkColor color = color_count.first; | |
| 380 if (IsInterestingColor(color)) | |
| 381 interesting_colors.push_back(color); | |
| 382 } | |
| 383 | |
| 384 if (interesting_colors.empty()) | |
|
sky
2017/02/17 21:42:19
If there is only one color, is it the prominent co
Evan Stade
2017/02/17 23:23:18
it could be, but only if it's "interesting" and wi
| |
| 385 return SK_ColorTRANSPARENT; | |
| 386 | |
| 387 // Group the colors into "boxes" and repeatedly split the most voluminous box. | |
| 388 // We stop the process when a box can no longer be split (there's only one | |
| 389 // color in it) or when the number of color boxes reaches 12. As per the | |
| 390 // Android docs, | |
| 391 // | |
| 392 // For landscapes, good values are in the range 12-16. For images which | |
| 393 // are largely made up of people's faces then this value should be increased | |
| 394 // to 24-32. | |
| 395 const int kMaxColors = 12; | |
| 396 // Boxes are sorted by volume with the most voluminous at the front of the PQ. | |
| 397 std::priority_queue<ColorBox> boxes; | |
| 398 boxes.emplace(&interesting_colors); | |
| 399 while (boxes.size() < kMaxColors) { | |
| 400 auto box = boxes.top(); | |
| 401 if (!box.CanSplit()) | |
| 402 break; | |
| 403 boxes.pop(); | |
| 404 boxes.push(box.Split()); | |
| 405 boxes.push(box); | |
| 406 } | |
| 407 | |
| 408 // Now extract a single color to represent each box. This is the average color | |
| 409 // in the box, weighted by the frequency of that color in the source image. | |
| 410 std::vector<WeightedColor> box_colors; | |
| 411 int max_weight = 0; | |
| 412 while (!boxes.empty()) { | |
| 413 box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts)); | |
| 414 boxes.pop(); | |
| 415 max_weight = std::max(max_weight, box_colors.back().weight); | |
| 416 } | |
| 417 | |
| 418 // Given these box average colors, find the best one for the desired color | |
| 419 // profile. "Best" in this case means the color which fits in the provided | |
| 420 // bounds and comes closest to |goal|. It's possible that no color will fit in | |
| 421 // the provided bounds, in which case we'll return an empty color. | |
| 422 double best_suitability = 0; | |
| 423 SkColor best_color = SK_ColorTRANSPARENT; | |
| 424 for (const auto& box_color : box_colors) { | |
| 425 HSL hsl; | |
| 426 SkColorToHSL(box_color.color, &hsl); | |
| 427 if (!IsWithinHSLRange(hsl, lower_bound, upper_bound)) | |
| 428 continue; | |
| 429 | |
| 430 double suitability = | |
| 431 (1 - std::abs(hsl.s - goal.s)) * 3 + | |
| 432 (1 - std::abs(hsl.l - goal.l)) * 6.5 + | |
| 433 (box_color.weight / static_cast<float>(max_weight)) * 0.5; | |
| 434 if (suitability > best_suitability) { | |
| 435 best_suitability = suitability; | |
| 436 best_color = box_color.color; | |
| 437 } | |
| 438 } | |
| 439 | |
| 440 return best_color; | |
| 441 } | |
| 442 | |
| 146 } // namespace | 443 } // namespace |
| 147 | 444 |
| 148 KMeanImageSampler::KMeanImageSampler() { | 445 KMeanImageSampler::KMeanImageSampler() { |
| 149 } | 446 } |
| 150 | 447 |
| 151 KMeanImageSampler::~KMeanImageSampler() { | 448 KMeanImageSampler::~KMeanImageSampler() { |
| 152 } | 449 } |
| 153 | 450 |
| 154 GridSampler::GridSampler() : calls_(0) { | 451 GridSampler::GridSampler() : calls_(0) { |
| 155 } | 452 } |
| (...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 394 const HSL& upper_bound, | 691 const HSL& upper_bound, |
| 395 KMeanImageSampler* sampler) { | 692 KMeanImageSampler* sampler) { |
| 396 // SkBitmap uses pre-multiplied alpha but the KMean clustering function | 693 // SkBitmap uses pre-multiplied alpha but the KMean clustering function |
| 397 // above uses non-pre-multiplied alpha. Transform the bitmap before we | 694 // above uses non-pre-multiplied alpha. Transform the bitmap before we |
| 398 // analyze it because the function reads each pixel multiple times. | 695 // analyze it because the function reads each pixel multiple times. |
| 399 int pixel_count = bitmap.width() * bitmap.height(); | 696 int pixel_count = bitmap.width() * bitmap.height(); |
| 400 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); | 697 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); |
| 401 UnPreMultiply(bitmap, image.get(), pixel_count); | 698 UnPreMultiply(bitmap, image.get(), pixel_count); |
| 402 | 699 |
| 403 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), | 700 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), |
| 404 bitmap.width(), | 701 bitmap.width(), bitmap.height(), |
| 405 bitmap.height(), | 702 lower_bound, upper_bound, sampler); |
| 406 lower_bound, | |
| 407 upper_bound, | |
| 408 sampler); | |
| 409 } | 703 } |
| 410 | 704 |
| 411 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { | 705 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { |
| 412 GridSampler sampler; | 706 GridSampler sampler; |
| 413 return CalculateKMeanColorOfBitmap( | 707 return CalculateKMeanColorOfBitmap( |
| 414 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); | 708 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); |
| 415 } | 709 } |
| 416 | 710 |
| 711 SkColor CalculateProminentColorOfBitmap(const SkBitmap& bitmap, | |
| 712 LumaRange luma, | |
| 713 SaturationRange saturation) { | |
| 714 // SkBitmap uses pre-multiplied alpha but the prominent color algorithm | |
| 715 // above uses non-pre-multiplied alpha. Transform the bitmap before we | |
| 716 // analyze it because the function reads each pixel multiple times. | |
| 717 int pixel_count = bitmap.width() * bitmap.height(); | |
| 718 if (pixel_count == 0) | |
| 719 return SK_ColorTRANSPARENT; | |
| 720 | |
| 721 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); | |
|
sky
2017/02/17 21:42:19
image_data? bitmap_data?
Evan Stade
2017/02/17 23:23:18
removed
| |
| 722 UnPreMultiply(bitmap, image.get(), pixel_count); | |
| 723 | |
| 724 // The hue is not relevant to our bounds or goal colors. | |
| 725 HSL lower_bound = { | |
| 726 -1, | |
| 727 }; | |
| 728 HSL upper_bound = { | |
| 729 -1, | |
| 730 }; | |
| 731 HSL goal = { | |
| 732 -1, | |
| 733 }; | |
| 734 | |
| 735 switch (luma) { | |
| 736 case LumaRange::LIGHT: | |
| 737 lower_bound.l = 0.55f; | |
| 738 upper_bound.l = 1; | |
| 739 goal.l = 0.74f; | |
| 740 break; | |
| 741 case LumaRange::NORMAL: | |
| 742 lower_bound.l = 0.3f; | |
| 743 upper_bound.l = 0.7f; | |
| 744 goal.l = 0.5f; | |
| 745 break; | |
| 746 case LumaRange::DARK: | |
| 747 lower_bound.l = 0; | |
| 748 upper_bound.l = 0.45f; | |
| 749 goal.l = 0.26f; | |
| 750 break; | |
| 751 } | |
| 752 | |
| 753 switch (saturation) { | |
| 754 case SaturationRange::VIBRANT: | |
| 755 lower_bound.s = 0.35f; | |
| 756 upper_bound.s = 1; | |
| 757 goal.s = 1; | |
| 758 break; | |
| 759 case SaturationRange::MUTED: | |
| 760 lower_bound.s = 0; | |
| 761 upper_bound.s = 0.4f; | |
| 762 goal.s = 0.3f; | |
| 763 break; | |
| 764 } | |
| 765 | |
| 766 return CalculateProminentColorOfBuffer( | |
| 767 reinterpret_cast<uint8_t*>(image.get()), bitmap.width(), bitmap.height(), | |
| 768 lower_bound, upper_bound, goal); | |
| 769 } | |
| 770 | |
| 417 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { | 771 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { |
| 418 // First need basic stats to normalize each channel separately. | 772 // First need basic stats to normalize each channel separately. |
| 419 SkAutoLockPixels bitmap_lock(bitmap); | 773 SkAutoLockPixels bitmap_lock(bitmap); |
| 420 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); | 774 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); |
| 421 if (!bitmap.getPixels()) | 775 if (!bitmap.getPixels()) |
| 422 return covariance; | 776 return covariance; |
| 423 | 777 |
| 424 // Assume ARGB_8888 format. | 778 // Assume ARGB_8888 format. |
| 425 DCHECK(bitmap.colorType() == kN32_SkColorType); | 779 DCHECK(bitmap.colorType() == kN32_SkColorType); |
| 426 | 780 |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 576 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); | 930 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); |
| 577 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); | 931 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); |
| 578 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); | 932 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); |
| 579 gfx::Vector3dF principal = eigenvectors.get_column(0); | 933 gfx::Vector3dF principal = eigenvectors.get_column(0); |
| 580 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) | 934 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) |
| 581 return false; // This may happen for some edge cases. | 935 return false; // This may happen for some edge cases. |
| 582 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); | 936 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); |
| 583 } | 937 } |
| 584 | 938 |
| 585 } // color_utils | 939 } // color_utils |
| OLD | NEW |