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

#include "chrome/browser/profiles/profile_avatar_icon_util.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>

#include "base/feature_list.h"
#include "base/format_macros.h"
#include "base/numerics/angle_conversions.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 "cc/paint/paint_flags.h"
#include "cc/paint/paint_shader.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/browser/ui/views/dotted_icon.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/browser_resources.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/abseil-cpp/absl/container/flat_hash_set.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPathBuilder.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.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.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/40147906
#include "ui/gfx/win/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;

// Values are expressed as a proportion of resulting image size.
struct AvatarWithDottedRingParams {
  // Radius of the dotted ring.
  float dotted_ring_radius = 0;
  // The stroke is fully inside the ring.
  float ring_stroke_width = 0;
  // Padding around the image to fit it inside the ring.
  float image_padding = 0;
};

constexpr AvatarWithDottedRingParams kAvatarWithDottedRingParamsWithPadding = {
    .dotted_ring_radius = 0.29,
    .ring_stroke_width = 0.05,
    .image_padding = 0.3,
};

constexpr AvatarWithDottedRingParams kAvatarWithDottedRingParamsNoPadding = {
    .dotted_ring_radius = 0.5,
    .ring_stroke_width = 0.075,
    .image_padding = 0.13,
};

// Sanity check: the dotted ring is smaller than the full image.
static_assert(kAvatarWithDottedRingParamsWithPadding.dotted_ring_radius <= 0.5);
static_assert(kAvatarWithDottedRingParamsNoPadding.dotted_ring_radius <= 0.5);

// Sanity check: the avatar image fits inside the dotted ring (taking the ring
// stroke width into account).
static_assert(kAvatarWithDottedRingParamsWithPadding.dotted_ring_radius >
              0.5 - kAvatarWithDottedRingParamsWithPadding.image_padding +
                  kAvatarWithDottedRingParamsWithPadding.ring_stroke_width);
static_assert(kAvatarWithDottedRingParamsNoPadding.dotted_ring_radius >
              0.5 - kAvatarWithDottedRingParamsNoPadding.image_padding +
                  kAvatarWithDottedRingParamsNoPadding.ring_stroke_width);

