Commit 920f460c authored by Jan Krcal's avatar Jan Krcal Committed by Commit Bot

[Profiles] Prefer colors with right saturation and lightness

This CL further improves the algorithm of color selection to:
 1) Disallow colors with very low saturation (currently the first line
    in the color picker).
 2) Prefer colors with lightness similar to the lightness of the current
    profile (this disallows the very dark colors for the very light
    colors and vice versa).

Bug: 1108295
Change-Id: I3adcc347b84285d5d2659f1972f075e992e0bebf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2532565
Commit-Queue: Jan Krcal <jkrcal@chromium.org>
Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Auto-Submit: Jan Krcal <jkrcal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827443}
parent c1f70566
...@@ -440,33 +440,44 @@ size_t ProfileAttributesEntry::GetAvatarIconIndex() const { ...@@ -440,33 +440,44 @@ size_t ProfileAttributesEntry::GetAvatarIconIndex() const {
return icon_index; return icon_index;
} }
ProfileThemeColors ProfileAttributesEntry::GetProfileThemeColors() const { base::Optional<ProfileThemeColors>
#if defined(OS_ANDROID) ProfileAttributesEntry::GetProfileThemeColorsIfSet() const {
// Profile theme colors shouldn't be queried on Android.
NOTREACHED();
return {SK_ColorRED, SK_ColorRED, SK_ColorRED};
#else
base::Optional<SkColor> profile_highlight_color = base::Optional<SkColor> profile_highlight_color =
GetProfileThemeColor(kProfileHighlightColorKey); GetProfileThemeColor(kProfileHighlightColorKey);
base::Optional<SkColor> default_avatar_fill_color = base::Optional<SkColor> default_avatar_fill_color =
GetProfileThemeColor(kDefaultAvatarFillColorKey); GetProfileThemeColor(kDefaultAvatarFillColorKey);
base::Optional<SkColor> default_avatar_stroke_color = base::Optional<SkColor> default_avatar_stroke_color =
GetProfileThemeColor(kDefaultAvatarStrokeColorKey); GetProfileThemeColor(kDefaultAvatarStrokeColorKey);
DCHECK_EQ(profile_highlight_color.has_value(),
default_avatar_stroke_color.has_value());
DCHECK_EQ(profile_highlight_color.has_value(),
default_avatar_fill_color.has_value());
if (!profile_highlight_color.has_value()) { if (!profile_highlight_color.has_value()) {
DCHECK(!default_avatar_fill_color.has_value() && return base::nullopt;
!default_avatar_stroke_color.has_value());
return GetDefaultProfileThemeColors(
ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors());
} }
DCHECK(default_avatar_fill_color.has_value() &&
default_avatar_stroke_color.has_value());
ProfileThemeColors colors; ProfileThemeColors colors;
colors.profile_highlight_color = profile_highlight_color.value(); colors.profile_highlight_color = profile_highlight_color.value();
colors.default_avatar_fill_color = default_avatar_fill_color.value(); colors.default_avatar_fill_color = default_avatar_fill_color.value();
colors.default_avatar_stroke_color = default_avatar_stroke_color.value(); colors.default_avatar_stroke_color = default_avatar_stroke_color.value();
return colors; return colors;
}
ProfileThemeColors ProfileAttributesEntry::GetProfileThemeColors() const {
#if defined(OS_ANDROID)
// Profile theme colors shouldn't be queried on Android.
NOTREACHED();
return {SK_ColorRED, SK_ColorRED, SK_ColorRED};
#else
base::Optional<ProfileThemeColors> theme_colors =
GetProfileThemeColorsIfSet();
if (theme_colors)
return *theme_colors;
return GetDefaultProfileThemeColors(
ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors());
#endif #endif
} }
......
...@@ -145,6 +145,9 @@ class ProfileAttributesEntry { ...@@ -145,6 +145,9 @@ class ProfileAttributesEntry {
// Returns the colors specified by the profile theme, or default colors if no // Returns the colors specified by the profile theme, or default colors if no
// theme is specified for the profile. // theme is specified for the profile.
ProfileThemeColors GetProfileThemeColors() const; ProfileThemeColors GetProfileThemeColors() const;
// Returns the colors specified by the profile theme, or empty if no theme is
// set for the profile.
base::Optional<ProfileThemeColors> GetProfileThemeColorsIfSet() const;
// Returns the metrics bucket this profile should be recorded in. // Returns the metrics bucket this profile should be recorded in.
// Note: The bucket index is assigned once and remains the same all time. 0 is // Note: The bucket index is assigned once and remains the same all time. 0 is
// reserved for the guest profile. // reserved for the guest profile.
......
...@@ -365,7 +365,11 @@ void DiceWebSigninInterceptor::OnExtendedAccountInfoUpdated( ...@@ -365,7 +365,11 @@ void DiceWebSigninInterceptor::OnExtendedAccountInfoUpdated(
return; return;
} }
SkColor profile_color = GenerateNewProfileColor().color; ProfileAttributesEntry* entry;
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_->GetPath(), &entry);
SkColor profile_color = GenerateNewProfileColor(entry).color;
Delegate::BubbleParameters bubble_parameters{ Delegate::BubbleParameters bubble_parameters{
*interception_type, info, GetPrimaryAccountInfo(identity_manager_), *interception_type, info, GetPrimaryAccountInfo(identity_manager_),
GetAutogeneratedThemeColors(profile_color).frame_color}; GetAutogeneratedThemeColors(profile_color).frame_color};
......
...@@ -18,6 +18,14 @@ ...@@ -18,6 +18,14 @@
namespace { 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. // This is the core definition of how ProfileThemeColors are obtained.
ProfileThemeColors GetProfileThemeColorsFromHighlightColor( ProfileThemeColors GetProfileThemeColorsFromHighlightColor(
SkColor highlight_color) { SkColor highlight_color) {
...@@ -42,6 +50,43 @@ size_t GenerateRandomIndex(size_t size) { ...@@ -42,6 +50,43 @@ size_t GenerateRandomIndex(size_t size) {
return static_cast<size_t>(base::RandInt(0, size - 1)); 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;
}
base::Optional<double> ExtractCurrentColorLightness(
ProfileAttributesEntry* current_profile) {
if (!current_profile)
return base::nullopt;
base::Optional<ProfileThemeColors> current_colors =
current_profile->GetProfileThemeColorsIfSet();
if (!current_colors)
return base::nullopt;
color_utils::HSL hsl;
color_utils::SkColorToHSL(current_colors->profile_highlight_color, &hsl);
return hsl.l;
}
} // namespace } // namespace
bool ProfileThemeColors::operator<(const ProfileThemeColors& other) const { bool ProfileThemeColors::operator<(const ProfileThemeColors& other) const {
...@@ -104,48 +149,61 @@ SkColor GetAvatarStrokeColor(SkColor avatar_fill_color) { ...@@ -104,48 +149,61 @@ SkColor GetAvatarStrokeColor(SkColor avatar_fill_color) {
return color_utils::HSLToSkColor(color_hsl, SkColorGetA(avatar_fill_color)); 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( chrome_colors::ColorInfo GenerateNewProfileColorWithGenerator(
ProfileAttributesStorage& storage, ProfileAttributesStorage& storage,
base::OnceCallback<size_t(size_t count)> random_generator) { base::OnceCallback<size_t(size_t count)> random_generator,
// TODO(crbug.com/1108295): ProfileAttributesEntry* current_profile) {
// - Exclude colors from the first line of the picker. // TODO(crbug.com/1108295): Return only a SkColor if the full ColorInfo is not
// - For sign-in interception, prefer colors from the same line (if not // needed.
// excluded).
// - Both of the previous constraints could be formulated using saturation and
// brightness, instead of assuming a concrete structure of lines.
// - Return only a SkColor if the full ColorInfo is not needed.
std::set<ProfileThemeColors> used_theme_colors; std::set<ProfileThemeColors> used_theme_colors;
for (ProfileAttributesEntry* entry : storage.GetAllProfilesAttributes()) { for (ProfileAttributesEntry* entry : storage.GetAllProfilesAttributes()) {
used_theme_colors.insert(entry->GetProfileThemeColors()); base::Optional<ProfileThemeColors> current_colors =
entry->GetProfileThemeColorsIfSet();
if (current_colors)
used_theme_colors.insert(*current_colors);
} }
// Collect indices of profile colors that are not in `used_theme_colors`. base::Optional<double> current_color_lightness =
std::vector<int> available_color_indices; ExtractCurrentColorLightness(current_profile);
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))
available_color_indices.push_back(i);
}
size_t index; // 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()) { if (available_color_indices.empty()) {
// No suitable color, fallback to random color. available_color_indices =
size_t size = base::size(chrome_colors::kGeneratedColorsInfo); GetAvailableColorIndices(used_theme_colors, base::nullopt);
index = std::move(random_generator).Run(size);
DCHECK_LT(index, size);
} else {
size_t size = available_color_indices.size();
size_t available_index = std::move(random_generator).Run(size);
DCHECK_LT(available_index, size);
index = available_color_indices[available_index];
} }
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]; return chrome_colors::kGeneratedColorsInfo[index];
} }
chrome_colors::ColorInfo GenerateNewProfileColor() { chrome_colors::ColorInfo GenerateNewProfileColor(
ProfileAttributesEntry* current_profile) {
return GenerateNewProfileColorWithGenerator( return GenerateNewProfileColorWithGenerator(
g_browser_process->profile_manager()->GetProfileAttributesStorage(), g_browser_process->profile_manager()->GetProfileAttributesStorage(),
base::BindOnce(&GenerateRandomIndex)); base::BindOnce(&GenerateRandomIndex), current_profile);
} }
...@@ -13,6 +13,7 @@ struct ColorInfo; ...@@ -13,6 +13,7 @@ struct ColorInfo;
} }
class CustomThemeSupplier; class CustomThemeSupplier;
class ProfileAttributesEntry;
class ProfileAttributesStorage; class ProfileAttributesStorage;
struct ProfileThemeColors { struct ProfileThemeColors {
...@@ -45,17 +46,27 @@ SkColor GetProfileForegroundIconColor(SkColor profile_highlight_color); ...@@ -45,17 +46,27 @@ SkColor GetProfileForegroundIconColor(SkColor profile_highlight_color);
// Returns the color that should be used to generate the default avatar icon. // Returns the color that should be used to generate the default avatar icon.
SkColor GetAvatarStrokeColor(SkColor avatar_fill_color); SkColor GetAvatarStrokeColor(SkColor avatar_fill_color);
// Filters used for generating colors for a new profile. Exposed for tests.
bool IsSaturatedForAutoselection(SkColor color);
bool IsLightForAutoselection(SkColor color, double reference_lightness);
// Returns a new color for a profile, based on the colors of the existing // Returns a new color for a profile, based on the colors of the existing
// profiles in `storage`. `random_generator` is called to provide randomness and // profiles in `storage`. `random_generator` is called to provide randomness and
// must return a value smaller than provided `count`. This implementation // must return a value smaller than provided `count`. This implementation
// function is mainly exposed for easier mocking in tests. In production code, // function is mainly exposed for easier mocking in tests. In production code,
// GenerateNewProfileColor() should be sufficient. // GenerateNewProfileColor() should be sufficient. `current_profile` should be
// specified if a new profile is created within an existing profile (such as for
// sign-in interception) and thus the two colors should somehow match.
chrome_colors::ColorInfo GenerateNewProfileColorWithGenerator( chrome_colors::ColorInfo GenerateNewProfileColorWithGenerator(
ProfileAttributesStorage& storage, ProfileAttributesStorage& storage,
base::OnceCallback<size_t(size_t count)> random_generator); base::OnceCallback<size_t(size_t count)> random_generator,
ProfileAttributesEntry* current_profile = nullptr);
// Returns a new random color for a profile, based on the colors of the existing // Returns a new random color for a profile, based on the colors of the existing
// profiles. // profiles. `current_profile` should be specified if a new profile is created
chrome_colors::ColorInfo GenerateNewProfileColor(); // within an existing profile (such as for sign-in interception) and thus the
// two colors should somehow match.
chrome_colors::ColorInfo GenerateNewProfileColor(
ProfileAttributesEntry* current_profile = nullptr);
#endif // CHROME_BROWSER_UI_SIGNIN_PROFILE_COLORS_UTIL_H_ #endif // CHROME_BROWSER_UI_SIGNIN_PROFILE_COLORS_UTIL_H_
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
#include "chrome/browser/profiles/profile_attributes_entry.h" #include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h" #include "chrome/browser/profiles/profile_attributes_storage.h"
...@@ -18,6 +19,7 @@ ...@@ -18,6 +19,7 @@
#include "chrome/test/base/testing_profile_manager.h" #include "chrome/test/base/testing_profile_manager.h"
#include "components/account_id/account_id.h" #include "components/account_id/account_id.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/color_utils.h"
namespace { namespace {
...@@ -28,10 +30,29 @@ size_t ReturnNth(size_t n, size_t size) { ...@@ -28,10 +30,29 @@ size_t ReturnNth(size_t n, size_t size) {
return n; return n;
} }
size_t CaptureCountAndReturnZero(size_t* storage, size_t count) {
*storage = count;
return 0;
}
const chrome_colors::ColorInfo& GetColor(size_t index) { const chrome_colors::ColorInfo& GetColor(size_t index) {
return chrome_colors::kGeneratedColorsInfo[index]; return chrome_colors::kGeneratedColorsInfo[index];
} }
ProfileThemeColors GetProfileThemeColorsForAutogeneratedColor(
SkColor autogenerated_color) {
auto pack = base::MakeRefCounted<BrowserThemePack>(
CustomThemeSupplier::ThemeType::AUTOGENERATED);
BrowserThemePack::BuildFromColor(autogenerated_color, pack.get());
return GetProfileThemeColorsForThemeSupplier(pack.get());
}
SkColor GetHighlightColor(size_t index) {
ProfileThemeColors theme_colors =
GetProfileThemeColorsForAutogeneratedColor(GetColor(index).color);
return theme_colors.profile_highlight_color;
}
class ProfileColorsUtilTest : public testing::Test { class ProfileColorsUtilTest : public testing::Test {
public: public:
ProfileColorsUtilTest() ProfileColorsUtilTest()
...@@ -41,7 +62,7 @@ class ProfileColorsUtilTest : public testing::Test { ...@@ -41,7 +62,7 @@ class ProfileColorsUtilTest : public testing::Test {
protected: protected:
void SetUp() override { ASSERT_TRUE(testing_profile_manager_.SetUp()); } void SetUp() override { ASSERT_TRUE(testing_profile_manager_.SetUp()); }
void AddProfile(base::Optional<SkColor> color) { ProfileAttributesEntry* AddProfile(base::Optional<SkColor> color) {
size_t number_of_profiles = storage()->GetNumberOfProfiles(); size_t number_of_profiles = storage()->GetNumberOfProfiles();
base::FilePath profile_path = base::FilePath profile_path =
...@@ -55,34 +76,56 @@ class ProfileColorsUtilTest : public testing::Test { ...@@ -55,34 +76,56 @@ class ProfileColorsUtilTest : public testing::Test {
EXPECT_EQ(number_of_profiles + 1, storage()->GetNumberOfProfiles()); EXPECT_EQ(number_of_profiles + 1, storage()->GetNumberOfProfiles());
if (!color.has_value())
return;
ProfileAttributesEntry* entry = nullptr; ProfileAttributesEntry* entry = nullptr;
EXPECT_TRUE(storage()->GetProfileAttributesWithPath(profile_path, &entry)); EXPECT_TRUE(storage()->GetProfileAttributesWithPath(profile_path, &entry));
auto pack = base::MakeRefCounted<BrowserThemePack>( if (color.has_value()) {
CustomThemeSupplier::ThemeType::AUTOGENERATED); entry->SetProfileThemeColors(
BrowserThemePack::BuildFromColor(*color, pack.get()); GetProfileThemeColorsForAutogeneratedColor(*color));
ProfileThemeColors colors = }
GetProfileThemeColorsForThemeSupplier(pack.get());
entry->SetProfileThemeColors(colors);
}
void ExpectAllColorsAvailable() { return entry;
for (size_t i = 0; i < kColorsCount; ++i)
EXPECT_EQ(GetAvailableColor(i).id, GetColor(i).id);
} }
ProfileAttributesStorage* storage() { ProfileAttributesStorage* storage() {
return testing_profile_manager_.profile_attributes_storage(); return testing_profile_manager_.profile_attributes_storage();
} }
chrome_colors::ColorInfo GetAvailableColor(size_t n) { size_t GetAvailableColorCount(ProfileAttributesEntry* entry = nullptr) {
size_t count;
// Instead of providing a random number generator, return an arbitrary value
// and capture the count of options.
GenerateNewProfileColorWithGenerator(
*storage(), base::Bind(&CaptureCountAndReturnZero, &count), entry);
return count;
}
int GetAvailableColorId(size_t n, ProfileAttributesEntry* entry = nullptr) {
// Instead of providing a random number generator, return the nth option // Instead of providing a random number generator, return the nth option
// deterministically. // deterministically.
return GenerateNewProfileColorWithGenerator(*storage(), return GenerateNewProfileColorWithGenerator(
base::Bind(&ReturnNth, n)); *storage(), base::Bind(&ReturnNth, n), entry)
.id;
}
std::set<int> GetAvailableColorsIds(ProfileAttributesEntry* entry = nullptr) {
size_t count = GetAvailableColorCount(entry);
std::set<int> colors;
for (size_t i = 0; i < count; i++)
colors.insert(GetAvailableColorId(i, entry));
return colors;
}
void ExpectAllSaturatedColorsAvailable(
ProfileAttributesEntry* entry = nullptr) {
std::set<int> available_colors = GetAvailableColorsIds(entry);
for (size_t i = 0; i < kColorsCount; ++i) {
if (IsSaturatedForAutoselection(GetHighlightColor(i))) {
EXPECT_TRUE(base::Contains(available_colors, GetColor(i).id));
} else {
EXPECT_FALSE(base::Contains(available_colors, GetColor(i).id));
}
}
} }
private: private:
...@@ -90,9 +133,50 @@ class ProfileColorsUtilTest : public testing::Test { ...@@ -90,9 +133,50 @@ class ProfileColorsUtilTest : public testing::Test {
base::test::TaskEnvironment task_environment_; base::test::TaskEnvironment task_environment_;
}; };
TEST_F(ProfileColorsUtilTest, IsSaturatedForAutoselection) {
// Just a sanity check. Don't want to include exact thresholds here.
EXPECT_FALSE(IsSaturatedForAutoselection(SK_ColorBLACK));
EXPECT_FALSE(IsSaturatedForAutoselection(SK_ColorGRAY));
EXPECT_FALSE(IsSaturatedForAutoselection(SK_ColorWHITE));
EXPECT_TRUE(IsSaturatedForAutoselection(SK_ColorRED));
EXPECT_TRUE(IsSaturatedForAutoselection(SK_ColorGREEN));
EXPECT_TRUE(IsSaturatedForAutoselection(SK_ColorBLUE));
// Even with some transparency, it is still saturated enough.
EXPECT_TRUE(IsSaturatedForAutoselection(SkColorSetA(SK_ColorRED, 150u)));
}
TEST_F(ProfileColorsUtilTest, IsLightForAutoselection) {
// Just a sanity check. Don't want to include exact thresholds here.
// Get two variants of red color: put slightly transparent red (a) on white
// and (b) on black. These should have enough similar lightness.
SkColor lighter = color_utils::GetResultingPaintColor(
/*fg=*/SkColorSetA(SK_ColorRED, 200u),
/*bg=*/SK_ColorWHITE);
color_utils::HSL lighter_hsl;
color_utils::SkColorToHSL(lighter, &lighter_hsl);
SkColor darker = color_utils::GetResultingPaintColor(
/*fg=*/SkColorSetA(SK_ColorRED, 200u),
/*bg=*/SK_ColorBLACK);
EXPECT_TRUE(IsLightForAutoselection(darker, lighter_hsl.l));
// Repeat the same with more difference to get the opposite outcome.
SkColor very_light = color_utils::GetResultingPaintColor(
/*fg=*/SkColorSetA(SK_ColorRED, 100u),
/*bg=*/SK_ColorWHITE);
color_utils::HSL very_light_hsl;
color_utils::SkColorToHSL(very_light, &very_light_hsl);
SkColor very_dark = color_utils::GetResultingPaintColor(
/*fg=*/SkColorSetA(SK_ColorRED, 100u),
/*bg=*/SK_ColorBLACK);
EXPECT_FALSE(IsLightForAutoselection(very_dark, very_light_hsl.l));
}
// Test that all colors are available with no other profiles. // Test that all colors are available with no other profiles.
TEST_F(ProfileColorsUtilTest, GenerateNewProfileColorWithNoColoredProfile) { TEST_F(ProfileColorsUtilTest, GenerateNewProfileColorWithNoColoredProfile) {
ExpectAllColorsAvailable(); ExpectAllSaturatedColorsAvailable();
// Add some profiles with the default theme. // Add some profiles with the default theme.
AddProfile(base::nullopt); AddProfile(base::nullopt);
...@@ -101,20 +185,45 @@ TEST_F(ProfileColorsUtilTest, GenerateNewProfileColorWithNoColoredProfile) { ...@@ -101,20 +185,45 @@ TEST_F(ProfileColorsUtilTest, GenerateNewProfileColorWithNoColoredProfile) {
AddProfile(SK_ColorRED); AddProfile(SK_ColorRED);
// It still behaves the same, all colors are available. // It still behaves the same, all colors are available.
ExpectAllColorsAvailable(); ExpectAllSaturatedColorsAvailable();
} }
// Test that the taken colors are not available. // Test that the taken colors are not available.
TEST_F(ProfileColorsUtilTest, TEST_F(ProfileColorsUtilTest,
GenerateNewProfileColorWithMultipleColoredProfiles) { GenerateNewProfileColorWithMultipleColoredProfiles) {
std::set<int> colors_ids = GetAvailableColorsIds();
EXPECT_TRUE(base::Contains(colors_ids, GetColor(5).id));
EXPECT_TRUE(base::Contains(colors_ids, GetColor(6).id));
AddProfile(GetColor(5).color); AddProfile(GetColor(5).color);
AddProfile(GetColor(6).color); AddProfile(GetColor(6).color);
EXPECT_EQ(GetAvailableColor(0).id, GetColor(0).id); std::set<int> limited_colors_ids = GetAvailableColorsIds();
EXPECT_EQ(GetAvailableColor(4).id, GetColor(4).id); EXPECT_EQ(colors_ids.size(), limited_colors_ids.size() + 2);
EXPECT_EQ(GetAvailableColor(5).id, GetColor(7).id); EXPECT_FALSE(base::Contains(limited_colors_ids, GetColor(5).id));
size_t max = kColorsCount - 1; EXPECT_FALSE(base::Contains(limited_colors_ids, GetColor(6).id));
EXPECT_EQ(GetAvailableColor(max - 2).id, GetColor(max).id); }
// Test that specifying a profile restricts the choice to colors of similar
// lightness.
TEST_F(ProfileColorsUtilTest, GenerateNewProfileColorForCurrentProfile) {
const size_t kCurrentProfile = 5;
ProfileAttributesEntry* entry = AddProfile(GetColor(kCurrentProfile).color);
color_utils::HSL current_profile_hsl;
color_utils::SkColorToHSL(GetHighlightColor(kCurrentProfile),
&current_profile_hsl);
std::set<int> colors_ids = GetAvailableColorsIds(entry);
EXPECT_FALSE(base::Contains(colors_ids, GetColor(5).id));
EXPECT_FALSE(colors_ids.empty());
for (size_t i = 0; i < kColorsCount; i++) {
const SkColor highlight_color = GetHighlightColor(i);
if (i != kCurrentProfile && IsSaturatedForAutoselection(highlight_color) &&
IsLightForAutoselection(highlight_color, current_profile_hsl.l)) {
EXPECT_TRUE(base::Contains(colors_ids, GetColor(i).id));
} else {
EXPECT_FALSE(base::Contains(colors_ids, GetColor(i).id));
}
}
} }
// Test that if all colors are taken, then again all are available. // Test that if all colors are taken, then again all are available.
...@@ -123,13 +232,55 @@ TEST_F(ProfileColorsUtilTest, GenerateNewProfileColorWithAllColorsTaken) { ...@@ -123,13 +232,55 @@ TEST_F(ProfileColorsUtilTest, GenerateNewProfileColorWithAllColorsTaken) {
AddProfile(GetColor(i).color); AddProfile(GetColor(i).color);
// Only the last color is available. // Only the last color is available.
EXPECT_EQ(GetAvailableColor(0).id, GetColor(kColorsCount - 1).id); EXPECT_EQ(GetAvailableColorId(0), GetColor(kColorsCount - 1).id);
// Take the last available color. // Take the last available color.
AddProfile(GetColor(kColorsCount - 1).color); AddProfile(GetColor(kColorsCount - 1).color);
// Again, all colors are available. // Again, all colors are available.
ExpectAllColorsAvailable(); ExpectAllSaturatedColorsAvailable();
}
// Test that when all colors with similar lightness are taken, all saturated
// colors are available.
TEST_F(ProfileColorsUtilTest,
GenerateNewProfileColorForCurrentProfileWithAllSimilarColorsTaken) {
// Start with an arbitrary profile.
const size_t kCurrentProfile = 5;
ProfileAttributesEntry* entry = AddProfile(GetColor(kCurrentProfile).color);
// Add profiles for almost all available colors.
std::set<int> colors_ids = GetAvailableColorsIds(entry);
// First keep one remaining color id without a profile.
ASSERT_FALSE(colors_ids.empty());
int remaining_id = *colors_ids.begin();
SkColor remaining_color;
for (size_t i = 0; i < kColorsCount; i++) {
if (base::Contains(colors_ids, GetColor(i).id)) {
if (GetColor(i).id == remaining_id) {
remaining_color = GetColor(i).color;
} else {
AddProfile(GetColor(i).color);
}
}
}
// One `remaining_id` is left.
std::set<int> singleton_colors_ids = GetAvailableColorsIds(entry);
EXPECT_EQ(singleton_colors_ids.size(), 1u);
EXPECT_TRUE(base::Contains(singleton_colors_ids, remaining_id));
// Add this remaining profile, all unused colors are again available.
AddProfile(remaining_color);
std::set<int> remaining_ids = GetAvailableColorsIds(entry);
for (size_t i = 0; i < kColorsCount; ++i) {
if (IsSaturatedForAutoselection(GetHighlightColor(i)) &&
!base::Contains(colors_ids, GetColor(i).id) && i != kCurrentProfile) {
EXPECT_TRUE(base::Contains(remaining_ids, GetColor(i).id));
} else {
EXPECT_FALSE(base::Contains(remaining_ids, GetColor(i).id));
}
}
} }
} // namespace } // namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment