chromium/ash/style/harmonized_colors.cc

// Copyright 2022 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/style/harmonized_colors.h"

#include <functional>
#include <utility>

#include "base/ranges/algorithm.h"
#include "third_party/material_color_utilities/src/cpp/palettes/tones.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/color/color_recipe.h"

namespace ash {

namespace {

using material_color_utilities::TonalPalette;

// Upper limits for the angle range.
constexpr std::array<SkColor, 4> kAngles = {49, 159, 219, 360};
constexpr std::array<SkColor, 4> kGreens = {0x5BA22FU, 0x4FA834U, 0x34A866,
                                            0x34A87AU};
constexpr std::array<SkColor, 4> kYellows = {0xEF9800U, 0xFBBC04, 0xFBC104U,
                                             0xFBA904U};
constexpr std::array<SkColor, 4> kReds = {0xF95C45U, 0xEA7135, 0xEA6235,
                                          0xEA3553};
constexpr std::array<SkColor, 4> kBlues = {0x3F5AA9, 0x00829D, 0x00829D,
                                           0x3F5AA9};

struct HarmonizedSeeds {
  SkColor red;
  SkColor green;
  SkColor yellow;
  SkColor blue;
};

std::pair<int, HarmonizedSeeds> MakeSeedEntry(int i) {
  HarmonizedSeeds seed;
  seed.red = kReds[i];
  seed.green = kGreens[i];
  seed.yellow = kYellows[i];
  seed.blue = kBlues[i];
  return std::make_pair(kAngles[i], seed);
}

const base::flat_map<int, HarmonizedSeeds> MakeMap() {
  std::vector<std::pair<int, HarmonizedSeeds>> storage;
  storage.reserve(4);
  for (int i = 0; i < 4; i++) {
    storage.push_back(MakeSeedEntry(i));
  }
  return base::flat_map<int, HarmonizedSeeds>(base::sorted_unique_t(),
                                              std::move(storage));
}

// Mappings of tones to ColorId as they comprise the tonal palette.
// https://m3.material.io/styles/color/the-color-system/key-colors-tones
constexpr std::array<std::pair<int, ui::ColorId>, 13> kRedIds{
    std::make_pair(0, cros_tokens::kCrosRefRed0),
    {10, cros_tokens::kCrosRefRed10},
    {20, cros_tokens::kCrosRefRed20},
    {30, cros_tokens::kCrosRefRed30},
    {40, cros_tokens::kCrosRefRed40},
    {50, cros_tokens::kCrosRefRed50},
    {60, cros_tokens::kCrosRefRed60},
    {70, cros_tokens::kCrosRefRed70},
    {80, cros_tokens::kCrosRefRed80},
    {90, cros_tokens::kCrosRefRed90},
    {95, cros_tokens::kCrosRefRed95},
    {99, cros_tokens::kCrosRefRed99},
    {100, cros_tokens::kCrosRefRed100}};

constexpr std::array<std::pair<int, ui::ColorId>, 13> kGreenIds{
    std::make_pair(0, cros_tokens::kCrosRefGreen0),
    {10, cros_tokens::kCrosRefGreen10},
    {20, cros_tokens::kCrosRefGreen20},
    {30, cros_tokens::kCrosRefGreen30},
    {40, cros_tokens::kCrosRefGreen40},
    {50, cros_tokens::kCrosRefGreen50},
    {60, cros_tokens::kCrosRefGreen60},
    {70, cros_tokens::kCrosRefGreen70},
    {80, cros_tokens::kCrosRefGreen80},
    {90, cros_tokens::kCrosRefGreen90},
    {95, cros_tokens::kCrosRefGreen95},
    {99, cros_tokens::kCrosRefGreen99},
    {100, cros_tokens::kCrosRefGreen100}};

constexpr std::array<std::pair<int, ui::ColorId>, 13> kYellowIds{
    std::make_pair(0, cros_tokens::kCrosRefYellow0),
    {10, cros_tokens::kCrosRefYellow10},
    {20, cros_tokens::kCrosRefYellow20},
    {30, cros_tokens::kCrosRefYellow30},
    {40, cros_tokens::kCrosRefYellow40},
    {50, cros_tokens::kCrosRefYellow50},
    {60, cros_tokens::kCrosRefYellow60},
    {70, cros_tokens::kCrosRefYellow70},
    {80, cros_tokens::kCrosRefYellow80},
    {90, cros_tokens::kCrosRefYellow90},
    {95, cros_tokens::kCrosRefYellow95},
    {99, cros_tokens::kCrosRefYellow99},
    {100, cros_tokens::kCrosRefYellow100}};

constexpr std::array<std::pair<int, ui::ColorId>, 13> kBlueIds{
    std::make_pair(0, cros_tokens::kCrosRefBlue0),
    {10, cros_tokens::kCrosRefBlue10},
    {20, cros_tokens::kCrosRefBlue20},
    {30, cros_tokens::kCrosRefBlue30},
    {40, cros_tokens::kCrosRefBlue40},
    {50, cros_tokens::kCrosRefBlue50},
    {60, cros_tokens::kCrosRefBlue60},
    {70, cros_tokens::kCrosRefBlue70},
    {80, cros_tokens::kCrosRefBlue80},
    {90, cros_tokens::kCrosRefBlue90},
    {95, cros_tokens::kCrosRefBlue95},
    {99, cros_tokens::kCrosRefBlue99},
    {100, cros_tokens::kCrosRefBlue100}};

// For the ColorId in `data`, insert the color value from `palette` for the
// associated luma in `data`.
void InsertIntoMixer(ui::ColorMixer& mixer,
                     const TonalPalette& palette,
                     const std::array<std::pair<int, ui::ColorId>, 13>& data) {
  for (const auto& entry : data) {
    // Under the hood, SkColor is in ARGB like Argb from the material color
    // utilities library.
    SkColor color = palette.get(entry.first);
    ui::ColorId color_id = entry.second;

    mixer[color_id] = {color};
  }
}

}  // namespace

// Returns the hue angle for `seed_color`.
int HueAngle(SkColor seed_color) {
  SkScalar hsv[3];
  SkColorToHSV(seed_color, hsv);

  // Hue is in degrees.
  int hue_angle = static_cast<int>(hsv[0]);
  DCHECK_LE(hue_angle, 360);
  DCHECK_GE(hue_angle, 0);

  return hue_angle;
}

void AddHarmonizedColors(ui::ColorMixer& mixer,
                         const ui::ColorProviderKey& key) {
  // Zip the arrays into a map indexed by the angle lower bound.
  static const base::flat_map<int, HarmonizedSeeds> kSeedMap = MakeMap();

  HarmonizedSeeds seeds;
  if (!key.user_color) {
    // If there's no seed color, always use the last one.
    auto last = kSeedMap.end();
    --last;
    seeds = last->second;
  } else {
    int angle = HueAngle(*key.user_color);
    DCHECK_LT(angle, 360);
    auto iter = kSeedMap.upper_bound(angle);
    DCHECK(iter != kSeedMap.end());
    seeds = iter->second;
  }

  InsertIntoMixer(mixer, TonalPalette(seeds.red), kRedIds);
  InsertIntoMixer(mixer, TonalPalette(seeds.green), kGreenIds);
  InsertIntoMixer(mixer, TonalPalette(seeds.blue), kBlueIds);
  InsertIntoMixer(mixer, TonalPalette(seeds.yellow), kYellowIds);
}

}  // namespace ash