1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141

ash / wallpaper / wallpaper_utils / wallpaper_color_calculator.cc [blame]

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/wallpaper/wallpaper_utils/wallpaper_color_calculator.h"

#include <string>
#include <utility>

#include "ash/wallpaper/wallpaper_utils/scored_sample.h"
#include "ash/wallpaper/wallpaper_utils/wallpaper_calculated_colors.h"
#include "ash/wallpaper/wallpaper_utils/wallpaper_color_extraction_result.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/task/task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chromeos/constants/chromeos_features.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"

namespace ash {

namespace {

// The largest image size, in pixels, to synchronously calculate the prominent
// color. This is a simple heuristic optimization because extraction on images
// smaller than this should run very quickly, and offloading the task to another
// thread would actually take longer.
const int kMaxPixelsForSynchronousCalculation = 100;

// Specifies the size of the resized image used to calculate the wallpaper
// colors.
constexpr int kWallpaperSizeForColorCalculation = 256;

const gfx::ImageSkia GetResizedImage(const gfx::ImageSkia& image) {
  if (std::max(image.width(), image.height()) <
      kWallpaperSizeForColorCalculation) {
    return image;
  }

  // Resize the image maintaining our aspect ratio.
  float aspect_ratio =
      static_cast<float>(image.width()) / static_cast<float>(image.height());
  int height = kWallpaperSizeForColorCalculation;
  int width = static_cast<int>(aspect_ratio * height);
  if (width > kWallpaperSizeForColorCalculation) {
    width = kWallpaperSizeForColorCalculation;
    height = static_cast<int>(width / aspect_ratio);
  }
  return gfx::ImageSkiaOperations::CreateResizedImage(
      image, skia::ImageOperations::RESIZE_GOOD, gfx::Size(width, height));
}

// Wrapper for color_utils::CalculateProminentColorsOfBitmap() and
// color_utils::CalculateKMeanColorOfBitmap that records wallpaper specific
// metrics. Note, |image| is resized to |kWallpaperSizeForColorCalculation|
// to speed up the calculation.
//
// NOTE: |image| is intentionally a copy to ensure it exists for the duration of
// the calculation.
WallpaperCalculatedColors CalculateWallpaperColor(const gfx::ImageSkia image) {
  base::TimeTicks start_time = base::TimeTicks::Now();
  gfx::ImageSkia resized_image = GetResizedImage(image);

  constexpr color_utils::HSL kNoBounds = {-1, -1, -1};
  SkColor k_mean_color = color_utils::CalculateKMeanColorOfBitmap(
      *resized_image.bitmap(), resized_image.height(), kNoBounds, kNoBounds,
      /*find_closest=*/true);

  // Compute result with with the improved clustering algorithm.
  SkColor celebi_color = ComputeWallpaperSeedColor(resized_image);

  DVLOG(2) << __func__ << " image_size=" << image.size().ToString()
           << " time=" << base::TimeTicks::Now() - start_time;

  return WallpaperCalculatedColors(k_mean_color, celebi_color);
}

bool ShouldCalculateSync(const gfx::ImageSkia& image) {
  return image.width() * image.height() <= kMaxPixelsForSynchronousCalculation;
}

}  // namespace

WallpaperColorCalculator::WallpaperColorCalculator(const gfx::ImageSkia& image)
    : image_(image) {
  // The task runner is used to compute the wallpaper colors on a thread
  // that doesn't block the UI. The user may or may not be waiting for it.
  // If we need to shutdown, we can just re-compute the value next time.
  task_runner_ = base::ThreadPool::CreateTaskRunner(
      {base::TaskPriority::USER_VISIBLE,
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
}

WallpaperColorCalculator::~WallpaperColorCalculator() = default;

bool WallpaperColorCalculator::StartCalculation(
    WallpaperColorCallback callback) {
  if (ShouldCalculateSync(image_)) {
    calculated_colors_ = CalculateWallpaperColor(image_);
    std::move(callback).Run(*calculated_colors_);
    return true;
  }

  image_.MakeThreadSafe();
  if (task_runner_->PostTaskAndReplyWithResult(
          FROM_HERE, base::BindOnce(&CalculateWallpaperColor, image_),
          base::BindOnce(&WallpaperColorCalculator::OnAsyncCalculationComplete,
                         weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(),
                         std::move(callback)))) {
    return true;
  }

  LOG(WARNING) << "PostSequencedWorkerTask failed. "
               << "Wallpaper prominent colors may not be calculated.";

  return false;
}

void WallpaperColorCalculator::SetTaskRunnerForTest(
    scoped_refptr<base::TaskRunner> task_runner) {
  task_runner_ = task_runner;
}

void WallpaperColorCalculator::OnAsyncCalculationComplete(
    base::TimeTicks async_start_time,
    WallpaperColorCallback callback,
    const WallpaperCalculatedColors& calculated_colors) {
  DVLOG(2) << __func__ << " time=" << base::TimeTicks::Now() - async_start_time;
  calculated_colors_ = calculated_colors;
  std::move(callback).Run(calculated_colors);
}

}  // namespace ash