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