blob: 0812d3312efc0d721a9d01ed6e99edb9c400eb78 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include <algorithm>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "cc/paint/paint_flags.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/avatar_menu.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/profiles/profile_colors_util.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/vector_icons/vector_icons.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/dynamic_color/palette_factory.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/native_theme/native_theme.h"
#include "url/url_canon.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/windows_version.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/grit/chrome_unscaled_resources.h" // nogncheck crbug.com/1125897
#include "ui/gfx/icon_util.h" // For Iconutil::kLargeIconSize.
#endif
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#endif
// Helper methods for transforming and drawing avatar icons.
namespace {
// Palette color tones for the material color utils used to select a
// material-appropriate color for a given profile color,
constexpr float kIconToneDark = 40.f;
constexpr float kIconToneLight = 80.f;
#if BUILDFLAG(IS_WIN)
const int kOldAvatarIconWidth = 38;
const int kOldAvatarIconHeight = 31;
// 2x sized versions of the old profile avatar icons.
// TODO(crbug.com/41444689): Clean this up.
const int kProfileAvatarIconResources2x[] = {
IDR_PROFILE_AVATAR_2X_0, IDR_PROFILE_AVATAR_2X_1,
IDR_PROFILE_AVATAR_2X_2, IDR_PROFILE_AVATAR_2X_3,
IDR_PROFILE_AVATAR_2X_4, IDR_PROFILE_AVATAR_2X_5,
IDR_PROFILE_AVATAR_2X_6, IDR_PROFILE_AVATAR_2X_7,
IDR_PROFILE_AVATAR_2X_8, IDR_PROFILE_AVATAR_2X_9,
IDR_PROFILE_AVATAR_2X_10, IDR_PROFILE_AVATAR_2X_11,
IDR_PROFILE_AVATAR_2X_12, IDR_PROFILE_AVATAR_2X_13,
IDR_PROFILE_AVATAR_2X_14, IDR_PROFILE_AVATAR_2X_15,
IDR_PROFILE_AVATAR_2X_16, IDR_PROFILE_AVATAR_2X_17,
IDR_PROFILE_AVATAR_2X_18, IDR_PROFILE_AVATAR_2X_19,
IDR_PROFILE_AVATAR_2X_20, IDR_PROFILE_AVATAR_2X_21,
IDR_PROFILE_AVATAR_2X_22, IDR_PROFILE_AVATAR_2X_23,
IDR_PROFILE_AVATAR_2X_24, IDR_PROFILE_AVATAR_2X_25,
IDR_PROFILE_AVATAR_2X_26,
};
// Returns a copied SkBitmap for the given image that can be safely passed to
// another thread.
SkBitmap GetSkBitmapCopy(const gfx::Image& image) {
DCHECK(!image.IsEmpty());
const SkBitmap* image_bitmap = image.ToSkBitmap();
SkBitmap bitmap_copy;
if (bitmap_copy.tryAllocPixels(image_bitmap->info()))
image_bitmap->readPixels(bitmap_copy.info(), bitmap_copy.getPixels(),
bitmap_copy.rowBytes(), 0, 0);
return bitmap_copy;
}
#endif // BUILDFLAG(IS_WIN)
// Determine what the scaled height of the avatar icon should be for a
// specified width, to preserve the aspect ratio.
int GetScaledAvatarHeightForWidth(int width, const gfx::ImageSkia& avatar) {
// Multiply the width by the inverted aspect ratio (height over
// width), and then add 0.5 to ensure the int truncation rounds nicely.
int scaled_height = width *
((float) avatar.height() / (float) avatar.width()) + 0.5f;
return scaled_height;
}
// A CanvasImageSource that draws a sized and positioned avatar with an
// optional border independently of the scale factor.
class AvatarImageSource : public gfx::CanvasImageSource {
public:
enum AvatarPosition {
POSITION_CENTER,
POSITION_BOTTOM_CENTER,
};
AvatarImageSource(gfx::ImageSkia avatar,
const gfx::Size& canvas_size,
int width,
AvatarPosition position,
profiles::AvatarShape shape);
AvatarImageSource(gfx::ImageSkia avatar,
const gfx::Size& canvas_size,
int width,
AvatarPosition position);
AvatarImageSource(const AvatarImageSource&) = delete;
AvatarImageSource& operator=(const AvatarImageSource&) = delete;
~AvatarImageSource() override;
// CanvasImageSource override:
void Draw(gfx::Canvas* canvas) override;
private:
gfx::ImageSkia avatar_;
const gfx::Size canvas_size_;
const int width_;
const int height_;
const AvatarPosition position_;
const profiles::AvatarShape shape_;
};
AvatarImageSource::AvatarImageSource(gfx::ImageSkia avatar,
const gfx::Size& canvas_size,
int width,
AvatarPosition position,
profiles::AvatarShape shape)
: gfx::CanvasImageSource(canvas_size),
canvas_size_(canvas_size),
width_(width),
height_(GetScaledAvatarHeightForWidth(width, avatar)),
position_(position),
shape_(shape) {
avatar_ = gfx::ImageSkiaOperations::CreateResizedImage(
avatar, skia::ImageOperations::RESIZE_BEST,
gfx::Size(width_, height_));
}
AvatarImageSource::AvatarImageSource(gfx::ImageSkia avatar,
const gfx::Size& canvas_size,
int width,
AvatarPosition position)
: AvatarImageSource(avatar,
canvas_size,
width,
position,
profiles::SHAPE_SQUARE) {}
AvatarImageSource::~AvatarImageSource() {
}
void AvatarImageSource::Draw(gfx::Canvas* canvas) {
// Center the avatar horizontally.
int x = (canvas_size_.width() - width_) / 2;
int y;
if (position_ == POSITION_CENTER) {
// Draw the avatar centered on the canvas.
y = (canvas_size_.height() - height_) / 2;
} else {
// Draw the avatar on the bottom center of the canvas, leaving 1px below.
y = canvas_size_.height() - height_ - 1;
}
#if BUILDFLAG(IS_ANDROID)
// Circular shape is only available on desktop platforms.
DCHECK(shape_ != profiles::SHAPE_CIRCLE);
#else
if (shape_ == profiles::SHAPE_CIRCLE) {
// Draw the avatar on the bottom center of the canvas; overrides the
// previous position specification to avoid leaving visible gap below the
// avatar.
y = canvas_size_.height() - height_;
// Calculate the circular mask that will be used to display the avatar
// image.
SkPath circular_mask;
circular_mask.addCircle(SkIntToScalar(canvas_size_.width() / 2),
SkIntToScalar(canvas_size_.height() / 2),
SkIntToScalar(canvas_size_.width() / 2));
canvas->ClipPath(circular_mask, true);
}
#endif
canvas->DrawImageInt(avatar_, x, y);
}
class ImageWithBackgroundSource : public gfx::CanvasImageSource {
public:
ImageWithBackgroundSource(const gfx::ImageSkia& image, SkColor background)
: gfx::CanvasImageSource(image.size()),
image_(image),
background_(background) {}
ImageWithBackgroundSource(const ImageWithBackgroundSource&) = delete;
ImageWithBackgroundSource& operator=(const ImageWithBackgroundSource&) =
delete;
~ImageWithBackgroundSource() override = default;
// gfx::CanvasImageSource override.
void Draw(gfx::Canvas* canvas) override {
canvas->DrawColor(background_);
canvas->DrawImageInt(image_, 0, 0);
}
private:
const gfx::ImageSkia image_;
const SkColor background_;
};
// Returns icon with padding with no background.
const gfx::ImageSkia CreatePaddedIcon(const gfx::VectorIcon& icon,
int size,
SkColor color,
float icon_to_image_ratio) {
const int padding =
static_cast<int>(size * (1.0f - icon_to_image_ratio) / 2.0f);
const gfx::ImageSkia sized_icon =
gfx::CreateVectorIcon(icon, size - 2 * padding, color);
return gfx::CanvasImageSource::CreatePadded(sized_icon, gfx::Insets(padding));
}
// Returns a filled person avatar icon.
gfx::Image GetLegacyPlaceholderAvatarIconWithColors(SkColor fill_color,
SkColor stroke_color,
int size) {
CHECK(!base::FeatureList::IsEnabled(kOutlineSilhouetteIcon));
const gfx::VectorIcon& person_icon =
size >= 40 ? kPersonFilledPaddedLargeIcon : kPersonFilledPaddedSmallIcon;
const gfx::ImageSkia icon_without_background = gfx::CreateVectorIcon(
gfx::IconDescription(person_icon, size, stroke_color));
const gfx::ImageSkia icon_with_background(
std::make_unique<ImageWithBackgroundSource>(icon_without_background,
fill_color),
gfx::Size(size, size));
return gfx::Image(icon_with_background);
}
} // namespace
namespace profiles {
struct IconResourceInfo {
int resource_id;
const char* filename;
int label_id;
};
constexpr int kAvatarIconSize = 96;
constexpr SkColor kAvatarTutorialBackgroundColor =
SkColorSetRGB(0x42, 0x85, 0xf4);
constexpr SkColor kAvatarTutorialContentTextColor =
SkColorSetRGB(0xc6, 0xda, 0xfc);
constexpr SkColor kAvatarBubbleAccountsBackgroundColor =
SkColorSetRGB(0xf3, 0xf3, 0xf3);
constexpr SkColor kAvatarBubbleGaiaBackgroundColor =
SkColorSetRGB(0xf5, 0xf5, 0xf5);
constexpr SkColor kUserManagerBackgroundColor = SkColorSetRGB(0xee, 0xee, 0xee);
constexpr char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
constexpr base::FilePath::CharType kGAIAPictureFileName[] =
FILE_PATH_LITERAL("Google Profile Picture.png");
constexpr base::FilePath::CharType kHighResAvatarFolderName[] =
FILE_PATH_LITERAL("Avatars");
// The size of the function-static kDefaultAvatarIconResources array below.
#if BUILDFLAG(IS_ANDROID)
constexpr size_t kDefaultAvatarIconsCount = 1;
#elif BUILDFLAG(IS_CHROMEOS_ASH)
constexpr size_t kDefaultAvatarIconsCount = 27;
#else
constexpr size_t kDefaultAvatarIconsCount = 56;
#endif
#if !BUILDFLAG(IS_ANDROID)
// The first 8 icons are generic.
constexpr size_t kGenericAvatarIconsCount = 8;
#else
constexpr size_t kGenericAvatarIconsCount = 0;
#endif
#if !BUILDFLAG(IS_ANDROID)
// The avatar used as a placeholder.
constexpr size_t kPlaceholderAvatarIndex = 26;
#else
constexpr size_t kPlaceholderAvatarIndex = 0;
#endif
ui::ImageModel GetGuestAvatar(int size) {
return ui::ImageModel::FromVectorIcon(
kUserAccountAvatarRefreshIcon,
switches::IsExplicitBrowserSigninUIOnDesktopEnabled()
? ui::kColorMenuIcon
: ui::kColorAvatarIconGuest,
size);
}
gfx::Image GetSizedAvatarIcon(const gfx::Image& image,
int width,
int height,
AvatarShape shape) {
gfx::Size size(width, height);
// Source for a centered, sized icon.
std::unique_ptr<gfx::ImageSkiaSource> source(
new AvatarImageSource(*image.ToImageSkia(), size, std::min(width, height),
AvatarImageSource::POSITION_CENTER, shape));
return gfx::Image(gfx::ImageSkia(std::move(source), size));
}
gfx::Image GetSizedAvatarIcon(const gfx::Image& image, int width, int height) {
return GetSizedAvatarIcon(image, width, height, profiles::SHAPE_SQUARE);
}
gfx::Image GetAvatarIconForWebUI(const gfx::Image& image) {
return GetSizedAvatarIcon(image, kAvatarIconSize, kAvatarIconSize);
}
gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image,
int dst_width,
int dst_height) {
// The image requires no border or resizing.
if (image.Height() <= kAvatarIconSize)
return image;
int size = std::min({kAvatarIconSize, dst_width, dst_height});
gfx::Size dst_size(dst_width, dst_height);
// Source for a sized icon drawn at the bottom center of the canvas,
// with an etched border (for GAIA images).
std::unique_ptr<gfx::ImageSkiaSource> source(
new AvatarImageSource(*image.ToImageSkia(), dst_size, size,
AvatarImageSource::POSITION_BOTTOM_CENTER));
return gfx::Image(gfx::ImageSkia(std::move(source), dst_size));
}
#if BUILDFLAG(IS_MAC)
gfx::Image GetAvatarIconForNSMenu(const base::FilePath& profile_path) {
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_path);
if (!entry) {
// This can happen if the user deletes the current profile.
return gfx::Image();
}
PlaceholderAvatarIconParams icon_params =
GetPlaceholderAvatarIconParamsVisibleAgainstColor(
ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors()
? SK_ColorBLACK
: SK_ColorWHITE);
// Get a higher res than 16px so it looks good after cropping to a circle.
gfx::Image icon = entry->GetAvatarIcon(
kAvatarIconSize, /*download_high_res=*/false, icon_params);
return profiles::GetSizedAvatarIcon(
icon, kMenuAvatarIconSize, kMenuAvatarIconSize, profiles::SHAPE_CIRCLE);
}
#endif
// Helper methods for accessing, transforming and drawing avatar icons.
size_t GetDefaultAvatarIconCount() {
return kDefaultAvatarIconsCount;
}
size_t GetGenericAvatarIconCount() {
return kGenericAvatarIconsCount;
}
size_t GetPlaceholderAvatarIndex() {
return kPlaceholderAvatarIndex;
}
size_t GetModernAvatarIconStartIndex() {
#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
return GetPlaceholderAvatarIndex() + 1;
#else
// Only use the placeholder avatar on ChromeOS and Android.
// TODO(crbug.com/41444689): Clean up code and remove code dependencies from
// Android and ChromeOS. Avatar icons from this file are not used on these
// platforms.
return GetPlaceholderAvatarIndex();
#endif
}
bool IsModernAvatarIconIndex(size_t icon_index) {
return icon_index >= GetModernAvatarIconStartIndex() &&
icon_index < GetDefaultAvatarIconCount();
}
int GetPlaceholderAvatarIconResourceID() {
// TODO(crbug.com/40138086): Replace with the new icon. Consider coloring the
// icon (i.e. providing the image through
// ProfileAttributesEntry::GetAvatarIcon(), instead) which would require more
// refactoring.
return IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE;
}
std::string GetPlaceholderAvatarIconUrl() {
// TODO(crbug.com/40138086): Replace with the new icon. Consider coloring the
// icon (i.e. providing the image through
// ProfileAttributesEntry::GetAvatarIcon(), instead) which would require more
// refactoring.
return "chrome://theme/IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE";
}
const IconResourceInfo* GetDefaultAvatarIconResourceInfo(size_t index) {
CHECK_LT(index, kDefaultAvatarIconsCount);
static const IconResourceInfo resource_info[kDefaultAvatarIconsCount] = {
// Old avatar icons:
#if !BUILDFLAG(IS_ANDROID)
{IDR_PROFILE_AVATAR_0, "avatar_generic.png", IDS_DEFAULT_AVATAR_LABEL_0},
{IDR_PROFILE_AVATAR_1, "avatar_generic_aqua.png",
IDS_DEFAULT_AVATAR_LABEL_1},
{IDR_PROFILE_AVATAR_2, "avatar_generic_blue.png",
IDS_DEFAULT_AVATAR_LABEL_2},
{IDR_PROFILE_AVATAR_3, "avatar_generic_green.png",
IDS_DEFAULT_AVATAR_LABEL_3},
{IDR_PROFILE_AVATAR_4, "avatar_generic_orange.png",
IDS_DEFAULT_AVATAR_LABEL_4},
{IDR_PROFILE_AVATAR_5, "avatar_generic_purple.png",
IDS_DEFAULT_AVATAR_LABEL_5},
{IDR_PROFILE_AVATAR_6, "avatar_generic_red.png",
IDS_DEFAULT_AVATAR_LABEL_6},
{IDR_PROFILE_AVATAR_7, "avatar_generic_yellow.png",
IDS_DEFAULT_AVATAR_LABEL_7},
{IDR_PROFILE_AVATAR_8, "avatar_secret_agent.png",
IDS_DEFAULT_AVATAR_LABEL_8},
{IDR_PROFILE_AVATAR_9, "avatar_superhero.png", IDS_DEFAULT_AVATAR_LABEL_9},
{IDR_PROFILE_AVATAR_10, "avatar_volley_ball.png",
IDS_DEFAULT_AVATAR_LABEL_10},
{IDR_PROFILE_AVATAR_11, "avatar_businessman.png",
IDS_DEFAULT_AVATAR_LABEL_11},
{IDR_PROFILE_AVATAR_12, "avatar_ninja.png", IDS_DEFAULT_AVATAR_LABEL_12},
{IDR_PROFILE_AVATAR_13, "avatar_alien.png", IDS_DEFAULT_AVATAR_LABEL_13},
{IDR_PROFILE_AVATAR_14, "avatar_awesome.png", IDS_DEFAULT_AVATAR_LABEL_14},
{IDR_PROFILE_AVATAR_15, "avatar_flower.png", IDS_DEFAULT_AVATAR_LABEL_15},
{IDR_PROFILE_AVATAR_16, "avatar_pizza.png", IDS_DEFAULT_AVATAR_LABEL_16},
{IDR_PROFILE_AVATAR_17, "avatar_soccer.png", IDS_DEFAULT_AVATAR_LABEL_17},
{IDR_PROFILE_AVATAR_18, "avatar_burger.png", IDS_DEFAULT_AVATAR_LABEL_18},
{IDR_PROFILE_AVATAR_19, "avatar_cat.png", IDS_DEFAULT_AVATAR_LABEL_19},
{IDR_PROFILE_AVATAR_20, "avatar_cupcake.png", IDS_DEFAULT_AVATAR_LABEL_20},
{IDR_PROFILE_AVATAR_21, "avatar_dog.png", IDS_DEFAULT_AVATAR_LABEL_21},
{IDR_PROFILE_AVATAR_22, "avatar_horse.png", IDS_DEFAULT_AVATAR_LABEL_22},
{IDR_PROFILE_AVATAR_23, "avatar_margarita.png",
IDS_DEFAULT_AVATAR_LABEL_23},
{IDR_PROFILE_AVATAR_24, "avatar_note.png", IDS_DEFAULT_AVATAR_LABEL_24},
{IDR_PROFILE_AVATAR_25, "avatar_sun_cloud.png",
IDS_DEFAULT_AVATAR_LABEL_25},
#endif
// Placeholder avatar icon:
{IDR_PROFILE_AVATAR_26, nullptr, IDS_DEFAULT_AVATAR_LABEL_26},
#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
// Modern avatar icons:
{IDR_PROFILE_AVATAR_27, "avatar_origami_cat.png",
IDS_DEFAULT_AVATAR_LABEL_27},
{IDR_PROFILE_AVATAR_28, "avatar_origami_corgi.png",
IDS_DEFAULT_AVATAR_LABEL_28},
{IDR_PROFILE_AVATAR_29, "avatar_origami_dragon.png",
IDS_DEFAULT_AVATAR_LABEL_29},
{IDR_PROFILE_AVATAR_30, "avatar_origami_elephant.png",
IDS_DEFAULT_AVATAR_LABEL_30},
{IDR_PROFILE_AVATAR_31, "avatar_origami_fox.png",
IDS_DEFAULT_AVATAR_LABEL_31},
{IDR_PROFILE_AVATAR_32, "avatar_origami_monkey.png",
IDS_DEFAULT_AVATAR_LABEL_32},
{IDR_PROFILE_AVATAR_33, "avatar_origami_panda.png",
IDS_DEFAULT_AVATAR_LABEL_33},
{IDR_PROFILE_AVATAR_34, "avatar_origami_penguin.png",
IDS_DEFAULT_AVATAR_LABEL_34},
{IDR_PROFILE_AVATAR_35, "avatar_origami_pinkbutterfly.png",
IDS_DEFAULT_AVATAR_LABEL_35},
{IDR_PROFILE_AVATAR_36, "avatar_origami_rabbit.png",
IDS_DEFAULT_AVATAR_LABEL_36},
{IDR_PROFILE_AVATAR_37, "avatar_origami_unicorn.png",
IDS_DEFAULT_AVATAR_LABEL_37},
{IDR_PROFILE_AVATAR_38, "avatar_illustration_basketball.png",
IDS_DEFAULT_AVATAR_LABEL_38},
{IDR_PROFILE_AVATAR_39, "avatar_illustration_bike.png",
IDS_DEFAULT_AVATAR_LABEL_39},
{IDR_PROFILE_AVATAR_40, "avatar_illustration_bird.png",
IDS_DEFAULT_AVATAR_LABEL_40},
{IDR_PROFILE_AVATAR_41, "avatar_illustration_cheese.png",
IDS_DEFAULT_AVATAR_LABEL_41},
{IDR_PROFILE_AVATAR_42, "avatar_illustration_football.png",
IDS_DEFAULT_AVATAR_LABEL_42},
{IDR_PROFILE_AVATAR_43, "avatar_illustration_ramen.png",
IDS_DEFAULT_AVATAR_LABEL_43},
{IDR_PROFILE_AVATAR_44, "avatar_illustration_sunglasses.png",
IDS_DEFAULT_AVATAR_LABEL_44},
{IDR_PROFILE_AVATAR_45, "avatar_illustration_sushi.png",
IDS_DEFAULT_AVATAR_LABEL_45},
{IDR_PROFILE_AVATAR_46, "avatar_illustration_tamagotchi.png",
IDS_DEFAULT_AVATAR_LABEL_46},
{IDR_PROFILE_AVATAR_47, "avatar_illustration_vinyl.png",
IDS_DEFAULT_AVATAR_LABEL_47},
{IDR_PROFILE_AVATAR_48, "avatar_abstract_avocado.png",
IDS_DEFAULT_AVATAR_LABEL_48},
{IDR_PROFILE_AVATAR_49, "avatar_abstract_cappuccino.png",
IDS_DEFAULT_AVATAR_LABEL_49},
{IDR_PROFILE_AVATAR_50, "avatar_abstract_icecream.png",
IDS_DEFAULT_AVATAR_LABEL_50},
{IDR_PROFILE_AVATAR_51, "avatar_abstract_icewater.png",
IDS_DEFAULT_AVATAR_LABEL_51},
{IDR_PROFILE_AVATAR_52, "avatar_abstract_melon.png",
IDS_DEFAULT_AVATAR_LABEL_52},
{IDR_PROFILE_AVATAR_53, "avatar_abstract_onigiri.png",
IDS_DEFAULT_AVATAR_LABEL_53},
{IDR_PROFILE_AVATAR_54, "avatar_abstract_pizza.png",
IDS_DEFAULT_AVATAR_LABEL_54},
{IDR_PROFILE_AVATAR_55, "avatar_abstract_sandwich.png",
IDS_DEFAULT_AVATAR_LABEL_55},
#endif
};
return &resource_info[index];
}
gfx::Image GetPlaceholderAvatarIconVisibleAgainstBackground(
SkColor profile_color_seed,
int size,
AvatarVisibilityAgainstBackground visibility) {
CHECK(base::FeatureList::IsEnabled(kOutlineSilhouetteIcon));
const gfx::VectorIcon& person_icon =
vector_icons::kAccountCircleChromeRefreshIcon;
// The palette is generated using the user color, which is independent of the
// profile's light or dark theme.
const ui::TonalPalette color_palette =
ui::GeneratePalette(profile_color_seed,
ui::ColorProviderKey::SchemeVariant::kTonalSpot)
->primary();
const SkColor visible_stroke_color =
visibility == AvatarVisibilityAgainstBackground::kVisibleAgainstDarkTheme
? color_palette.get(kIconToneLight)
: color_palette.get(kIconToneDark);
const gfx::ImageSkia icon_without_background = gfx::CreateVectorIcon(
gfx::IconDescription(person_icon, size, visible_stroke_color));
return gfx::Image(icon_without_background);
}
gfx::Image GetPlaceholderAvatarIconWithColors(
SkColor fill_color,
SkColor stroke_color,
int size,
const PlaceholderAvatarIconParams& icon_params) {
if (!base::FeatureList::IsEnabled(kOutlineSilhouetteIcon)) {
return GetLegacyPlaceholderAvatarIconWithColors(fill_color, stroke_color,
size);
}
// If the icon should be an outline icon visible against the background, use
// `GetPlaceholderAvatarIconVisibleAgainstBackground()` instead.
CHECK(!icon_params.visibility_against_background.has_value());
const gfx::VectorIcon& person_icon =
vector_icons::kAccountCircleChromeRefreshIcon;
const gfx::ImageSkia avatar_icon_without_background =
icon_params.has_padding
? CreatePaddedIcon(person_icon, size, stroke_color, 0.5f)
: gfx::CreateVectorIcon(
gfx::IconDescription(person_icon, size, stroke_color));
if (icon_params.has_background) {
return gfx::Image(
gfx::ImageSkia(std::make_unique<ImageWithBackgroundSource>(
avatar_icon_without_background, fill_color),
gfx::Size(size, size)));
} else {
return gfx::Image(avatar_icon_without_background);
}
}
int GetDefaultAvatarIconResourceIDAtIndex(size_t index) {
return GetDefaultAvatarIconResourceInfo(index)->resource_id;
}
#if BUILDFLAG(IS_WIN)
int GetOldDefaultAvatar2xIconResourceIDAtIndex(size_t index) {
DCHECK_LT(index, std::size(kProfileAvatarIconResources2x));
return kProfileAvatarIconResources2x[index];
}
#endif // BUILDFLAG(IS_WIN)
const char* GetDefaultAvatarIconFileNameAtIndex(size_t index) {
CHECK_NE(index, kPlaceholderAvatarIndex);
return GetDefaultAvatarIconResourceInfo(index)->filename;
}
base::FilePath GetPathOfHighResAvatarAtIndex(size_t index) {
const char* file_name = GetDefaultAvatarIconFileNameAtIndex(index);
base::FilePath user_data_dir;
CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
return user_data_dir.Append(kHighResAvatarFolderName).AppendASCII(file_name);
}
std::string GetDefaultAvatarIconUrl(size_t index) {
#if !BUILDFLAG(IS_ANDROID)
CHECK(IsDefaultAvatarIconIndex(index));
#endif
return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index);
}
int GetDefaultAvatarLabelResourceIDAtIndex(size_t index) {
return GetDefaultAvatarIconResourceInfo(index)->label_id;
}
bool IsDefaultAvatarIconIndex(size_t index) {
return index < kDefaultAvatarIconsCount;
}
bool IsDefaultAvatarIconUrl(const std::string& url, size_t* icon_index) {
DCHECK(icon_index);
if (!base::StartsWith(url, kDefaultUrlPrefix, base::CompareCase::SENSITIVE))
return false;
int int_value = -1;
if (base::StringToInt(base::MakeStringPiece(
url.begin() + strlen(kDefaultUrlPrefix), url.end()),
&int_value)) {
if (int_value < 0 ||
int_value >= static_cast<int>(kDefaultAvatarIconsCount))
return false;
*icon_index = int_value;
return true;
}
return false;
}
base::Value::Dict GetAvatarIconAndLabelDict(const std::string& url,
const std::u16string& label,
size_t index,
bool selected,
bool is_gaia_avatar) {
base::Value::Dict avatar_info;
avatar_info.Set("url", url);
avatar_info.Set("label", label);
avatar_info.Set("index", static_cast<int>(index));
avatar_info.Set("selected", selected);
avatar_info.Set("isGaiaAvatar", is_gaia_avatar);
return avatar_info;
}
base::Value::Dict GetDefaultProfileAvatarIconAndLabel(SkColor fill_color,
SkColor stroke_color,
bool selected) {
gfx::Image icon = profiles::GetPlaceholderAvatarIconWithColors(
fill_color, stroke_color, kAvatarIconSize);
size_t index = profiles::GetPlaceholderAvatarIndex();
return GetAvatarIconAndLabelDict(
webui::GetBitmapDataUrl(icon.AsBitmap()),
l10n_util::GetStringUTF16(
profiles::GetDefaultAvatarLabelResourceIDAtIndex(index)),
index, selected, /*is_gaia_avatar=*/false);
}
base::Value::List GetCustomProfileAvatarIconsAndLabels(
size_t selected_avatar_idx) {
base::Value::List avatars;
for (size_t i = GetModernAvatarIconStartIndex();
i < GetDefaultAvatarIconCount(); ++i) {
avatars.Append(GetAvatarIconAndLabelDict(
profiles::GetDefaultAvatarIconUrl(i),
l10n_util::GetStringUTF16(
profiles::GetDefaultAvatarLabelResourceIDAtIndex(i)),
i, i == selected_avatar_idx, /*is_gaia_avatar=*/false));
}
return avatars;
}
size_t GetRandomAvatarIconIndex(
const std::unordered_set<size_t>& used_icon_indices) {
size_t interval_begin = GetModernAvatarIconStartIndex();
size_t interval_end = GetDefaultAvatarIconCount();
size_t interval_length = interval_end - interval_begin;
size_t random_offset = base::RandInt(0, interval_length - 1);
// Find the next unused index.
for (size_t i = 0; i < interval_length; ++i) {
size_t icon_index = interval_begin + (random_offset + i) % interval_length;
if (used_icon_indices.count(icon_index) == 0u)
return icon_index;
}
// All indices are used, so return a random one.
return interval_begin + random_offset;
}
#if !BUILDFLAG(IS_ANDROID)
base::Value::List GetIconsAndLabelsForProfileAvatarSelector(
const base::FilePath& profile_path) {
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_path);
DCHECK(entry);
bool using_gaia = entry->IsUsingGAIAPicture();
size_t selected_avatar_idx =
using_gaia ? SIZE_MAX : entry->GetAvatarIconIndex();
// Obtain a list of the modern avatar icons.
base::Value::List avatars(
GetCustomProfileAvatarIconsAndLabels(selected_avatar_idx));
if (entry->GetSigninState() == SigninState::kNotSignedIn) {
ProfileThemeColors colors = entry->GetProfileThemeColors();
auto generic_avatar_info = GetDefaultProfileAvatarIconAndLabel(
colors.default_avatar_fill_color, colors.default_avatar_stroke_color,
selected_avatar_idx == GetPlaceholderAvatarIndex());
avatars.Insert(avatars.begin(),
base::Value(std::move(generic_avatar_info)));
return avatars;
}
// Add the GAIA picture to the beginning of the list if it is available.
const gfx::Image* icon = entry->GetGAIAPicture();
if (icon) {
gfx::Image avatar_icon = GetAvatarIconForWebUI(*icon);
auto gaia_picture_info = GetAvatarIconAndLabelDict(
/*url=*/webui::GetBitmapDataUrl(avatar_icon.AsBitmap()),
/*label=*/
l10n_util::GetStringUTF16(IDS_SETTINGS_CHANGE_PICTURE_PROFILE_PHOTO),
/*index=*/0, using_gaia, /*is_gaia_avatar=*/true);
avatars.Insert(avatars.begin(), base::Value(std::move(gaia_picture_info)));
}
return avatars;
}
#endif // !BUILDFLAG(IS_ANDROID)
void SetDefaultProfileAvatarIndex(Profile* profile, size_t avatar_icon_index) {
CHECK(IsDefaultAvatarIconIndex(avatar_icon_index));
PrefService* pref_service = profile->GetPrefs();
pref_service->SetInteger(prefs::kProfileAvatarIndex, avatar_icon_index);
pref_service->SetBoolean(prefs::kProfileUsingDefaultAvatar,
avatar_icon_index == GetPlaceholderAvatarIndex());
pref_service->SetBoolean(prefs::kProfileUsingGAIAAvatar, false);
ProfileMetrics::LogProfileAvatarSelection(avatar_icon_index);
}
#if BUILDFLAG(IS_WIN)
SkBitmap GetWin2xAvatarImage(ProfileAttributesEntry* entry) {
// Request just one size large enough for all uses.
return GetSkBitmapCopy(
entry->GetAvatarIcon(IconUtil::kLargeIconSize, /*use_high_res_file=*/true,
/*icon_params=*/{.has_padding = false}));
}
SkBitmap GetWin2xAvatarIconAsSquare(const SkBitmap& source_bitmap) {
constexpr int kIconScaleFactor = 2;
if ((source_bitmap.width() != kIconScaleFactor * kOldAvatarIconWidth) ||
(source_bitmap.height() != kIconScaleFactor * kOldAvatarIconHeight)) {
// It's not an old avatar icon, the image should be square.
DCHECK_EQ(source_bitmap.width(), source_bitmap.height());
return source_bitmap;
}
// If |source_bitmap| matches the old avatar icon dimensions, i.e. it's an
// old avatar icon, shave a couple of columns so the |source_bitmap| is more
// square. So when resized to a square aspect ratio it looks pretty.
gfx::Rect frame(gfx::SkIRectToRect(source_bitmap.bounds()));
frame.Inset(
gfx::Insets::VH(/*vertical=*/0, /*horizontal=*/kIconScaleFactor * 2));
SkBitmap cropped_bitmap;
source_bitmap.extractSubset(&cropped_bitmap, gfx::RectToSkIRect(frame));
return cropped_bitmap;
}
SkBitmap GetBadgedWinIconBitmapForAvatar(const SkBitmap& app_icon_bitmap,
const SkBitmap& avatar_bitmap) {
// TODO(dfried): This function often doesn't actually do the thing it claims
// to. We should probably fix it.
SkBitmap source_bitmap = profiles::GetWin2xAvatarIconAsSquare(avatar_bitmap);
int avatar_badge_width = kProfileAvatarBadgeSizeWin;
if (app_icon_bitmap.width() != kShortcutIconSizeWin) {
avatar_badge_width = std::ceilf(
app_icon_bitmap.width() *
(float{kProfileAvatarBadgeSizeWin} / float{kShortcutIconSizeWin}));
}
// Resize the avatar image down to the desired badge size, maintaining aspect
// ratio (but prefer more square than rectangular when rounding).
const int avatar_badge_height = base::ClampCeil(
avatar_badge_width *
(static_cast<float>(source_bitmap.height()) / source_bitmap.width()));
SkBitmap sk_icon = skia::ImageOperations::Resize(
source_bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
avatar_badge_width, avatar_badge_height);
// Sanity check - avatars shouldn't be taller than they are wide.
DCHECK_GE(sk_icon.width(), sk_icon.height());
// Overlay the avatar on the icon, anchoring it to the bottom-right of the
// icon on Win 10 and earlier, and the top right on Win 11. Win 11 moved the
// taskbar icon badge to the top right from the bottom right, so profile
// badging needs to move as well, to avoid double badging.
SkBitmap badged_bitmap;
badged_bitmap.allocN32Pixels(app_icon_bitmap.width(),
app_icon_bitmap.height());
SkCanvas offscreen_canvas(badged_bitmap, SkSurfaceProps{});
offscreen_canvas.clear(SK_ColorTRANSPARENT);
offscreen_canvas.drawImage(app_icon_bitmap.asImage(), 0, 0);
// Render the avatar in a cutout circle. If the avatar is not square, center
// it in the circle but favor pushing it further down.
const int cutout_size = avatar_badge_width;
const int cutout_left = app_icon_bitmap.width() - cutout_size;
const int cutout_top = base::win::GetVersion() >= base::win::Version::WIN11
? 0
: app_icon_bitmap.height() - cutout_size;
const int icon_left = cutout_left;
const int icon_top =
cutout_top + base::ClampCeil((cutout_size - avatar_badge_height) / 2.0f);
const SkRRect clip_circle = SkRRect::MakeOval(
SkRect::MakeXYWH(cutout_left, cutout_top, cutout_size, cutout_size));
offscreen_canvas.clipRRect(clip_circle, true);
offscreen_canvas.drawImage(sk_icon.asImage(), icon_left, icon_top);
return badged_bitmap;
}
#endif // BUILDFLAG(IS_WIN)
} // namespace profiles