#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.
constexpr auto kProfileAvatarIconResources2x = std::to_array<int>({
    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() = default;

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.
    const SkPath circular_mask =
        SkPath::Circle(canvas_size_.width() / 2, canvas_size_.height() / 2,
                       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_;
};

#if !BUILDFLAG(IS_ANDROID)
struct AvatarRingGeometry {
  AvatarRingGeometry(int avatar_size, int gap_width, int ring_thickness)
      : total_size(avatar_size + 2 * (gap_width + ring_thickness)),
        avatar_size(avatar_size),
        gap_width(gap_width),
        ring_thickness(ring_thickness),
        inner_radius(avatar_size / 2 + gap_width),
        outer_radius(inner_radius + ring_thickness) {}

  const int total_size;
  const int avatar_size;
  const int gap_width;
  const int ring_thickness;
  const int inner_radius;
  const int outer_radius;
};

SkPath GetAvatarRingPath(const AvatarRingGeometry& geom,
                         const gfx::PointF& center) {
  SkPathBuilder path_builder;
  path_builder.setFillType(SkPathFillType::kEvenOdd);
  path_builder.addCircle(center.x(), center.y(), geom.outer_radius);
  path_builder.addCircle(center.x(), center.y(), geom.inner_radius);
  return path_builder.detach();
}

class ImageWithDottedCircleSource : public gfx::CanvasImageSource {
 public:
  ImageWithDottedCircleSource(const gfx::ImageSkia& image,
                              int ring_radius,
                              float ring_stroke_width,
                              SkColor ring_color)
      : gfx::CanvasImageSource(image.size()),
        image_(image),
        ring_size_(2 * ring_radius),
        ring_stroke_width_(ring_stroke_width),
        ring_color_(ring_color) {}

  ImageWithDottedCircleSource(const ImageWithDottedCircleSource&) = delete;
  ImageWithDottedCircleSource& operator=(const ImageWithDottedCircleSource&) =
      delete;

  ~ImageWithDottedCircleSource() override = default;

  // gfx::CanvasImageSource override.
  void Draw(gfx::Canvas* canvas) override {
    canvas->DrawImageInt(image_, 0, 0);
    float padding = (image_.width() - ring_size_) / 2;
    PaintRingDottedPath(canvas,
                        gfx::Rect(padding, padding, ring_size_, ring_size_),
                        ring_color_,
                        /*opacity_ratio=*/1,
                        /*stroke_width=*/ring_stroke_width_);
  }

 private:
  const gfx::ImageSkia image_;
  const int ring_size_;
  const float ring_stroke_width_;
  const SkColor ring_color_;
};

sk_sp<cc::PaintShader> CreateLinearGradientRingShader(
    int size,
    SkColor start_color,
    SkColor end_color,
    base::span<const float, 4> positions,
    base::span<const float, 2> p1_normalized,
    base::span<const float, 2> p2_normalized) {
  const float size_f = static_cast<float>(size);
  SkPoint points[2] = {
      SkPoint::Make(size_f * p1_normalized[0], size_f * p1_normalized[1]),
      SkPoint::Make(size_f * p2_normalized[0], size_f * p2_normalized[1])};

  SkColor4f colors[4] = {
      SkColor4f::FromColor(start_color), SkColor4f::FromColor(start_color),
      SkColor4f::FromColor(end_color), SkColor4f::FromColor(end_color)};

  return cc::PaintShader::MakeLinearGradient(points, colors, positions.data(),
                                             /*count=*/4, SkTileMode::kClamp);
}

class AvatarWithProjectedRingSource : public gfx::CanvasImageSource {
 public:
  AvatarWithProjectedRingSource(const gfx::ImageSkia& avatar,
                                const AvatarRingGeometry& geom,
                                sk_sp<cc::PaintShader> shader)
      : gfx::CanvasImageSource(gfx::Size(geom.total_size, geom.total_size)),
        avatar_(avatar),
        geom_(geom),
        shader_(std::move(shader)) {}

  AvatarWithProjectedRingSource(const AvatarWithProjectedRingSource&) = delete;
  AvatarWithProjectedRingSource& operator=(
      const AvatarWithProjectedRingSource&) = delete;
  ~AvatarWithProjectedRingSource() override = default;

  void Draw(gfx::Canvas* canvas) override {
    const float center_x = size().width() / 2.0f;
    const float center_y = size().height() / 2.0f;
    gfx::PointF center(center_x, center_y);

    // Save the current canvas state (matrix and clip) to restore it later.
    canvas->Save();

    // 1. Clip to the ring shape.
    // Subsequent drawing is restricted to the area inside this path.
    SkPath ring_path = GetAvatarRingPath(geom_, center);
    canvas->ClipPath(ring_path, true);

    // 2. Draw the gradient.
    cc::PaintFlags flags;
    flags.setAntiAlias(true);
    flags.setStyle(cc::PaintFlags::kFill_Style);
    flags.setShader(shader_);

    canvas->DrawRect(gfx::RectF(size().width(), size().height()), flags);
    // Restore the saved canvas state, removing the clip path so we can draw the
    // avatar and gap on the rest of the canvas.
    canvas->Restore();

    // 3. Explicitly clear the gap area to ensure it's transparent.
    cc::PaintFlags clear_flags;
    clear_flags.setAntiAlias(true);
    clear_flags.setBlendMode(SkBlendMode::kClear);
    canvas->DrawCircle(/*center_point=*/center,
                       /*radius=*/geom_.inner_radius, clear_flags);

    // 4. Draw the user avatar.
    int offset = geom_.gap_width + geom_.ring_thickness;
    canvas->DrawImageInt(avatar_, /*x=*/offset, /*y=*/offset);
  }

 private:
  const gfx::ImageSkia avatar_;
  const AvatarRingGeometry geom_;
  const sk_sp<cc::PaintShader> shader_;
};
#endif  // !BUILDFLAG(IS_ANDROID)

// 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));
}

class CircleImageSource : public gfx::CanvasImageSource {
 public:
  CircleImageSource(int size, SkColor color)
      : gfx::CanvasImageSource(gfx::Size(size, size)), color_(color) {}

  CircleImageSource(const CircleImageSource&) = delete;
  CircleImageSource& operator=(const CircleImageSource&) = delete;

  ~CircleImageSource() override = default;

  void Draw(gfx::Canvas* canvas) override {
    float radius = size().width() / 2.0f;
    cc::PaintFlags flags;
    flags.setStyle(cc::PaintFlags::kFill_Style);
    flags.setAntiAlias(true);
    flags.setColor(color_);
    canvas->DrawCircle(gfx::PointF(radius, radius), radius, flags);
  }

  static gfx::ImageSkia CropCircle(const gfx::ImageSkia& image) {
    CHECK_EQ(image.width(), image.height());
    // The color here is irrelevant as long as it's opaque; only alpha matters.
    return gfx::ImageSkiaOperations::CreateMaskedImage(
        image, gfx::CanvasImageSource::MakeImageSkia<CircleImageSource>(
                   image.width(), SK_ColorWHITE));
  }

 private:
  const SkColor color_;
};

// CanvasImageSource that combines a background image with user's avatar,
// the avatar is positioned and resized in terms of the background image DIPs,
// it also is cropped in a circle.
class AvatarEmbeddedImageSource : public gfx::CanvasImageSource {
 public:
  AvatarEmbeddedImageSource(const gfx::Image& image,
                            const gfx::Image& avatar,
                            const gfx::Point& avatar_position,
                            size_t avatar_size)
      : gfx::CanvasImageSource(image.Size()),
        image_(image),
        avatar_(avatar),
        avatar_position_(avatar_position),
        avatar_size_(avatar_size) {
    CHECK(!image_.IsEmpty());
  }

  AvatarEmbeddedImageSource(const AvatarEmbeddedImageSource&) = delete;
  AvatarEmbeddedImageSource& operator=(const AvatarEmbeddedImageSource&) =
      delete;

  ~AvatarEmbeddedImageSource() override = default;

  // gfx::CanvasImageSource:
  void Draw(gfx::Canvas* canvas) override {
    // Draw the background image first.
    canvas->DrawImageInt(image_.AsImageSkia(), 0, 0);

    // Setting a clippath makes subsequent avatar drawing cropped in a circle.
    const SkPath avatar_bound = SkPath::Oval(
        SkRect::MakeXYWH(avatar_position_.x(), avatar_position_.y(),
                         /*w=*/avatar_size_, /*h=*/avatar_size_));
    canvas->ClipPath(avatar_bound, /*do_anti_alias=*/true);

    // Finally draw the avatar, above the background and cropped.
    // Note that some testing profiles do not have an avatar.
    if (!avatar_.IsEmpty()) {
      gfx::ImageSkia avatar = gfx::ImageSkiaOperations::CreateResizedImage(
          avatar_.AsImageSkia(),
          skia::ImageOperations::ResizeMethod::RESIZE_BEST,
          gfx::Size(avatar_size_, avatar_size_));
      canvas->DrawImageInt(avatar, avatar_position_.x(), avatar_position_.y());
    }
  }

 private:
  const gfx::Image image_;
  const gfx::Image avatar_;
  const gfx::Point avatar_position_;
  const size_t avatar_size_;
};

}  // 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 std::string_view 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)
constexpr size_t kDefaultAvatarIconsCount = 27;
#else
constexpr size_t kDefaultAvatarIconsCount = 56;
#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) {
  // Guest profiles generally use the default theme, no need to go through the
  // `ThemeService`.
  return ui::ImageModel::FromVectorIcon(
      features::IsRoundedIconsEnabled() ? kAccountBoxIcon : kAccountBoxOldIcon,
      ui::kColorSysPrimary, size);
}

