// 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