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.
5 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
7 #include "base/files/file_util.h"
8 #include "base/format_macros.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/path_service.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/stringprintf.h"
13 #include "chrome/common/chrome_paths.h"
14 #include "grit/theme_resources.h"
15 #include "skia/ext/image_operations.h"
16 #include "third_party/skia/include/core/SkPaint.h"
17 #include "third_party/skia/include/core/SkPath.h"
18 #include "third_party/skia/include/core/SkScalar.h"
19 #include "third_party/skia/include/core/SkXfermode.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/image/canvas_image_source.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/image/image_skia_operations.h"
24 #include "ui/gfx/rect.h"
25 #include "ui/gfx/skia_util.h"
27 // Helper methods for transforming and drawing avatar icons.
30 // Determine what the scaled height of the avatar icon should be for a
31 // specified width, to preserve the aspect ratio.
32 int GetScaledAvatarHeightForWidth(int width
, const gfx::ImageSkia
& avatar
) {
33 // Multiply the width by the inverted aspect ratio (height over
34 // width), and then add 0.5 to ensure the int truncation rounds nicely.
35 int scaled_height
= width
*
36 ((float) avatar
.height() / (float) avatar
.width()) + 0.5f
;
40 // A CanvasImageSource that draws a sized and positioned avatar with an
41 // optional border independently of the scale factor.
42 class AvatarImageSource
: public gfx::CanvasImageSource
{
46 POSITION_BOTTOM_CENTER
,
55 AvatarImageSource(gfx::ImageSkia avatar
,
56 const gfx::Size
& canvas_size
,
58 AvatarPosition position
,
60 virtual ~AvatarImageSource();
62 // CanvasImageSource override:
63 virtual void Draw(gfx::Canvas
* canvas
) override
;
66 gfx::ImageSkia avatar_
;
67 const gfx::Size canvas_size_
;
70 const AvatarPosition position_
;
71 const AvatarBorder border_
;
73 DISALLOW_COPY_AND_ASSIGN(AvatarImageSource
);
76 AvatarImageSource::AvatarImageSource(gfx::ImageSkia avatar
,
77 const gfx::Size
& canvas_size
,
79 AvatarPosition position
,
81 : gfx::CanvasImageSource(canvas_size
, false),
82 canvas_size_(canvas_size
),
84 height_(GetScaledAvatarHeightForWidth(width
, avatar
)),
87 avatar_
= gfx::ImageSkiaOperations::CreateResizedImage(
88 avatar
, skia::ImageOperations::RESIZE_BEST
,
89 gfx::Size(width_
, height_
));
92 AvatarImageSource::~AvatarImageSource() {
95 void AvatarImageSource::Draw(gfx::Canvas
* canvas
) {
96 // Center the avatar horizontally.
97 int x
= (canvas_size_
.width() - width_
) / 2;
100 if (position_
== POSITION_CENTER
) {
101 // Draw the avatar centered on the canvas.
102 y
= (canvas_size_
.height() - height_
) / 2;
104 // Draw the avatar on the bottom center of the canvas, leaving 1px below.
105 y
= canvas_size_
.height() - height_
- 1;
108 canvas
->DrawImageInt(avatar_
, x
, y
);
110 // The border should be square.
111 int border_size
= std::max(width_
, height_
);
112 // Reset the x and y for the square border.
113 x
= (canvas_size_
.width() - border_size
) / 2;
114 y
= (canvas_size_
.height() - border_size
) / 2;
116 if (border_
== BORDER_NORMAL
) {
117 // Draw a gray border on the inside of the avatar.
118 SkColor border_color
= SkColorSetARGB(83, 0, 0, 0);
120 // Offset the rectangle by a half pixel so the border is drawn within the
121 // appropriate pixels no matter the scale factor. Subtract 1 from the right
122 // and bottom sizes to specify the endpoints, yielding -0.5.
124 path
.addRect(SkFloatToScalar(x
+ 0.5f
), // left
125 SkFloatToScalar(y
+ 0.5f
), // top
126 SkFloatToScalar(x
+ border_size
- 0.5f
), // right
127 SkFloatToScalar(y
+ border_size
- 0.5f
)); // bottom
130 paint
.setColor(border_color
);
131 paint
.setStyle(SkPaint::kStroke_Style
);
132 paint
.setStrokeWidth(SkIntToScalar(1));
134 canvas
->DrawPath(path
, paint
);
135 } else if (border_
== BORDER_ETCHED
) {
136 // Give the avatar an etched look by drawing a highlight on the bottom and
138 SkColor shadow_color
= SkColorSetARGB(83, 0, 0, 0);
139 SkColor highlight_color
= SkColorSetARGB(96, 255, 255, 255);
142 paint
.setStyle(SkPaint::kStroke_Style
);
143 paint
.setStrokeWidth(SkIntToScalar(1));
147 // Left and top shadows. To support higher scale factors than 1, position
148 // the orthogonal dimension of each line on the half-pixel to separate the
149 // pixel. For a vertical line, this means adding 0.5 to the x-value.
150 path
.moveTo(SkFloatToScalar(x
+ 0.5f
), SkIntToScalar(y
+ height_
));
152 // Draw up to the top-left. Stop with the y-value at a half-pixel.
153 path
.rLineTo(SkIntToScalar(0), SkFloatToScalar(-height_
+ 0.5f
));
155 // Draw right to the top-right, stopping within the last pixel.
156 path
.rLineTo(SkFloatToScalar(width_
- 0.5f
), SkIntToScalar(0));
158 paint
.setColor(shadow_color
);
159 canvas
->DrawPath(path
, paint
);
163 // Bottom and right highlights. Note that the shadows own the shared corner
164 // pixels, so reduce the sizes accordingly.
165 path
.moveTo(SkIntToScalar(x
+ 1), SkFloatToScalar(y
+ height_
- 0.5f
));
167 // Draw right to the bottom-right.
168 path
.rLineTo(SkFloatToScalar(width_
- 1.5f
), SkIntToScalar(0));
170 // Draw up to the top-right.
171 path
.rLineTo(SkIntToScalar(0), SkFloatToScalar(-height_
+ 1.5f
));
173 paint
.setColor(highlight_color
);
174 canvas
->DrawPath(path
, paint
);
182 struct IconResourceInfo
{
184 const char* filename
;
187 const int kAvatarIconWidth
= 38;
188 const int kAvatarIconHeight
= 31;
189 const SkColor kAvatarTutorialBackgroundColor
= SkColorSetRGB(0x42, 0x85, 0xf4);
190 const SkColor kAvatarTutorialContentTextColor
= SkColorSetRGB(0xc6, 0xda, 0xfc);
191 const SkColor kAvatarBubbleAccountsBackgroundColor
=
192 SkColorSetRGB(0xf3, 0xf3, 0xf3);
194 const char kDefaultUrlPrefix
[] = "chrome://theme/IDR_PROFILE_AVATAR_";
195 const char kGAIAPictureFileName
[] = "Google Profile Picture.png";
196 const char kHighResAvatarFolderName
[] = "Avatars";
198 // This avatar does not exist on the server, the high res copy is in the build.
199 const char kNoHighResAvatar
[] = "NothingToDownload";
201 // The size of the function-static kDefaultAvatarIconResources array below.
202 const size_t kDefaultAvatarIconsCount
= 27;
204 // The first 8 icons are generic.
205 const size_t kGenericAvatarIconsCount
= 8;
207 // The avatar used as a placeholder (grey silhouette).
208 const int kPlaceholderAvatarIcon
= 26;
210 gfx::Image
GetSizedAvatarIcon(const gfx::Image
& image
,
212 int width
, int height
) {
213 if (!is_rectangle
&& image
.Height() <= height
)
216 gfx::Size
size(width
, height
);
218 // Source for a centered, sized icon. GAIA images get a border.
219 scoped_ptr
<gfx::ImageSkiaSource
> source(
220 new AvatarImageSource(
221 *image
.ToImageSkia(),
223 std::min(width
, height
),
224 AvatarImageSource::POSITION_CENTER
,
225 AvatarImageSource::BORDER_NONE
));
227 return gfx::Image(gfx::ImageSkia(source
.release(), size
));
230 gfx::Image
GetAvatarIconForMenu(const gfx::Image
& image
,
232 return GetSizedAvatarIcon(
233 image
, is_rectangle
, kAvatarIconWidth
, kAvatarIconHeight
);
236 gfx::Image
GetAvatarIconForWebUI(const gfx::Image
& image
,
238 return GetSizedAvatarIcon(image
, is_rectangle
,
239 kAvatarIconWidth
, kAvatarIconHeight
);
242 gfx::Image
GetAvatarIconForTitleBar(const gfx::Image
& image
,
246 // The image requires no border or resizing.
247 if (!is_gaia_image
&& image
.Height() <= kAvatarIconHeight
)
250 int size
= std::min(std::min(kAvatarIconWidth
, kAvatarIconHeight
),
251 std::min(dst_width
, dst_height
));
252 gfx::Size
dst_size(dst_width
, dst_height
);
254 // Source for a sized icon drawn at the bottom center of the canvas,
255 // with an etched border (for GAIA images).
256 scoped_ptr
<gfx::ImageSkiaSource
> source(
257 new AvatarImageSource(
258 *image
.ToImageSkia(),
261 AvatarImageSource::POSITION_BOTTOM_CENTER
,
262 is_gaia_image
? AvatarImageSource::BORDER_ETCHED
:
263 AvatarImageSource::BORDER_NONE
));
265 return gfx::Image(gfx::ImageSkia(source
.release(), dst_size
));
268 SkBitmap
GetAvatarIconAsSquare(const SkBitmap
& source_bitmap
,
270 SkBitmap square_bitmap
;
271 if ((source_bitmap
.width() == scale_factor
* profiles::kAvatarIconWidth
) &&
272 (source_bitmap
.height() == scale_factor
* profiles::kAvatarIconHeight
)) {
273 // Shave a couple of columns so the |source_bitmap| is more square. So when
274 // resized to a square aspect ratio it looks pretty.
275 gfx::Rect
frame(scale_factor
* profiles::kAvatarIconWidth
,
276 scale_factor
* profiles::kAvatarIconHeight
);
277 frame
.Inset(scale_factor
* 2, 0, scale_factor
* 2, 0);
278 source_bitmap
.extractSubset(&square_bitmap
, gfx::RectToSkIRect(frame
));
280 // If not the avatar icon's aspect ratio, the image should be square.
281 DCHECK(source_bitmap
.width() == source_bitmap
.height());
282 square_bitmap
= source_bitmap
;
284 return square_bitmap
;
287 // Helper methods for accessing, transforming and drawing avatar icons.
288 size_t GetDefaultAvatarIconCount() {
289 return kDefaultAvatarIconsCount
;
292 size_t GetGenericAvatarIconCount() {
293 return kGenericAvatarIconsCount
;
296 int GetPlaceholderAvatarIndex() {
297 return kPlaceholderAvatarIcon
;
300 int GetPlaceholderAvatarIconResourceID() {
301 return IDR_PROFILE_AVATAR_26
;
304 const IconResourceInfo
* GetDefaultAvatarIconResourceInfo(size_t index
) {
305 static const IconResourceInfo resource_info
[kDefaultAvatarIconsCount
] = {
306 { IDR_PROFILE_AVATAR_0
, "avatar_generic.png"},
307 { IDR_PROFILE_AVATAR_1
, "avatar_generic_aqua.png"},
308 { IDR_PROFILE_AVATAR_2
, "avatar_generic_blue.png"},
309 { IDR_PROFILE_AVATAR_3
, "avatar_generic_green.png"},
310 { IDR_PROFILE_AVATAR_4
, "avatar_generic_orange.png"},
311 { IDR_PROFILE_AVATAR_5
, "avatar_generic_purple.png"},
312 { IDR_PROFILE_AVATAR_6
, "avatar_generic_red.png"},
313 { IDR_PROFILE_AVATAR_7
, "avatar_generic_yellow.png"},
314 { IDR_PROFILE_AVATAR_8
, "avatar_secret_agent.png"},
315 { IDR_PROFILE_AVATAR_9
, "avatar_superhero.png"},
316 { IDR_PROFILE_AVATAR_10
, "avatar_volley_ball.png"},
317 { IDR_PROFILE_AVATAR_11
, "avatar_businessman.png"},
318 { IDR_PROFILE_AVATAR_12
, "avatar_ninja.png"},
319 { IDR_PROFILE_AVATAR_13
, "avatar_alien.png"},
320 { IDR_PROFILE_AVATAR_14
, "avatar_smiley.png"},
321 { IDR_PROFILE_AVATAR_15
, "avatar_flower.png"},
322 { IDR_PROFILE_AVATAR_16
, "avatar_pizza.png"},
323 { IDR_PROFILE_AVATAR_17
, "avatar_soccer.png"},
324 { IDR_PROFILE_AVATAR_18
, "avatar_burger.png"},
325 { IDR_PROFILE_AVATAR_19
, "avatar_cat.png"},
326 { IDR_PROFILE_AVATAR_20
, "avatar_cupcake.png"},
327 { IDR_PROFILE_AVATAR_21
, "avatar_dog.png"},
328 { IDR_PROFILE_AVATAR_22
, "avatar_horse.png"},
329 { IDR_PROFILE_AVATAR_23
, "avatar_margarita.png"},
330 { IDR_PROFILE_AVATAR_24
, "avatar_note.png"},
331 { IDR_PROFILE_AVATAR_25
, "avatar_sun_cloud.png"},
332 { IDR_PROFILE_AVATAR_26
, kNoHighResAvatar
},
334 return &resource_info
[index
];
337 int GetDefaultAvatarIconResourceIDAtIndex(size_t index
) {
338 DCHECK(IsDefaultAvatarIconIndex(index
));
339 return GetDefaultAvatarIconResourceInfo(index
)->resource_id
;
342 const char* GetDefaultAvatarIconFileNameAtIndex(size_t index
) {
343 DCHECK(index
< kDefaultAvatarIconsCount
);
344 return GetDefaultAvatarIconResourceInfo(index
)->filename
;
347 const char* GetNoHighResAvatarFileName() {
348 return kNoHighResAvatar
;
351 base::FilePath
GetPathOfHighResAvatarAtIndex(size_t index
) {
352 std::string file_name
= GetDefaultAvatarIconResourceInfo(index
)->filename
;
353 base::FilePath user_data_dir
;
354 PathService::Get(chrome::DIR_USER_DATA
, &user_data_dir
);
355 return user_data_dir
.AppendASCII(
356 kHighResAvatarFolderName
).AppendASCII(file_name
);
359 std::string
GetDefaultAvatarIconUrl(size_t index
) {
360 DCHECK(IsDefaultAvatarIconIndex(index
));
361 return base::StringPrintf("%s%" PRIuS
, kDefaultUrlPrefix
, index
);
364 bool IsDefaultAvatarIconIndex(size_t index
) {
365 return index
< kDefaultAvatarIconsCount
;
368 bool IsDefaultAvatarIconUrl(const std::string
& url
, size_t* icon_index
) {
370 if (url
.find(kDefaultUrlPrefix
) != 0)
374 if (base::StringToInt(base::StringPiece(url
.begin() +
375 strlen(kDefaultUrlPrefix
),
379 int_value
>= static_cast<int>(kDefaultAvatarIconsCount
))
381 *icon_index
= int_value
;
388 } // namespace profiles