gfx::Image GetSizedAvatarIcon(const gfx::Image& image,
                              int width,
                              int height,
                              AvatarShape shape) {
  gfx::Size size(width, height);

  // No need to resize.
  if (image.Size() == size) {
    switch (shape) {
      case AvatarShape::SHAPE_CIRCLE:
        if (width == height) {
          return gfx::Image(
              CircleImageSource::CropCircle(*image.ToImageSkia()));
        }
        break;
      case AvatarShape::SHAPE_SQUARE:
        return image;
    }
  }

  // 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);
}

ui::ImageModel GetSizedAvatarImageModel(const ui::ImageModel& image, int size) {
  DCHECK(!image.IsImageGenerator());  // Not prepared to handle these.
  if (image.Size() == gfx::Size(size, size)) {
    return image;
  }
  if (image.IsImage()) {
    gfx::ImageSkia image_skia = image.GetImage().AsImageSkia();
    return ui::ImageModel::FromImageSkia(
        gfx::ImageSkiaOperations::CreateResizedImage(
            image_skia, skia::ImageOperations::RESIZE_BEST,
            gfx::Size(size, size)));
  }
  const ui::VectorIconModel& model = image.GetVectorIcon();
  return ui::ImageModel::FromVectorIcon(*model.vector_icon(), model.color(),
                                        size);
}

