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 "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"
26
27 // Helper methods for transforming and drawing avatar icons.
28 namespace {
29
30 // Determine what the scaled height of the avatar icon should be for a
31 // specified width, to preserve the aspect ratio.
GetScaledAvatarHeightForWidth(int width,const gfx::ImageSkia & avatar)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;
37 return scaled_height;
38 }
39
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 {
43 public:
44 enum AvatarPosition {
45 POSITION_CENTER,
46 POSITION_BOTTOM_CENTER,
47 };
48
49 enum AvatarBorder {
50 BORDER_NONE,
51 BORDER_NORMAL,
52 BORDER_ETCHED,
53 };
54
55 AvatarImageSource(gfx::ImageSkia avatar,
56 const gfx::Size& canvas_size,
57 int width,
58 AvatarPosition position,
59 AvatarBorder border);
60 virtual ~AvatarImageSource();
61
62 // CanvasImageSource override:
63 virtual void Draw(gfx::Canvas* canvas) OVERRIDE;
64
65 private:
66 gfx::ImageSkia avatar_;
67 const gfx::Size canvas_size_;
68 const int width_;
69 const int height_;
70 const AvatarPosition position_;
71 const AvatarBorder border_;
72
73 DISALLOW_COPY_AND_ASSIGN(AvatarImageSource);
74 };
75
AvatarImageSource(gfx::ImageSkia avatar,const gfx::Size & canvas_size,int width,AvatarPosition position,AvatarBorder border)76 AvatarImageSource::AvatarImageSource(gfx::ImageSkia avatar,
77 const gfx::Size& canvas_size,
78 int width,
79 AvatarPosition position,
80 AvatarBorder border)
81 : gfx::CanvasImageSource(canvas_size, false),
82 canvas_size_(canvas_size),
83 width_(width),
84 height_(GetScaledAvatarHeightForWidth(width, avatar)),
85 position_(position),
86 border_(border) {
87 avatar_ = gfx::ImageSkiaOperations::CreateResizedImage(
88 avatar, skia::ImageOperations::RESIZE_BEST,
89 gfx::Size(width_, height_));
90 }
91
~AvatarImageSource()92 AvatarImageSource::~AvatarImageSource() {
93 }
94
Draw(gfx::Canvas * canvas)95 void AvatarImageSource::Draw(gfx::Canvas* canvas) {
96 // Center the avatar horizontally.
97 int x = (canvas_size_.width() - width_) / 2;
98 int y;
99
100 if (position_ == POSITION_CENTER) {
101 // Draw the avatar centered on the canvas.
102 y = (canvas_size_.height() - height_) / 2;
103 } else {
104 // Draw the avatar on the bottom center of the canvas, leaving 1px below.
105 y = canvas_size_.height() - height_ - 1;
106 }
107
108 canvas->DrawImageInt(avatar_, x, y);
109
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;
115
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);
119
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.
123 SkPath path;
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
128
129 SkPaint paint;
130 paint.setColor(border_color);
131 paint.setStyle(SkPaint::kStroke_Style);
132 paint.setStrokeWidth(SkIntToScalar(1));
133
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
137 // right edges.
138 SkColor shadow_color = SkColorSetARGB(83, 0, 0, 0);
139 SkColor highlight_color = SkColorSetARGB(96, 255, 255, 255);
140
141 SkPaint paint;
142 paint.setStyle(SkPaint::kStroke_Style);
143 paint.setStrokeWidth(SkIntToScalar(1));
144
145 SkPath path;
146
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_));
151
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));
154
155 // Draw right to the top-right, stopping within the last pixel.
156 path.rLineTo(SkFloatToScalar(width_ - 0.5f), SkIntToScalar(0));
157
158 paint.setColor(shadow_color);
159 canvas->DrawPath(path, paint);
160
161 path.reset();
162
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));
166
167 // Draw right to the bottom-right.
168 path.rLineTo(SkFloatToScalar(width_ - 1.5f), SkIntToScalar(0));
169
170 // Draw up to the top-right.
171 path.rLineTo(SkIntToScalar(0), SkFloatToScalar(-height_ + 1.5f));
172
173 paint.setColor(highlight_color);
174 canvas->DrawPath(path, paint);
175 }
176 }
177
178 } // namespace
179
180 namespace profiles {
181
182 struct IconResourceInfo {
183 int resource_id;
184 const char* filename;
185 };
186
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);
193
194 const char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
195 const char kGAIAPictureFileName[] = "Google Profile Picture.png";
196 const char kHighResAvatarFolderName[] = "Avatars";
197
198 // This avatar does not exist on the server, the high res copy is in the build.
199 const char kNoHighResAvatar[] = "NothingToDownload";
200
201 // The size of the function-static kDefaultAvatarIconResources array below.
202 const size_t kDefaultAvatarIconsCount = 27;
203
204 // The first 8 icons are generic.
205 const size_t kGenericAvatarIconsCount = 8;
206
207 // The avatar used as a placeholder (grey silhouette).
208 const int kPlaceholderAvatarIcon = 26;
209
GetSizedAvatarIcon(const gfx::Image & image,bool is_rectangle,int width,int height)210 gfx::Image GetSizedAvatarIcon(const gfx::Image& image,
211 bool is_rectangle,
212 int width, int height) {
213 if (!is_rectangle && image.Height() <= height)
214 return image;
215
216 gfx::Size size(width, height);
217
218 // Source for a centered, sized icon. GAIA images get a border.
219 scoped_ptr<gfx::ImageSkiaSource> source(
220 new AvatarImageSource(
221 *image.ToImageSkia(),
222 size,
223 std::min(width, height),
224 AvatarImageSource::POSITION_CENTER,
225 AvatarImageSource::BORDER_NONE));
226
227 return gfx::Image(gfx::ImageSkia(source.release(), size));
228 }
229
GetAvatarIconForMenu(const gfx::Image & image,bool is_rectangle)230 gfx::Image GetAvatarIconForMenu(const gfx::Image& image,
231 bool is_rectangle) {
232 return GetSizedAvatarIcon(
233 image, is_rectangle, kAvatarIconWidth, kAvatarIconHeight);
234 }
235
GetAvatarIconForWebUI(const gfx::Image & image,bool is_rectangle)236 gfx::Image GetAvatarIconForWebUI(const gfx::Image& image,
237 bool is_rectangle) {
238 return GetSizedAvatarIcon(image, is_rectangle,
239 kAvatarIconWidth, kAvatarIconHeight);
240 }
241
GetAvatarIconForTitleBar(const gfx::Image & image,bool is_gaia_image,int dst_width,int dst_height)242 gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image,
243 bool is_gaia_image,
244 int dst_width,
245 int dst_height) {
246 // The image requires no border or resizing.
247 if (!is_gaia_image && image.Height() <= kAvatarIconHeight)
248 return image;
249
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);
253
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(),
259 dst_size,
260 size,
261 AvatarImageSource::POSITION_BOTTOM_CENTER,
262 is_gaia_image ? AvatarImageSource::BORDER_ETCHED :
263 AvatarImageSource::BORDER_NONE));
264
265 return gfx::Image(gfx::ImageSkia(source.release(), dst_size));
266 }
267
GetAvatarIconAsSquare(const SkBitmap & source_bitmap,int scale_factor)268 SkBitmap GetAvatarIconAsSquare(const SkBitmap& source_bitmap,
269 int scale_factor) {
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));
279 } else {
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;
283 }
284 return square_bitmap;
285 }
286
287 // Helper methods for accessing, transforming and drawing avatar icons.
GetDefaultAvatarIconCount()288 size_t GetDefaultAvatarIconCount() {
289 return kDefaultAvatarIconsCount;
290 }
291
GetGenericAvatarIconCount()292 size_t GetGenericAvatarIconCount() {
293 return kGenericAvatarIconsCount;
294 }
295
GetPlaceholderAvatarIndex()296 int GetPlaceholderAvatarIndex() {
297 return kPlaceholderAvatarIcon;
298 }
299
GetPlaceholderAvatarIconResourceID()300 int GetPlaceholderAvatarIconResourceID() {
301 return IDR_PROFILE_AVATAR_26;
302 }
303
GetDefaultAvatarIconResourceInfo(size_t index)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},
333 };
334 return &resource_info[index];
335 }
336
GetDefaultAvatarIconResourceIDAtIndex(size_t index)337 int GetDefaultAvatarIconResourceIDAtIndex(size_t index) {
338 DCHECK(IsDefaultAvatarIconIndex(index));
339 return GetDefaultAvatarIconResourceInfo(index)->resource_id;
340 }
341
GetDefaultAvatarIconFileNameAtIndex(size_t index)342 const char* GetDefaultAvatarIconFileNameAtIndex(size_t index) {
343 DCHECK(index < kDefaultAvatarIconsCount);
344 return GetDefaultAvatarIconResourceInfo(index)->filename;
345 }
346
GetNoHighResAvatarFileName()347 const char* GetNoHighResAvatarFileName() {
348 return kNoHighResAvatar;
349 }
350
GetPathOfHighResAvatarAtIndex(size_t index)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);
357 }
358
GetDefaultAvatarIconUrl(size_t index)359 std::string GetDefaultAvatarIconUrl(size_t index) {
360 DCHECK(IsDefaultAvatarIconIndex(index));
361 return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index);
362 }
363
IsDefaultAvatarIconIndex(size_t index)364 bool IsDefaultAvatarIconIndex(size_t index) {
365 return index < kDefaultAvatarIconsCount;
366 }
367
IsDefaultAvatarIconUrl(const std::string & url,size_t * icon_index)368 bool IsDefaultAvatarIconUrl(const std::string& url, size_t* icon_index) {
369 DCHECK(icon_index);
370 if (url.find(kDefaultUrlPrefix) != 0)
371 return false;
372
373 int int_value = -1;
374 if (base::StringToInt(base::StringPiece(url.begin() +
375 strlen(kDefaultUrlPrefix),
376 url.end()),
377 &int_value)) {
378 if (int_value < 0 ||
379 int_value >= static_cast<int>(kDefaultAvatarIconsCount))
380 return false;
381 *icon_index = int_value;
382 return true;
383 }
384
385 return false;
386 }
387
388 } // namespace profiles
389