text: // Copyright 2020 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/ui/signin/profile_colors_util.h"
  
  #include "base/containers/contains.h"
  #include "base/rand_util.h"
  #include "build/build_config.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/profiles/profile_attributes_storage.h"
  #include "chrome/browser/profiles/profile_manager.h"
  #include "chrome/browser/themes/browser_theme_pack.h"
  #include "chrome/browser/themes/custom_theme_supplier.h"
  #include "chrome/browser/themes/theme_properties.h"
  #include "chrome/common/search/generated_colors_info.h"
  #include "ui/gfx/color_utils.h"
  #include "ui/native_theme/native_theme.h"
  
  namespace {
  
  // Minimum saturation for a color to be autoselected (as picking 'colorful'
  // colors is needed to distinguish colors from each other).
  constexpr double kMinimumSaturation = 0.25;
  
  // Maximum diff in lightness of an autoselected color to the color of the
  // current profile (so that the interception UI does not look bad).
  constexpr double kMaximumLightnessDiff = 0.35;
  
  // This is the core definition of how ProfileThemeColors are obtained.
  ProfileThemeColors GetProfileThemeColorsFromHighlightColor(
      SkColor highlight_color) {
    ProfileThemeColors colors;
    colors.profile_highlight_color = highlight_color;
    colors.default_avatar_fill_color = highlight_color;
    colors.default_avatar_stroke_color =
        GetAvatarStrokeColor(colors.default_avatar_fill_color);
    return colors;
  }
  
  ProfileThemeColors GetProfileThemeColorsForAutogeneratedColor(
      SkColor autogenerated_color) {
    auto pack = base::MakeRefCounted<BrowserThemePack>(
        CustomThemeSupplier::ThemeType::AUTOGENERATED);
    BrowserThemePack::BuildFromColor(autogenerated_color, pack.get());
    return GetProfileThemeColorsForThemeSupplier(pack.get());
  }
  
  size_t GenerateRandomIndex(size_t size) {
    DCHECK_GT(size, 0u);
    return static_cast<size_t>(base::RandInt(0, size - 1));
  }
  
  std::vector<int> GetAvailableColorIndices(
      const std::set<ProfileThemeColors>& used_theme_colors,
      base::Optional<double> current_color_lightness) {
    std::vector<int> available_color_indices;
    for (size_t i = 0; i < base::size(chrome_colors::kGeneratedColorsInfo); ++i) {
      ProfileThemeColors theme_colors =
          GetProfileThemeColorsForAutogeneratedColor(
              chrome_colors::kGeneratedColorsInfo[i].color);
      if (base::Contains(used_theme_colors, theme_colors))
        continue;
  
      const SkColor highlight = theme_colors.profile_highlight_color;
      if (!IsSaturatedForAutoselection(highlight))
        continue;
      if (current_color_lightness &&
          !IsLightForAutoselection(highlight, *current_color_lightness))
        continue;
  
      available_color_indices.push_back(i);
    }
    return available_color_indices;
  }
  
  double ExtractCurrentColorLightness(ProfileAttributesEntry* current_profile) {
    ProfileThemeColors current_colors;
    if (!current_profile) {
      current_colors = GetDefaultProfileThemeColors(
          ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors());
    } else {
      current_colors = current_profile->GetProfileThemeColors();
    }
  
    color_utils::HSL hsl;
    color_utils::SkColorToHSL(current_colors.profile_highlight_color, &hsl);
    return hsl.l;
  }
  
  }  // namespace
  
  bool ProfileThemeColors::operator<(const ProfileThemeColors& other) const {
    return std::tie(this->profile_highlight_color,
                    this->default_avatar_fill_color,
                    this->default_avatar_stroke_color) <
           std::tie(other.profile_highlight_color,
                    other.default_avatar_fill_color,
                    other.default_avatar_stroke_color);
  }
  
  bool ProfileThemeColors::operator==(const ProfileThemeColors& other) const {
    return std::tie(this->profile_highlight_color,
                    this->default_avatar_fill_color,
                    this->default_avatar_stroke_color) ==
           std::tie(other.profile_highlight_color,
                    other.default_avatar_fill_color,
                    other.default_avatar_stroke_color);
  }
  
  bool ProfileThemeColors::operator!=(const ProfileThemeColors& other) const {
    return !(*this == other);
  }
  
  ProfileThemeColors GetProfileThemeColorsForThemeSupplier(
      const CustomThemeSupplier* supplier) {
    SkColor highlight_color;
    bool is_defined =
        supplier->GetColor(ThemeProperties::COLOR_FRAME_ACTIVE, &highlight_color);
    DCHECK(is_defined);
    return GetProfileThemeColorsFromHighlightColor(highlight_color);
  }
  
  ProfileThemeColors GetDefaultProfileThemeColors(bool dark_mode) {
    return GetProfileThemeColorsFromHighlightColor(
        ThemeProperties::GetDefaultColor(ThemeProperties::COLOR_FRAME_ACTIVE,
                                         /*incognito=*/false, dark_mode));
  }
  
  SkColor GetProfileForegroundTextColor(SkColor profile_highlight_color) {
    return color_utils::GetColorWithMaxContrast(profile_highlight_color);
  }
  
  SkColor GetProfileForegroundIconColor(SkColor profile_highlight_color) {
    SkColor text_color = GetProfileForegroundTextColor(profile_highlight_color);
    SkColor icon_color = color_utils::DeriveDefaultIconColor(text_color);
    return color_utils::BlendForMinContrast(icon_color, profile_highlight_color,
                                            text_color)
        .color;
  }
  
  SkColor GetAvatarStrokeColor(SkColor avatar_fill_color) {
    if (color_utils::IsDark(avatar_fill_color)) {
      return SK_ColorWHITE;
    }
  
    color_utils::HSL color_hsl;
    color_utils::SkColorToHSL(avatar_fill_color, &color_hsl);
    color_hsl.l = std::max(0., color_hsl.l - 0.5);
    return color_utils::HSLToSkColor(color_hsl, SkColorGetA(avatar_fill_color));
  }
  
  bool IsSaturatedForAutoselection(SkColor color) {
    color_utils::HSL hsl;
    color_utils::SkColorToHSL(color, &hsl);
    return hsl.s >= kMinimumSaturation;
  }
  
  bool IsLightForAutoselection(SkColor color, double reference_lightness) {
    color_utils::HSL hsl;
    color_utils::SkColorToHSL(color, &hsl);
    return std::abs(hsl.l - reference_lightness) <= kMaximumLightnessDiff;
  }
  
  chrome_colors::ColorInfo GenerateNewProfileColorWithGenerator(
      ProfileAttributesStorage& storage,
      base::OnceCallback<size_t(size_t count)> random_generator,
      ProfileAttributesEntry* current_profile) {
    // TODO(crbug.com/1108295): Return only a SkColor if the full ColorInfo is not
    // needed.
    std::set<ProfileThemeColors> used_theme_colors;
    for (ProfileAttributesEntry* entry : storage.GetAllProfilesAttributes()) {
      base::Optional<ProfileThemeColors> current_colors =
          entry->GetProfileThemeColorsIfSet();
      if (current_colors)
        used_theme_colors.insert(*current_colors);
    }
  
    double current_color_lightness =
        ExtractCurrentColorLightness(current_profile);
  
    // Collect indices of profile colors that match all the filters.
    std::vector<int> available_color_indices =
        GetAvailableColorIndices(used_theme_colors, current_color_lightness);
    // Relax the constraints until some colors become available.
    if (available_color_indices.empty()) {
      available_color_indices =
          GetAvailableColorIndices(used_theme_colors, base::nullopt);
    }
    if (available_color_indices.empty()) {
      // If needed, we could allow unsaturated colors (shades of grey) before
      // allowing a duplicate color.
      available_color_indices =
          GetAvailableColorIndices(std::set<ProfileThemeColors>(), base::nullopt);
    }
    DCHECK(!available_color_indices.empty());
  
    size_t size = available_color_indices.size();
    size_t available_index = std::move(random_generator).Run(size);
    DCHECK_LT(available_index, size);
    size_t index = available_color_indices[available_index];
    return chrome_colors::kGeneratedColorsInfo[index];
  }
  
  chrome_colors::ColorInfo GenerateNewProfileColor(
      ProfileAttributesEntry* current_profile) {
    return GenerateNewProfileColorWithGenerator(
        g_browser_process->profile_manager()->GetProfileAttributesStorage(),
        base::BindOnce(&GenerateRandomIndex), current_profile);
  }
  