#if !BUILDFLAG(IS_ANDROID)
gfx::ImageSkia GetAvatarWithDottedRing(
    const ui::ImageModel& image,
    int size,
    bool has_padding,
    bool has_background,
    const ui::ColorProvider& color_provider) {
  DCHECK(!image.IsEmpty());

  const AvatarWithDottedRingParams& params =
      has_padding ? kAvatarWithDottedRingParamsWithPadding
                  : kAvatarWithDottedRingParamsNoPadding;

  const int avatar_padding = std::nearbyint(params.image_padding * size);
  const int avatar_ring_radius =
      std::nearbyint(params.dotted_ring_radius * size);
  const int avatar_size = size - 2 * avatar_padding;
  const float avatar_ring_stroke = params.ring_stroke_width * size;

  // Shrink the avatar to fit inside the dotted ring.
  gfx::ImageSkia sized_avatar_image =
      GetSizedAvatarImageModel(image, avatar_size).Rasterize(&color_provider);
  // Crop to a circle.
  sized_avatar_image = CircleImageSource::CropCircle(sized_avatar_image);
  // Add padding.
  gfx::ImageSkia padded_image = gfx::CanvasImageSource::CreatePadded(
      sized_avatar_image, gfx::Insets(avatar_padding));
  // Add background color.
  if (has_background) {
    padded_image = AddBackgroundToImage(
        padded_image, color_provider.GetColor(ui::kColorBubbleBackground));
  }
  // Add dotted ring.
  return gfx::ImageSkia(
      std::make_unique<ImageWithDottedCircleSource>(
          padded_image, avatar_ring_radius, avatar_ring_stroke,
          color_provider.GetColor(ui::kColorSysStateInactiveRing)),
      gfx::Size(size, size));
}
#endif  // !BUILDFLAG(IS_ANDROID)

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();
  }

  // TODO(pkasting): This should use a `ColorProvider` instead.
  const bool dark_mode =
      ui::NativeTheme::GetInstanceForNativeUi()->preferred_color_scheme() ==
      ui::NativeTheme::PreferredColorScheme::kDark;
  const SkColor bg_color = dark_mode ? SK_ColorBLACK : SK_ColorWHITE;
  PlaceholderAvatarIconParams icon_params =
      GetPlaceholderAvatarIconParamsVisibleAgainstColor(bg_color);
  // 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);
  constexpr int kMenuAvatarIconSizeForNSMenu = 20;
  return profiles::GetSizedAvatarIcon(icon, kMenuAvatarIconSizeForNSMenu,
                                      kMenuAvatarIconSizeForNSMenu,
                                      profiles::SHAPE_CIRCLE);
}
#endif

// Helper methods for accessing, transforming and drawing avatar icons.
size_t GetDefaultAvatarIconCount() {
  return kDefaultAvatarIconsCount;
}

size_t GetPlaceholderAvatarIndex() {
  return kPlaceholderAvatarIndex;
}

