1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/feature_list.h"
13 #include "base/files/file_util.h"
14 #include "base/format_macros.h"
15 #include "base/macros.h"
16 #include "base/path_service.h"
17 #include "base/rand_util.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/values.h"
22 #include "build/build_config.h"
23 #include "cc/paint/paint_flags.h"
24 #include "chrome/app/vector_icons/vector_icons.h"
25 #include "chrome/browser/browser_process.h"
26 #include "chrome/browser/profiles/avatar_menu.h"
27 #include "chrome/browser/ui/ui_features.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "chrome/grit/generated_resources.h"
30 #include "chrome/grit/theme_resources.h"
31 #include "skia/ext/image_operations.h"
32 #include "third_party/skia/include/core/SkPaint.h"
33 #include "third_party/skia/include/core/SkPath.h"
34 #include "third_party/skia/include/core/SkScalar.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "ui/base/webui/web_ui_util.h"
38 #include "ui/gfx/canvas.h"
39 #include "ui/gfx/color_palette.h"
40 #include "ui/gfx/favicon_size.h"
41 #include "ui/gfx/geometry/rect.h"
42 #include "ui/gfx/image/canvas_image_source.h"
43 #include "ui/gfx/image/image.h"
44 #include "ui/gfx/image/image_skia_operations.h"
45 #include "ui/gfx/paint_vector_icon.h"
46 #include "ui/gfx/skia_util.h"
47 #include "ui/native_theme/native_theme.h"
48 #include "url/url_canon.h"
49 
50 #if defined(OS_WIN)
51 #include "chrome/browser/profiles/profile_attributes_entry.h"
52 #include "chrome/grit/chrome_unscaled_resources.h"  // nogncheck crbug.com/1125897
53 #include "ui/gfx/icon_util.h"  // For Iconutil::kLargeIconSize.
54 #endif
55 
56 #if defined(OS_MAC)
57 #include "chrome/browser/profiles/profile_attributes_entry.h"
58 #include "chrome/browser/profiles/profile_attributes_storage.h"
59 #include "chrome/browser/profiles/profile_manager.h"
60 #endif
61 
62 // Helper methods for transforming and drawing avatar icons.
63 namespace {
64 
65 #if defined(OS_WIN)
66 const int kOldAvatarIconWidth = 38;
67 const int kOldAvatarIconHeight = 31;
68 
69 // 2x sized versions of the old profile avatar icons.
70 // TODO(crbug.com/937834): Clean this up.
71 const int kProfileAvatarIconResources2x[] = {
72     IDR_PROFILE_AVATAR_2X_0,  IDR_PROFILE_AVATAR_2X_1,
73     IDR_PROFILE_AVATAR_2X_2,  IDR_PROFILE_AVATAR_2X_3,
74     IDR_PROFILE_AVATAR_2X_4,  IDR_PROFILE_AVATAR_2X_5,
75     IDR_PROFILE_AVATAR_2X_6,  IDR_PROFILE_AVATAR_2X_7,
76     IDR_PROFILE_AVATAR_2X_8,  IDR_PROFILE_AVATAR_2X_9,
77     IDR_PROFILE_AVATAR_2X_10, IDR_PROFILE_AVATAR_2X_11,
78     IDR_PROFILE_AVATAR_2X_12, IDR_PROFILE_AVATAR_2X_13,
79     IDR_PROFILE_AVATAR_2X_14, IDR_PROFILE_AVATAR_2X_15,
80     IDR_PROFILE_AVATAR_2X_16, IDR_PROFILE_AVATAR_2X_17,
81     IDR_PROFILE_AVATAR_2X_18, IDR_PROFILE_AVATAR_2X_19,
82     IDR_PROFILE_AVATAR_2X_20, IDR_PROFILE_AVATAR_2X_21,
83     IDR_PROFILE_AVATAR_2X_22, IDR_PROFILE_AVATAR_2X_23,
84     IDR_PROFILE_AVATAR_2X_24, IDR_PROFILE_AVATAR_2X_25,
85     IDR_PROFILE_AVATAR_2X_26,
86 };
87 
88 // Returns a copied SkBitmap for the given image that can be safely passed to
89 // another thread.
GetSkBitmapCopy(const gfx::Image & image)90 SkBitmap GetSkBitmapCopy(const gfx::Image& image) {
91   DCHECK(!image.IsEmpty());
92   const SkBitmap* image_bitmap = image.ToSkBitmap();
93   SkBitmap bitmap_copy;
94   if (bitmap_copy.tryAllocPixels(image_bitmap->info()))
95     image_bitmap->readPixels(bitmap_copy.info(), bitmap_copy.getPixels(),
96                              bitmap_copy.rowBytes(), 0, 0);
97   return bitmap_copy;
98 }
99 #endif  // OS_WIN
100 
101 // Determine what the scaled height of the avatar icon should be for a
102 // specified width, to preserve the aspect ratio.
GetScaledAvatarHeightForWidth(int width,const gfx::ImageSkia & avatar)103 int GetScaledAvatarHeightForWidth(int width, const gfx::ImageSkia& avatar) {
104   // Multiply the width by the inverted aspect ratio (height over
105   // width), and then add 0.5 to ensure the int truncation rounds nicely.
106   int scaled_height = width *
107       ((float) avatar.height() / (float) avatar.width()) + 0.5f;
108   return scaled_height;
109 }
110 
111 // A CanvasImageSource that draws a sized and positioned avatar with an
112 // optional border independently of the scale factor.
113 class AvatarImageSource : public gfx::CanvasImageSource {
114  public:
115   enum AvatarPosition {
116     POSITION_CENTER,
117     POSITION_BOTTOM_CENTER,
118   };
119 
120   enum AvatarBorder {
121     BORDER_NONE,
122     BORDER_NORMAL,
123     BORDER_ETCHED,
124   };
125 
126   AvatarImageSource(gfx::ImageSkia avatar,
127                     const gfx::Size& canvas_size,
128                     int width,
129                     AvatarPosition position,
130                     AvatarBorder border,
131                     profiles::AvatarShape shape);
132 
133   AvatarImageSource(gfx::ImageSkia avatar,
134                     const gfx::Size& canvas_size,
135                     int width,
136                     AvatarPosition position,
137                     AvatarBorder border);
138 
139   ~AvatarImageSource() override;
140 
141   // CanvasImageSource override:
142   void Draw(gfx::Canvas* canvas) override;
143 
144  private:
145   gfx::ImageSkia avatar_;
146   const gfx::Size canvas_size_;
147   const int width_;
148   const int height_;
149   const AvatarPosition position_;
150   const AvatarBorder border_;
151   const profiles::AvatarShape shape_;
152 
153   DISALLOW_COPY_AND_ASSIGN(AvatarImageSource);
154 };
155 
AvatarImageSource(gfx::ImageSkia avatar,const gfx::Size & canvas_size,int width,AvatarPosition position,AvatarBorder border,profiles::AvatarShape shape)156 AvatarImageSource::AvatarImageSource(gfx::ImageSkia avatar,
157                                      const gfx::Size& canvas_size,
158                                      int width,
159                                      AvatarPosition position,
160                                      AvatarBorder border,
161                                      profiles::AvatarShape shape)
162     : gfx::CanvasImageSource(canvas_size),
163       canvas_size_(canvas_size),
164       width_(width),
165       height_(GetScaledAvatarHeightForWidth(width, avatar)),
166       position_(position),
167       border_(border),
168       shape_(shape) {
169   avatar_ = gfx::ImageSkiaOperations::CreateResizedImage(
170       avatar, skia::ImageOperations::RESIZE_BEST,
171       gfx::Size(width_, height_));
172 }
173 
AvatarImageSource(gfx::ImageSkia avatar,const gfx::Size & canvas_size,int width,AvatarPosition position,AvatarBorder border)174 AvatarImageSource::AvatarImageSource(gfx::ImageSkia avatar,
175                                      const gfx::Size& canvas_size,
176                                      int width,
177                                      AvatarPosition position,
178                                      AvatarBorder border)
179     : AvatarImageSource(avatar,
180                         canvas_size,
181                         width,
182                         position,
183                         border,
184                         profiles::SHAPE_SQUARE) {}
185 
~AvatarImageSource()186 AvatarImageSource::~AvatarImageSource() {
187 }
188 
Draw(gfx::Canvas * canvas)189 void AvatarImageSource::Draw(gfx::Canvas* canvas) {
190   // Center the avatar horizontally.
191   int x = (canvas_size_.width() - width_) / 2;
192   int y;
193 
194   if (position_ == POSITION_CENTER) {
195     // Draw the avatar centered on the canvas.
196     y = (canvas_size_.height() - height_) / 2;
197   } else {
198     // Draw the avatar on the bottom center of the canvas, leaving 1px below.
199     y = canvas_size_.height() - height_ - 1;
200   }
201 
202 #if defined(OS_ANDROID)
203   // Circular shape is only available on desktop platforms.
204   DCHECK(shape_ != profiles::SHAPE_CIRCLE);
205 #else
206   if (shape_ == profiles::SHAPE_CIRCLE) {
207     // Draw the avatar on the bottom center of the canvas; overrides the
208     // previous position specification to avoid leaving visible gap below the
209     // avatar.
210     y = canvas_size_.height() - height_;
211 
212     // Calculate the circular mask that will be used to display the avatar
213     // image.
214     SkPath circular_mask;
215     circular_mask.addCircle(SkIntToScalar(canvas_size_.width() / 2),
216                             SkIntToScalar(canvas_size_.height() / 2),
217                             SkIntToScalar(canvas_size_.width() / 2));
218     canvas->ClipPath(circular_mask, true);
219   }
220 #endif
221 
222   canvas->DrawImageInt(avatar_, x, y);
223 
224   // The border should be square.
225   int border_size = std::max(width_, height_);
226   // Reset the x and y for the square border.
227   x = (canvas_size_.width() - border_size) / 2;
228   y = (canvas_size_.height() - border_size) / 2;
229 
230   if (border_ == BORDER_NORMAL) {
231     // Draw a gray border on the inside of the avatar.
232     SkColor border_color = SkColorSetARGB(83, 0, 0, 0);
233 
234     // Offset the rectangle by a half pixel so the border is drawn within the
235     // appropriate pixels no matter the scale factor. Subtract 1 from the right
236     // and bottom sizes to specify the endpoints, yielding -0.5.
237     SkPath path;
238     path.addRect(SkFloatToScalar(x + 0.5f),  // left
239                  SkFloatToScalar(y + 0.5f),  // top
240                  SkFloatToScalar(x + border_size - 0.5f),   // right
241                  SkFloatToScalar(y + border_size - 0.5f));  // bottom
242 
243     cc::PaintFlags flags;
244     flags.setColor(border_color);
245     flags.setStyle(cc::PaintFlags::kStroke_Style);
246     flags.setStrokeWidth(SkIntToScalar(1));
247 
248     canvas->DrawPath(path, flags);
249   } else if (border_ == BORDER_ETCHED) {
250     // Give the avatar an etched look by drawing a highlight on the bottom and
251     // right edges.
252     SkColor shadow_color = SkColorSetARGB(83, 0, 0, 0);
253     SkColor highlight_color = SkColorSetARGB(96, 255, 255, 255);
254 
255     cc::PaintFlags flags;
256     flags.setStyle(cc::PaintFlags::kStroke_Style);
257     flags.setStrokeWidth(SkIntToScalar(1));
258 
259     SkPath path;
260 
261     // Left and top shadows. To support higher scale factors than 1, position
262     // the orthogonal dimension of each line on the half-pixel to separate the
263     // pixel. For a vertical line, this means adding 0.5 to the x-value.
264     path.moveTo(SkFloatToScalar(x + 0.5f), SkIntToScalar(y + height_));
265 
266     // Draw up to the top-left. Stop with the y-value at a half-pixel.
267     path.rLineTo(SkIntToScalar(0), SkFloatToScalar(-height_ + 0.5f));
268 
269     // Draw right to the top-right, stopping within the last pixel.
270     path.rLineTo(SkFloatToScalar(width_ - 0.5f), SkIntToScalar(0));
271 
272     flags.setColor(shadow_color);
273     canvas->DrawPath(path, flags);
274 
275     path.reset();
276 
277     // Bottom and right highlights. Note that the shadows own the shared corner
278     // pixels, so reduce the sizes accordingly.
279     path.moveTo(SkIntToScalar(x + 1), SkFloatToScalar(y + height_ - 0.5f));
280 
281     // Draw right to the bottom-right.
282     path.rLineTo(SkFloatToScalar(width_ - 1.5f), SkIntToScalar(0));
283 
284     // Draw up to the top-right.
285     path.rLineTo(SkIntToScalar(0), SkFloatToScalar(-height_ + 1.5f));
286 
287     flags.setColor(highlight_color);
288     canvas->DrawPath(path, flags);
289   }
290 }
291 
292 class ImageWithBackgroundSource : public gfx::CanvasImageSource {
293  public:
ImageWithBackgroundSource(const gfx::ImageSkia & image,SkColor background)294   ImageWithBackgroundSource(const gfx::ImageSkia& image, SkColor background)
295       : gfx::CanvasImageSource(image.size()),
296         image_(image),
297         background_(background) {}
298 
299   ~ImageWithBackgroundSource() override = default;
300 
301   // gfx::CanvasImageSource override.
Draw(gfx::Canvas * canvas)302   void Draw(gfx::Canvas* canvas) override {
303     canvas->DrawColor(background_);
304     canvas->DrawImageInt(image_, 0, 0);
305   }
306 
307  private:
308   const gfx::ImageSkia image_;
309   const SkColor background_;
310 
311   DISALLOW_COPY_AND_ASSIGN(ImageWithBackgroundSource);
312 };
313 
314 }  // namespace
315 
316 namespace profiles {
317 
318 struct IconResourceInfo {
319   int resource_id;
320   const char* filename;
321   int label_id;
322 };
323 
324 constexpr int kAvatarIconSize = 96;
325 constexpr SkColor kAvatarTutorialBackgroundColor =
326     SkColorSetRGB(0x42, 0x85, 0xf4);
327 constexpr SkColor kAvatarTutorialContentTextColor =
328     SkColorSetRGB(0xc6, 0xda, 0xfc);
329 constexpr SkColor kAvatarBubbleAccountsBackgroundColor =
330     SkColorSetRGB(0xf3, 0xf3, 0xf3);
331 constexpr SkColor kAvatarBubbleGaiaBackgroundColor =
332     SkColorSetRGB(0xf5, 0xf5, 0xf5);
333 constexpr SkColor kUserManagerBackgroundColor = SkColorSetRGB(0xee, 0xee, 0xee);
334 
335 constexpr char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
336 constexpr base::FilePath::CharType kGAIAPictureFileName[] =
337     FILE_PATH_LITERAL("Google Profile Picture.png");
338 constexpr base::FilePath::CharType kHighResAvatarFolderName[] =
339     FILE_PATH_LITERAL("Avatars");
340 
341 // The size of the function-static kDefaultAvatarIconResources array below.
342 #if defined(OS_ANDROID)
343 constexpr size_t kDefaultAvatarIconsCount = 1;
344 #elif defined(OS_CHROMEOS)
345 constexpr size_t kDefaultAvatarIconsCount = 27;
346 #else
347 constexpr size_t kDefaultAvatarIconsCount = 56;
348 #endif
349 
350 #if !defined(OS_ANDROID)
351 // The first 8 icons are generic.
352 constexpr size_t kGenericAvatarIconsCount = 8;
353 #else
354 constexpr size_t kGenericAvatarIconsCount = 0;
355 #endif
356 
357 #if !defined(OS_ANDROID)
358 // The avatar used as a placeholder.
359 constexpr size_t kPlaceholderAvatarIndex = 26;
360 #else
361 constexpr size_t kPlaceholderAvatarIndex = 0;
362 #endif
363 
GetGuestAvatar(int size)364 ui::ImageModel GetGuestAvatar(int size) {
365   if (base::FeatureList::IsEnabled(features::kNewProfilePicker)) {
366     return ui::ImageModel::FromVectorIcon(
367         kUserAccountAvatarIcon, ui::NativeTheme::kColorId_AvatarIconGuest,
368         size);
369   }
370   return ui::ImageModel::FromVectorIcon(kUserAccountAvatarIcon,
371                                         gfx::kGoogleGrey500, size);
372 }
373 
GetSizedAvatarIcon(const gfx::Image & image,bool is_rectangle,int width,int height,AvatarShape shape)374 gfx::Image GetSizedAvatarIcon(const gfx::Image& image,
375                               bool is_rectangle,
376                               int width,
377                               int height,
378                               AvatarShape shape) {
379   if (!is_rectangle && image.Height() <= height)
380     return image;
381 
382   gfx::Size size(width, height);
383 
384   // Source for a centered, sized icon. GAIA images get a border.
385   std::unique_ptr<gfx::ImageSkiaSource> source(
386       new AvatarImageSource(*image.ToImageSkia(), size, std::min(width, height),
387                             AvatarImageSource::POSITION_CENTER,
388                             AvatarImageSource::BORDER_NONE, shape));
389 
390   return gfx::Image(gfx::ImageSkia(std::move(source), size));
391 }
392 
GetSizedAvatarIcon(const gfx::Image & image,bool is_rectangle,int width,int height)393 gfx::Image GetSizedAvatarIcon(const gfx::Image& image,
394                               bool is_rectangle,
395                               int width,
396                               int height) {
397   return GetSizedAvatarIcon(image, is_rectangle, width, height,
398                             profiles::SHAPE_SQUARE);
399 }
400 
GetAvatarIconForWebUI(const gfx::Image & image,bool is_rectangle)401 gfx::Image GetAvatarIconForWebUI(const gfx::Image& image,
402                                  bool is_rectangle) {
403   return GetSizedAvatarIcon(image, is_rectangle, kAvatarIconSize,
404                             kAvatarIconSize);
405 }
406 
GetAvatarIconForTitleBar(const gfx::Image & image,bool is_gaia_image,int dst_width,int dst_height)407 gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image,
408                                     bool is_gaia_image,
409                                     int dst_width,
410                                     int dst_height) {
411   // The image requires no border or resizing.
412   if (!is_gaia_image && image.Height() <= kAvatarIconSize)
413     return image;
414 
415   int size = std::min({kAvatarIconSize, dst_width, dst_height});
416   gfx::Size dst_size(dst_width, dst_height);
417 
418   // Source for a sized icon drawn at the bottom center of the canvas,
419   // with an etched border (for GAIA images).
420   std::unique_ptr<gfx::ImageSkiaSource> source(
421       new AvatarImageSource(*image.ToImageSkia(), dst_size, size,
422                             AvatarImageSource::POSITION_BOTTOM_CENTER,
423                             is_gaia_image ? AvatarImageSource::BORDER_ETCHED
424                                           : AvatarImageSource::BORDER_NONE));
425 
426   return gfx::Image(gfx::ImageSkia(std::move(source), dst_size));
427 }
428 
429 #if defined(OS_MAC)
GetAvatarIconForNSMenu(const base::FilePath & profile_path)430 gfx::Image GetAvatarIconForNSMenu(const base::FilePath& profile_path) {
431   ProfileAttributesEntry* entry;
432   if (!g_browser_process->profile_manager()
433            ->GetProfileAttributesStorage()
434            .GetProfileAttributesWithPath(profile_path, &entry)) {
435     // This can happen if the user deletes the current profile.
436     return gfx::Image();
437   }
438 
439   if (base::FeatureList::IsEnabled(features::kNewProfilePicker)) {
440     // Get a higher res than 16px so it looks good after cropping to a circle.
441     gfx::Image icon =
442         entry->GetAvatarIcon(kAvatarIconSize, /*download_high_res=*/false);
443     return profiles::GetSizedAvatarIcon(
444         icon, /*is_rectangle=*/true, kMenuAvatarIconSize, kMenuAvatarIconSize,
445         profiles::SHAPE_CIRCLE);
446   }
447 
448   constexpr int kOldMenuAvatarIconSize = 38;
449   gfx::Image icon =
450       entry->GetAvatarIcon(kOldMenuAvatarIconSize, /*download_high_res=*/false);
451 
452   // The image might be too large and need to be resized, e.g. if this is a
453   // signed-in user using the GAIA profile photo.
454   if (icon.Width() > kOldMenuAvatarIconSize ||
455       icon.Height() > kOldMenuAvatarIconSize) {
456     icon = profiles::GetSizedAvatarIcon(icon, /*is_rectangle=*/true,
457                                         kOldMenuAvatarIconSize,
458                                         kOldMenuAvatarIconSize);
459   }
460   return icon;
461 }
462 #endif
463 
464 // Helper methods for accessing, transforming and drawing avatar icons.
GetDefaultAvatarIconCount()465 size_t GetDefaultAvatarIconCount() {
466   return kDefaultAvatarIconsCount;
467 }
468 
GetGenericAvatarIconCount()469 size_t GetGenericAvatarIconCount() {
470   return kGenericAvatarIconsCount;
471 }
472 
GetPlaceholderAvatarIndex()473 size_t GetPlaceholderAvatarIndex() {
474   return kPlaceholderAvatarIndex;
475 }
476 
GetModernAvatarIconStartIndex()477 size_t GetModernAvatarIconStartIndex() {
478 #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
479   return GetPlaceholderAvatarIndex() + 1;
480 #else
481   // Only use the placeholder avatar on ChromeOS and Android.
482   // TODO(crbug.com/937834): Clean up code and remove code dependencies from
483   // Android and ChromeOS. Avatar icons from this file are not used on these
484   // platforms.
485   return GetPlaceholderAvatarIndex();
486 #endif
487 }
488 
IsModernAvatarIconIndex(size_t icon_index)489 bool IsModernAvatarIconIndex(size_t icon_index) {
490   return icon_index >= GetModernAvatarIconStartIndex() &&
491          icon_index < GetDefaultAvatarIconCount();
492 }
493 
GetPlaceholderAvatarIconResourceID()494 int GetPlaceholderAvatarIconResourceID() {
495   // TODO(crbug.com/1100835): Replace with the new icon. Consider coloring the
496   // icon (i.e. providing the image through
497   // ProfileAttributesEntry::GetAvatarIcon(), instead) which would require more
498   // refactoring.
499   return IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE;
500 }
501 
GetPlaceholderAvatarIconUrl()502 std::string GetPlaceholderAvatarIconUrl() {
503   // TODO(crbug.com/1100835): Replace with the new icon. Consider coloring the
504   // icon (i.e. providing the image through
505   // ProfileAttributesEntry::GetAvatarIcon(), instead) which would require more
506   // refactoring.
507   return "chrome://theme/IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE";
508 }
509 
GetDefaultAvatarIconResourceInfo(size_t index)510 const IconResourceInfo* GetDefaultAvatarIconResourceInfo(size_t index) {
511   CHECK_LT(index, kDefaultAvatarIconsCount);
512   static const IconResourceInfo resource_info[kDefaultAvatarIconsCount] = {
513   // Old avatar icons:
514 #if !defined(OS_ANDROID)
515     {IDR_PROFILE_AVATAR_0, "avatar_generic.png", IDS_DEFAULT_AVATAR_LABEL_0},
516     {IDR_PROFILE_AVATAR_1, "avatar_generic_aqua.png",
517      IDS_DEFAULT_AVATAR_LABEL_1},
518     {IDR_PROFILE_AVATAR_2, "avatar_generic_blue.png",
519      IDS_DEFAULT_AVATAR_LABEL_2},
520     {IDR_PROFILE_AVATAR_3, "avatar_generic_green.png",
521      IDS_DEFAULT_AVATAR_LABEL_3},
522     {IDR_PROFILE_AVATAR_4, "avatar_generic_orange.png",
523      IDS_DEFAULT_AVATAR_LABEL_4},
524     {IDR_PROFILE_AVATAR_5, "avatar_generic_purple.png",
525      IDS_DEFAULT_AVATAR_LABEL_5},
526     {IDR_PROFILE_AVATAR_6, "avatar_generic_red.png",
527      IDS_DEFAULT_AVATAR_LABEL_6},
528     {IDR_PROFILE_AVATAR_7, "avatar_generic_yellow.png",
529      IDS_DEFAULT_AVATAR_LABEL_7},
530     {IDR_PROFILE_AVATAR_8, "avatar_secret_agent.png",
531      IDS_DEFAULT_AVATAR_LABEL_8},
532     {IDR_PROFILE_AVATAR_9, "avatar_superhero.png", IDS_DEFAULT_AVATAR_LABEL_9},
533     {IDR_PROFILE_AVATAR_10, "avatar_volley_ball.png",
534      IDS_DEFAULT_AVATAR_LABEL_10},
535     {IDR_PROFILE_AVATAR_11, "avatar_businessman.png",
536      IDS_DEFAULT_AVATAR_LABEL_11},
537     {IDR_PROFILE_AVATAR_12, "avatar_ninja.png", IDS_DEFAULT_AVATAR_LABEL_12},
538     {IDR_PROFILE_AVATAR_13, "avatar_alien.png", IDS_DEFAULT_AVATAR_LABEL_13},
539     {IDR_PROFILE_AVATAR_14, "avatar_awesome.png", IDS_DEFAULT_AVATAR_LABEL_14},
540     {IDR_PROFILE_AVATAR_15, "avatar_flower.png", IDS_DEFAULT_AVATAR_LABEL_15},
541     {IDR_PROFILE_AVATAR_16, "avatar_pizza.png", IDS_DEFAULT_AVATAR_LABEL_16},
542     {IDR_PROFILE_AVATAR_17, "avatar_soccer.png", IDS_DEFAULT_AVATAR_LABEL_17},
543     {IDR_PROFILE_AVATAR_18, "avatar_burger.png", IDS_DEFAULT_AVATAR_LABEL_18},
544     {IDR_PROFILE_AVATAR_19, "avatar_cat.png", IDS_DEFAULT_AVATAR_LABEL_19},
545     {IDR_PROFILE_AVATAR_20, "avatar_cupcake.png", IDS_DEFAULT_AVATAR_LABEL_20},
546     {IDR_PROFILE_AVATAR_21, "avatar_dog.png", IDS_DEFAULT_AVATAR_LABEL_21},
547     {IDR_PROFILE_AVATAR_22, "avatar_horse.png", IDS_DEFAULT_AVATAR_LABEL_22},
548     {IDR_PROFILE_AVATAR_23, "avatar_margarita.png",
549      IDS_DEFAULT_AVATAR_LABEL_23},
550     {IDR_PROFILE_AVATAR_24, "avatar_note.png", IDS_DEFAULT_AVATAR_LABEL_24},
551     {IDR_PROFILE_AVATAR_25, "avatar_sun_cloud.png",
552      IDS_DEFAULT_AVATAR_LABEL_25},
553 #endif
554     // Placeholder avatar icon:
555     {IDR_PROFILE_AVATAR_26, nullptr, IDS_DEFAULT_AVATAR_LABEL_26},
556 
557 #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
558     // Modern avatar icons:
559     {IDR_PROFILE_AVATAR_27, "avatar_origami_cat.png",
560      IDS_DEFAULT_AVATAR_LABEL_27},
561     {IDR_PROFILE_AVATAR_28, "avatar_origami_corgi.png",
562      IDS_DEFAULT_AVATAR_LABEL_28},
563     {IDR_PROFILE_AVATAR_29, "avatar_origami_dragon.png",
564      IDS_DEFAULT_AVATAR_LABEL_29},
565     {IDR_PROFILE_AVATAR_30, "avatar_origami_elephant.png",
566      IDS_DEFAULT_AVATAR_LABEL_30},
567     {IDR_PROFILE_AVATAR_31, "avatar_origami_fox.png",
568      IDS_DEFAULT_AVATAR_LABEL_31},
569     {IDR_PROFILE_AVATAR_32, "avatar_origami_monkey.png",
570      IDS_DEFAULT_AVATAR_LABEL_32},
571     {IDR_PROFILE_AVATAR_33, "avatar_origami_panda.png",
572      IDS_DEFAULT_AVATAR_LABEL_33},
573     {IDR_PROFILE_AVATAR_34, "avatar_origami_penguin.png",
574      IDS_DEFAULT_AVATAR_LABEL_34},
575     {IDR_PROFILE_AVATAR_35, "avatar_origami_pinkbutterfly.png",
576      IDS_DEFAULT_AVATAR_LABEL_35},
577     {IDR_PROFILE_AVATAR_36, "avatar_origami_rabbit.png",
578      IDS_DEFAULT_AVATAR_LABEL_36},
579     {IDR_PROFILE_AVATAR_37, "avatar_origami_unicorn.png",
580      IDS_DEFAULT_AVATAR_LABEL_37},
581     {IDR_PROFILE_AVATAR_38, "avatar_illustration_basketball.png",
582      IDS_DEFAULT_AVATAR_LABEL_38},
583     {IDR_PROFILE_AVATAR_39, "avatar_illustration_bike.png",
584      IDS_DEFAULT_AVATAR_LABEL_39},
585     {IDR_PROFILE_AVATAR_40, "avatar_illustration_bird.png",
586      IDS_DEFAULT_AVATAR_LABEL_40},
587     {IDR_PROFILE_AVATAR_41, "avatar_illustration_cheese.png",
588      IDS_DEFAULT_AVATAR_LABEL_41},
589     {IDR_PROFILE_AVATAR_42, "avatar_illustration_football.png",
590      IDS_DEFAULT_AVATAR_LABEL_42},
591     {IDR_PROFILE_AVATAR_43, "avatar_illustration_ramen.png",
592      IDS_DEFAULT_AVATAR_LABEL_43},
593     {IDR_PROFILE_AVATAR_44, "avatar_illustration_sunglasses.png",
594      IDS_DEFAULT_AVATAR_LABEL_44},
595     {IDR_PROFILE_AVATAR_45, "avatar_illustration_sushi.png",
596      IDS_DEFAULT_AVATAR_LABEL_45},
597     {IDR_PROFILE_AVATAR_46, "avatar_illustration_tamagotchi.png",
598      IDS_DEFAULT_AVATAR_LABEL_46},
599     {IDR_PROFILE_AVATAR_47, "avatar_illustration_vinyl.png",
600      IDS_DEFAULT_AVATAR_LABEL_47},
601     {IDR_PROFILE_AVATAR_48, "avatar_abstract_avocado.png",
602      IDS_DEFAULT_AVATAR_LABEL_48},
603     {IDR_PROFILE_AVATAR_49, "avatar_abstract_cappuccino.png",
604      IDS_DEFAULT_AVATAR_LABEL_49},
605     {IDR_PROFILE_AVATAR_50, "avatar_abstract_icecream.png",
606      IDS_DEFAULT_AVATAR_LABEL_50},
607     {IDR_PROFILE_AVATAR_51, "avatar_abstract_icewater.png",
608      IDS_DEFAULT_AVATAR_LABEL_51},
609     {IDR_PROFILE_AVATAR_52, "avatar_abstract_melon.png",
610      IDS_DEFAULT_AVATAR_LABEL_52},
611     {IDR_PROFILE_AVATAR_53, "avatar_abstract_onigiri.png",
612      IDS_DEFAULT_AVATAR_LABEL_53},
613     {IDR_PROFILE_AVATAR_54, "avatar_abstract_pizza.png",
614      IDS_DEFAULT_AVATAR_LABEL_54},
615     {IDR_PROFILE_AVATAR_55, "avatar_abstract_sandwich.png",
616      IDS_DEFAULT_AVATAR_LABEL_55},
617 #endif
618   };
619   return &resource_info[index];
620 }
621 
GetPlaceholderAvatarIconWithColors(SkColor fill_color,SkColor stroke_color,int size)622 gfx::Image GetPlaceholderAvatarIconWithColors(SkColor fill_color,
623                                               SkColor stroke_color,
624                                               int size) {
625   const gfx::VectorIcon& person_icon =
626       size >= 40 ? kPersonFilledPaddedLargeIcon : kPersonFilledPaddedSmallIcon;
627   gfx::ImageSkia icon_without_background = gfx::CreateVectorIcon(
628       gfx::IconDescription(person_icon, size, stroke_color));
629   gfx::ImageSkia icon_with_background(
630       std::make_unique<ImageWithBackgroundSource>(icon_without_background,
631                                                   fill_color),
632       gfx::Size(size, size));
633   return gfx::Image(icon_with_background);
634 }
635 
GetDefaultAvatarIconResourceIDAtIndex(size_t index)636 int GetDefaultAvatarIconResourceIDAtIndex(size_t index) {
637   return GetDefaultAvatarIconResourceInfo(index)->resource_id;
638 }
639 
640 #if defined(OS_WIN)
GetOldDefaultAvatar2xIconResourceIDAtIndex(size_t index)641 int GetOldDefaultAvatar2xIconResourceIDAtIndex(size_t index) {
642   DCHECK_LT(index, base::size(kProfileAvatarIconResources2x));
643   return kProfileAvatarIconResources2x[index];
644 }
645 #endif  // defined(OS_WIN)
646 
GetDefaultAvatarIconFileNameAtIndex(size_t index)647 const char* GetDefaultAvatarIconFileNameAtIndex(size_t index) {
648   CHECK_NE(index, kPlaceholderAvatarIndex);
649   return GetDefaultAvatarIconResourceInfo(index)->filename;
650 }
651 
GetPathOfHighResAvatarAtIndex(size_t index)652 base::FilePath GetPathOfHighResAvatarAtIndex(size_t index) {
653   const char* file_name = GetDefaultAvatarIconFileNameAtIndex(index);
654   base::FilePath user_data_dir;
655   CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
656   return user_data_dir.Append(kHighResAvatarFolderName).AppendASCII(file_name);
657 }
658 
GetDefaultAvatarIconUrl(size_t index)659 std::string GetDefaultAvatarIconUrl(size_t index) {
660 #if !defined(OS_ANDROID)
661   CHECK(IsDefaultAvatarIconIndex(index));
662 #endif
663   return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index);
664 }
665 
GetDefaultAvatarLabelResourceIDAtIndex(size_t index)666 int GetDefaultAvatarLabelResourceIDAtIndex(size_t index) {
667   return GetDefaultAvatarIconResourceInfo(index)->label_id;
668 }
669 
IsDefaultAvatarIconIndex(size_t index)670 bool IsDefaultAvatarIconIndex(size_t index) {
671   return index < kDefaultAvatarIconsCount;
672 }
673 
IsDefaultAvatarIconUrl(const std::string & url,size_t * icon_index)674 bool IsDefaultAvatarIconUrl(const std::string& url, size_t* icon_index) {
675   DCHECK(icon_index);
676   if (!base::StartsWith(url, kDefaultUrlPrefix, base::CompareCase::SENSITIVE))
677     return false;
678 
679   int int_value = -1;
680   if (base::StringToInt(base::StringPiece(url.begin() +
681                                           strlen(kDefaultUrlPrefix),
682                                           url.end()),
683                         &int_value)) {
684     if (int_value < 0 ||
685         int_value >= static_cast<int>(kDefaultAvatarIconsCount))
686       return false;
687     *icon_index = int_value;
688     return true;
689   }
690 
691   return false;
692 }
693 
GetAvatarIconAndLabelDict(const std::string & url,const base::string16 & label,size_t index,bool selected,bool is_gaia_avatar)694 std::unique_ptr<base::DictionaryValue> GetAvatarIconAndLabelDict(
695     const std::string& url,
696     const base::string16& label,
697     size_t index,
698     bool selected,
699     bool is_gaia_avatar) {
700   std::unique_ptr<base::DictionaryValue> avatar_info(
701       new base::DictionaryValue());
702   avatar_info->SetStringPath("url", url);
703   avatar_info->SetStringPath("label", label);
704   avatar_info->SetIntPath("index", index);
705   avatar_info->SetBoolPath("selected", selected);
706   avatar_info->SetBoolPath("isGaiaAvatar", is_gaia_avatar);
707   return avatar_info;
708 }
709 
GetDefaultProfileAvatarIconAndLabel(SkColor fill_color,SkColor stroke_color,bool selected)710 std::unique_ptr<base::DictionaryValue> GetDefaultProfileAvatarIconAndLabel(
711     SkColor fill_color,
712     SkColor stroke_color,
713     bool selected) {
714   std::unique_ptr<base::DictionaryValue> avatar_info(
715       new base::DictionaryValue());
716   gfx::Image icon = profiles::GetPlaceholderAvatarIconWithColors(
717       fill_color, stroke_color, kAvatarIconSize);
718   size_t index = profiles::GetPlaceholderAvatarIndex();
719   return GetAvatarIconAndLabelDict(
720       webui::GetBitmapDataUrl(icon.AsBitmap()),
721       l10n_util::GetStringUTF16(
722           profiles::GetDefaultAvatarLabelResourceIDAtIndex(index)),
723       index, selected, /*is_gaia_avatar=*/false);
724 }
725 
GetCustomProfileAvatarIconsAndLabels(size_t selected_avatar_idx)726 std::unique_ptr<base::ListValue> GetCustomProfileAvatarIconsAndLabels(
727     size_t selected_avatar_idx) {
728   std::unique_ptr<base::ListValue> avatars(new base::ListValue());
729 
730   for (size_t i = GetModernAvatarIconStartIndex();
731        i < GetDefaultAvatarIconCount(); ++i) {
732     avatars->Append(GetAvatarIconAndLabelDict(
733         profiles::GetDefaultAvatarIconUrl(i),
734         l10n_util::GetStringUTF16(
735             profiles::GetDefaultAvatarLabelResourceIDAtIndex(i)),
736         i, i == selected_avatar_idx, /*is_gaia_avatar=*/false));
737   }
738   return avatars;
739 }
740 
GetRandomAvatarIconIndex(const std::unordered_set<size_t> & used_icon_indices)741 size_t GetRandomAvatarIconIndex(
742     const std::unordered_set<size_t>& used_icon_indices) {
743   size_t interval_begin = GetModernAvatarIconStartIndex();
744   size_t interval_end = GetDefaultAvatarIconCount();
745   size_t interval_length = interval_end - interval_begin;
746 
747   size_t random_offset = base::RandInt(0, interval_length - 1);
748   // Find the next unused index.
749   for (size_t i = 0; i < interval_length; ++i) {
750     size_t icon_index = interval_begin + (random_offset + i) % interval_length;
751     if (used_icon_indices.count(icon_index) == 0u)
752       return icon_index;
753   }
754   // All indices are used, so return a random one.
755   return interval_begin + random_offset;
756 }
757 
758 #if defined(OS_WIN)
GetWin2xAvatarImage(ProfileAttributesEntry * entry)759 SkBitmap GetWin2xAvatarImage(ProfileAttributesEntry* entry) {
760   // Request just one size large enough for all uses.
761   return GetSkBitmapCopy(entry->GetAvatarIcon(IconUtil::kLargeIconSize));
762 }
763 
GetWin2xAvatarIconAsSquare(const SkBitmap & source_bitmap)764 SkBitmap GetWin2xAvatarIconAsSquare(const SkBitmap& source_bitmap) {
765   constexpr int kIconScaleFactor = 2;
766   if ((source_bitmap.width() != kIconScaleFactor * kOldAvatarIconWidth) ||
767       (source_bitmap.height() != kIconScaleFactor * kOldAvatarIconHeight)) {
768     // It's not an old avatar icon, the image should be square.
769     DCHECK_EQ(source_bitmap.width(), source_bitmap.height());
770     return source_bitmap;
771   }
772 
773   // If |source_bitmap| matches the old avatar icon dimensions, i.e. it's an
774   // old avatar icon, shave a couple of columns so the |source_bitmap| is more
775   // square. So when resized to a square aspect ratio it looks pretty.
776   gfx::Rect frame(gfx::SkIRectToRect(source_bitmap.bounds()));
777   frame.Inset(/*horizontal=*/kIconScaleFactor * 2, /*vertical=*/0);
778   SkBitmap cropped_bitmap;
779   source_bitmap.extractSubset(&cropped_bitmap, gfx::RectToSkIRect(frame));
780   return cropped_bitmap;
781 }
782 
GetBadgedWinIconBitmapForAvatar(const SkBitmap & app_icon_bitmap,const SkBitmap & avatar_bitmap)783 SkBitmap GetBadgedWinIconBitmapForAvatar(const SkBitmap& app_icon_bitmap,
784                                          const SkBitmap& avatar_bitmap) {
785   // TODO(dfried): This function often doesn't actually do the thing it claims
786   // to. We should probably fix it.
787   SkBitmap source_bitmap = profiles::GetWin2xAvatarIconAsSquare(avatar_bitmap);
788 
789   int avatar_badge_width = kProfileAvatarBadgeSizeWin;
790   if (app_icon_bitmap.width() != kShortcutIconSizeWin) {
791     avatar_badge_width = std::ceilf(
792         app_icon_bitmap.width() *
793         (float{kProfileAvatarBadgeSizeWin} / float{kShortcutIconSizeWin}));
794   }
795 
796   // Resize the avatar image down to the desired badge size, maintaining aspect
797   // ratio (but prefer more square than rectangular when rounding).
798   const int avatar_badge_height =
799       std::ceilf(avatar_badge_width * (float{source_bitmap.height()} /
800                                        float{source_bitmap.width()}));
801   SkBitmap sk_icon = skia::ImageOperations::Resize(
802       source_bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
803       avatar_badge_width, avatar_badge_height);
804 
805   // Sanity check - avatars shouldn't be taller than they are wide.
806   DCHECK_GE(sk_icon.width(), sk_icon.height());
807 
808   // Overlay the avatar on the icon, anchoring it to the bottom-right of the
809   // icon.
810   SkBitmap badged_bitmap;
811   badged_bitmap.allocN32Pixels(app_icon_bitmap.width(),
812                                app_icon_bitmap.height());
813   SkCanvas offscreen_canvas(badged_bitmap, SkSurfaceProps{});
814   offscreen_canvas.clear(SK_ColorTRANSPARENT);
815   offscreen_canvas.drawBitmap(app_icon_bitmap, 0, 0);
816 
817   // Render the avatar in a cutout circle. If the avatar is not square, center
818   // it in the circle but favor pushing it further down.
819   const int cutout_size = avatar_badge_width;
820   const int cutout_left = app_icon_bitmap.width() - cutout_size;
821   const int cutout_top = app_icon_bitmap.height() - cutout_size;
822   const int icon_left = cutout_left;
823   const int icon_top =
824       cutout_top + int{std::ceilf((cutout_size - avatar_badge_height) / 2.0f)};
825   const SkRRect clip_circle = SkRRect::MakeOval(
826       SkRect::MakeXYWH(cutout_left, cutout_top, cutout_size, cutout_size));
827 
828   offscreen_canvas.clipRRect(clip_circle, true);
829   offscreen_canvas.drawBitmap(sk_icon, icon_left, icon_top);
830   return badged_bitmap;
831 }
832 #endif  // OS_WIN
833 
834 }  // namespace profiles
835