size_t GetModernAvatarIconStartIndex() {
#if !BUILDFLAG(IS_CHROMEOS) && !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 std::array<IconResourceInfo, kDefaultAvatarIconsCount>
      resource_info = {{
  // 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) && !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) {
  const gfx::VectorIcon& person_icon =
      features::IsRoundedIconsEnabled()
          ? kAccountCircleIcon
          : vector_icons::kAccountCircleChromeRefreshOldIcon;

  // 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 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 =
      features::IsRoundedIconsEnabled()
          ? kAccountCircleIcon
          : vector_icons::kAccountCircleChromeRefreshOldIcon;

  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(
        AddBackgroundToImage(avatar_icon_without_background, fill_color));
  } 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) {
  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;
}

size_t GetSanitizedAvatarIndex(int icon_index) {
  if (icon_index < 0 ||
      !IsDefaultAvatarIconIndex(static_cast<size_t>(icon_index))) {
    return GetPlaceholderAvatarIndex();
  }
  return static_cast<size_t>(icon_index);
}

bool IsDefaultAvatarIconUrl(std::string_view 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(url.substr(kDefaultUrlPrefix.size()), &int_value)) {
    if (int_value < 0 ||
        int_value >= static_cast<int>(kDefaultAvatarIconsCount))
      return false;
    *icon_index = int_value;
    return true;
  }

  return false;
}

base::DictValue GetAvatarIconAndLabelDict(const std::string& url,
                                          const std::u16string& label,
                                          size_t index,
                                          bool selected,
                                          bool is_gaia_avatar) {
  base::DictValue 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::DictValue 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::ListValue GetCustomProfileAvatarIconsAndLabels(
    size_t selected_avatar_idx) {
  base::ListValue 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 absl::flat_hash_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::RandIntInclusive(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.contains(icon_index)) {
      return icon_index;
    }
  }
  // All indices are used, so return a random one.
  return interval_begin + random_offset;
}

#if !BUILDFLAG(IS_ANDROID)
base::ListValue 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::ListValue 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)

gfx::ImageSkia AddBackgroundToImage(const gfx::ImageSkia& image,
                                    SkColor background_color) {
  return gfx::ImageSkia(
      std::make_unique<ImageWithBackgroundSource>(image, background_color),
      image.size());
}

ui::ImageModel EmbedAvatarOntoImage(int resource_id,
                                    const gfx::Image& avatar,
                                    const gfx::Point& avatar_position,
                                    size_t avatar_size) {
  return ui::ImageModel::FromImageSkia(
      gfx::CanvasImageSource::MakeImageSkia<AvatarEmbeddedImageSource>(
          ui::ResourceBundle::GetSharedInstance().GetImageNamed(resource_id),
          avatar, avatar_position, avatar_size));
}

#if !BUILDFLAG(IS_ANDROID)
gfx::ImageSkia AddLinearGradientRingToAvatar(
    const ui::ImageModel& avatar_image,
    const ui::ColorProvider& color_provider,
    SkColor start_color,
    SkColor end_color,
    base::span<const float, 4> positions,
    base::span<const float, 2> p1_normalized,
    base::span<const float, 2> p2_normalized,
    int avatar_size,
    int gap_width,
    int ring_thickness) {
  DCHECK(!avatar_image.IsEmpty());

  AvatarRingGeometry geom(avatar_size, gap_width, ring_thickness);

  gfx::ImageSkia sized_avatar_image =
      GetSizedAvatarImageModel(avatar_image, geom.avatar_size)
          .Rasterize(&color_provider);
  sized_avatar_image = CircleImageSource::CropCircle(sized_avatar_image);

  auto shader =
      CreateLinearGradientRingShader(geom.total_size, start_color, end_color,
                                     positions, p1_normalized, p2_normalized);

  return gfx::ImageSkia(std::make_unique<AvatarWithProjectedRingSource>(
                            sized_avatar_image, geom, std::move(shader)),
                        gfx::Size(geom.total_size, geom.total_size));
}
#endif  // !BUILDFLAG(IS_ANDROID)

}  // namespace profiles
