• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/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/generated_resources.h"
15 #include "grit/theme_resources.h"
16 #include "skia/ext/image_operations.h"
17 #include "third_party/skia/include/core/SkPaint.h"
18 #include "third_party/skia/include/core/SkPath.h"
19 #include "third_party/skia/include/core/SkScalar.h"
20 #include "third_party/skia/include/core/SkXfermode.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/image/canvas_image_source.h"
24 #include "ui/gfx/image/image.h"
25 #include "ui/gfx/image/image_skia_operations.h"
26 #include "ui/gfx/rect.h"
27 #include "ui/gfx/skia_util.h"
28 
29 // Helper methods for transforming and drawing avatar icons.
30 namespace {
31 
32 // Determine what the scaled height of the avatar icon should be for a
33 // specified width, to preserve the aspect ratio.
GetScaledAvatarHeightForWidth(int width,const gfx::ImageSkia & avatar)34 int GetScaledAvatarHeightForWidth(int width, const gfx::ImageSkia& avatar) {
35   // Multiply the width by the inverted aspect ratio (height over
36   // width), and then add 0.5 to ensure the int truncation rounds nicely.
37   int scaled_height = width *
38       ((float) avatar.height() / (float) avatar.width()) + 0.5f;
39   return scaled_height;
40 }
41 
42 // A CanvasImageSource that draws a sized and positioned avatar with an
43 // optional border independently of the scale factor.
44 class AvatarImageSource : public gfx::CanvasImageSource {
45  public:
46   enum AvatarPosition {
47     POSITION_CENTER,
48     POSITION_BOTTOM_CENTER,
49   };
50 
51   enum AvatarBorder {
52     BORDER_NONE,
53     BORDER_NORMAL,
54     BORDER_ETCHED,
55   };
56 
57   AvatarImageSource(gfx::ImageSkia avatar,
58                     const gfx::Size& canvas_size,
59                     int width,
60                     AvatarPosition position,
61                     AvatarBorder border);
62   virtual ~AvatarImageSource();
63 
64   // CanvasImageSource override:
65   virtual void Draw(gfx::Canvas* canvas) OVERRIDE;
66 
67  private:
68   gfx::ImageSkia avatar_;
69   const gfx::Size canvas_size_;
70   const int width_;
71   const int height_;
72   const AvatarPosition position_;
73   const AvatarBorder border_;
74 
75   DISALLOW_COPY_AND_ASSIGN(AvatarImageSource);
76 };
77 
AvatarImageSource(gfx::ImageSkia avatar,const gfx::Size & canvas_size,int width,AvatarPosition position,AvatarBorder border)78 AvatarImageSource::AvatarImageSource(gfx::ImageSkia avatar,
79                                      const gfx::Size& canvas_size,
80                                      int width,
81                                      AvatarPosition position,
82                                      AvatarBorder border)
83     : gfx::CanvasImageSource(canvas_size, false),
84       canvas_size_(canvas_size),
85       width_(width),
86       height_(GetScaledAvatarHeightForWidth(width, avatar)),
87       position_(position),
88       border_(border) {
89   avatar_ = gfx::ImageSkiaOperations::CreateResizedImage(
90       avatar, skia::ImageOperations::RESIZE_BEST,
91       gfx::Size(width_, height_));
92 }
93 
~AvatarImageSource()94 AvatarImageSource::~AvatarImageSource() {
95 }
96 
Draw(gfx::Canvas * canvas)97 void AvatarImageSource::Draw(gfx::Canvas* canvas) {
98   // Center the avatar horizontally.
99   int x = (canvas_size_.width() - width_) / 2;
100   int y;
101 
102   if (position_ == POSITION_CENTER) {
103     // Draw the avatar centered on the canvas.
104     y = (canvas_size_.height() - height_) / 2;
105   } else {
106     // Draw the avatar on the bottom center of the canvas, leaving 1px below.
107     y = canvas_size_.height() - height_ - 1;
108   }
109 
110   canvas->DrawImageInt(avatar_, x, y);
111 
112   // The border should be square.
113   int border_size = std::max(width_, height_);
114   // Reset the x and y for the square border.
115   x = (canvas_size_.width() - border_size) / 2;
116   y = (canvas_size_.height() - border_size) / 2;
117 
118   if (border_ == BORDER_NORMAL) {
119     // Draw a gray border on the inside of the avatar.
120     SkColor border_color = SkColorSetARGB(83, 0, 0, 0);
121 
122     // Offset the rectangle by a half pixel so the border is drawn within the
123     // appropriate pixels no matter the scale factor. Subtract 1 from the right
124     // and bottom sizes to specify the endpoints, yielding -0.5.
125     SkPath path;
126     path.addRect(SkFloatToScalar(x + 0.5f),  // left
127                  SkFloatToScalar(y + 0.5f),  // top
128                  SkFloatToScalar(x + border_size - 0.5f),   // right
129                  SkFloatToScalar(y + border_size - 0.5f));  // bottom
130 
131     SkPaint paint;
132     paint.setColor(border_color);
133     paint.setStyle(SkPaint::kStroke_Style);
134     paint.setStrokeWidth(SkIntToScalar(1));
135 
136     canvas->DrawPath(path, paint);
137   } else if (border_ == BORDER_ETCHED) {
138     // Give the avatar an etched look by drawing a highlight on the bottom and
139     // right edges.
140     SkColor shadow_color = SkColorSetARGB(83, 0, 0, 0);
141     SkColor highlight_color = SkColorSetARGB(96, 255, 255, 255);
142 
143     SkPaint paint;
144     paint.setStyle(SkPaint::kStroke_Style);
145     paint.setStrokeWidth(SkIntToScalar(1));
146 
147     SkPath path;
148 
149     // Left and top shadows. To support higher scale factors than 1, position
150     // the orthogonal dimension of each line on the half-pixel to separate the
151     // pixel. For a vertical line, this means adding 0.5 to the x-value.
152     path.moveTo(SkFloatToScalar(x + 0.5f), SkIntToScalar(y + height_));
153 
154     // Draw up to the top-left. Stop with the y-value at a half-pixel.
155     path.rLineTo(SkIntToScalar(0), SkFloatToScalar(-height_ + 0.5f));
156 
157     // Draw right to the top-right, stopping within the last pixel.
158     path.rLineTo(SkFloatToScalar(width_ - 0.5f), SkIntToScalar(0));
159 
160     paint.setColor(shadow_color);
161     canvas->DrawPath(path, paint);
162 
163     path.reset();
164 
165     // Bottom and right highlights. Note that the shadows own the shared corner
166     // pixels, so reduce the sizes accordingly.
167     path.moveTo(SkIntToScalar(x + 1), SkFloatToScalar(y + height_ - 0.5f));
168 
169     // Draw right to the bottom-right.
170     path.rLineTo(SkFloatToScalar(width_ - 1.5f), SkIntToScalar(0));
171 
172     // Draw up to the top-right.
173     path.rLineTo(SkIntToScalar(0), SkFloatToScalar(-height_ + 1.5f));
174 
175     paint.setColor(highlight_color);
176     canvas->DrawPath(path, paint);
177   }
178 }
179 
180 }  // namespace
181 
182 namespace profiles {
183 
184 struct IconResourceInfo {
185   int resource_id;
186   const char* filename;
187 };
188 
189 const int kAvatarIconWidth = 38;
190 const int kAvatarIconHeight = 31;
191 const SkColor kAvatarTutorialBackgroundColor = SkColorSetRGB(0x42, 0x85, 0xf4);
192 const SkColor kAvatarTutorialContentTextColor = SkColorSetRGB(0xc6, 0xda, 0xfc);
193 const SkColor kAvatarBubbleAccountsBackgroundColor =
194     SkColorSetRGB(0xf3, 0xf3, 0xf3);
195 
196 const char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
197 const char kGAIAPictureFileName[] = "Google Profile Picture.png";
198 const char kHighResAvatarFolderName[] = "Avatars";
199 
200 // This avatar does not exist on the server, the high res copy is in the build.
201 const char kNoHighResAvatar[] = "NothingToDownload";
202 
203 // The size of the function-static kDefaultAvatarIconResources array below.
204 const size_t kDefaultAvatarIconsCount = 27;
205 
206 // The first 8 icons are generic.
207 const size_t kGenericAvatarIconsCount = 8;
208 
209 // The avatar used as a placeholder (grey silhouette).
210 const int kPlaceholderAvatarIcon = 26;
211 
GetSizedAvatarIcon(const gfx::Image & image,bool is_rectangle,int width,int height)212 gfx::Image GetSizedAvatarIcon(const gfx::Image& image,
213                               bool is_rectangle,
214                               int width, int height) {
215   if (!is_rectangle && image.Height() <= height)
216     return image;
217 
218   gfx::Size size(width, height);
219 
220   // Source for a centered, sized icon. GAIA images get a border.
221   scoped_ptr<gfx::ImageSkiaSource> source(
222       new AvatarImageSource(
223           *image.ToImageSkia(),
224           size,
225           std::min(width, height),
226           AvatarImageSource::POSITION_CENTER,
227           AvatarImageSource::BORDER_NONE));
228 
229   return gfx::Image(gfx::ImageSkia(source.release(), size));
230 }
231 
GetAvatarIconForMenu(const gfx::Image & image,bool is_rectangle)232 gfx::Image GetAvatarIconForMenu(const gfx::Image& image,
233                                 bool is_rectangle) {
234   return GetSizedAvatarIcon(
235       image, is_rectangle, kAvatarIconWidth, kAvatarIconHeight);
236 }
237 
GetAvatarIconForWebUI(const gfx::Image & image,bool is_rectangle)238 gfx::Image GetAvatarIconForWebUI(const gfx::Image& image,
239                                  bool is_rectangle) {
240   return GetSizedAvatarIcon(image, is_rectangle,
241                             kAvatarIconWidth, kAvatarIconHeight);
242 }
243 
GetAvatarIconForTitleBar(const gfx::Image & image,bool is_gaia_image,int dst_width,int dst_height)244 gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image,
245                                     bool is_gaia_image,
246                                     int dst_width,
247                                     int dst_height) {
248   // The image requires no border or resizing.
249   if (!is_gaia_image && image.Height() <= kAvatarIconHeight)
250     return image;
251 
252   int size = std::min(std::min(kAvatarIconWidth, kAvatarIconHeight),
253                       std::min(dst_width, dst_height));
254   gfx::Size dst_size(dst_width, dst_height);
255 
256   // Source for a sized icon drawn at the bottom center of the canvas,
257   // with an etched border (for GAIA images).
258   scoped_ptr<gfx::ImageSkiaSource> source(
259       new AvatarImageSource(
260           *image.ToImageSkia(),
261           dst_size,
262           size,
263           AvatarImageSource::POSITION_BOTTOM_CENTER,
264           is_gaia_image ? AvatarImageSource::BORDER_ETCHED :
265               AvatarImageSource::BORDER_NONE));
266 
267   return gfx::Image(gfx::ImageSkia(source.release(), dst_size));
268 }
269 
GetAvatarIconAsSquare(const SkBitmap & source_bitmap,int scale_factor)270 SkBitmap GetAvatarIconAsSquare(const SkBitmap& source_bitmap,
271                                int scale_factor) {
272   SkBitmap square_bitmap;
273   if ((source_bitmap.width() == scale_factor * profiles::kAvatarIconWidth) &&
274       (source_bitmap.height() == scale_factor * profiles::kAvatarIconHeight)) {
275     // Shave a couple of columns so the |source_bitmap| is more square. So when
276     // resized to a square aspect ratio it looks pretty.
277     gfx::Rect frame(scale_factor * profiles::kAvatarIconWidth,
278                     scale_factor * profiles::kAvatarIconHeight);
279     frame.Inset(scale_factor * 2, 0, scale_factor * 2, 0);
280     source_bitmap.extractSubset(&square_bitmap, gfx::RectToSkIRect(frame));
281   } else {
282     // If not the avatar icon's aspect ratio, the image should be square.
283     DCHECK(source_bitmap.width() == source_bitmap.height());
284     square_bitmap = source_bitmap;
285   }
286   return square_bitmap;
287 }
288 
289 // Helper methods for accessing, transforming and drawing avatar icons.
GetDefaultAvatarIconCount()290 size_t GetDefaultAvatarIconCount() {
291   return kDefaultAvatarIconsCount;
292 }
293 
GetGenericAvatarIconCount()294 size_t GetGenericAvatarIconCount() {
295   return kGenericAvatarIconsCount;
296 }
297 
GetPlaceholderAvatarIndex()298 int GetPlaceholderAvatarIndex() {
299   return kPlaceholderAvatarIcon;
300 }
301 
GetPlaceholderAvatarIconResourceID()302 int GetPlaceholderAvatarIconResourceID() {
303   return IDR_PROFILE_AVATAR_26;
304 }
305 
GetDefaultAvatarIconResourceInfo(size_t index)306 const IconResourceInfo* GetDefaultAvatarIconResourceInfo(size_t index) {
307   static const IconResourceInfo resource_info[kDefaultAvatarIconsCount] = {
308     { IDR_PROFILE_AVATAR_0, "avatar_generic.png"},
309     { IDR_PROFILE_AVATAR_1, "avatar_generic_aqua.png"},
310     { IDR_PROFILE_AVATAR_2, "avatar_generic_blue.png"},
311     { IDR_PROFILE_AVATAR_3, "avatar_generic_green.png"},
312     { IDR_PROFILE_AVATAR_4, "avatar_generic_orange.png"},
313     { IDR_PROFILE_AVATAR_5, "avatar_generic_purple.png"},
314     { IDR_PROFILE_AVATAR_6, "avatar_generic_red.png"},
315     { IDR_PROFILE_AVATAR_7, "avatar_generic_yellow.png"},
316     { IDR_PROFILE_AVATAR_8, "avatar_secret_agent.png"},
317     { IDR_PROFILE_AVATAR_9, "avatar_superhero.png"},
318     { IDR_PROFILE_AVATAR_10, "avatar_volley_ball.png"},
319     { IDR_PROFILE_AVATAR_11, "avatar_businessman.png"},
320     { IDR_PROFILE_AVATAR_12, "avatar_ninja.png"},
321     { IDR_PROFILE_AVATAR_13, "avatar_alien.png"},
322     { IDR_PROFILE_AVATAR_14, "avatar_smiley.png"},
323     { IDR_PROFILE_AVATAR_15, "avatar_flower.png"},
324     { IDR_PROFILE_AVATAR_16, "avatar_pizza.png"},
325     { IDR_PROFILE_AVATAR_17, "avatar_soccer.png"},
326     { IDR_PROFILE_AVATAR_18, "avatar_burger.png"},
327     { IDR_PROFILE_AVATAR_19, "avatar_cat.png"},
328     { IDR_PROFILE_AVATAR_20, "avatar_cupcake.png"},
329     { IDR_PROFILE_AVATAR_21, "avatar_dog.png"},
330     { IDR_PROFILE_AVATAR_22, "avatar_horse.png"},
331     { IDR_PROFILE_AVATAR_23, "avatar_margarita.png"},
332     { IDR_PROFILE_AVATAR_24, "avatar_note.png"},
333     { IDR_PROFILE_AVATAR_25, "avatar_sun_cloud.png"},
334     { IDR_PROFILE_AVATAR_26, kNoHighResAvatar},
335   };
336   return &resource_info[index];
337 }
338 
GetDefaultAvatarIconResourceIDAtIndex(size_t index)339 int GetDefaultAvatarIconResourceIDAtIndex(size_t index) {
340   DCHECK(IsDefaultAvatarIconIndex(index));
341   return GetDefaultAvatarIconResourceInfo(index)->resource_id;
342 }
343 
GetDefaultAvatarIconFileNameAtIndex(size_t index)344 const char* GetDefaultAvatarIconFileNameAtIndex(size_t index) {
345   DCHECK(index < kDefaultAvatarIconsCount);
346   return GetDefaultAvatarIconResourceInfo(index)->filename;
347 }
348 
GetNoHighResAvatarFileName()349 const char* GetNoHighResAvatarFileName() {
350   return kNoHighResAvatar;
351 }
352 
GetPathOfHighResAvatarAtIndex(size_t index)353 base::FilePath GetPathOfHighResAvatarAtIndex(size_t index) {
354   std::string file_name = GetDefaultAvatarIconResourceInfo(index)->filename;
355   base::FilePath user_data_dir;
356   PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
357   return user_data_dir.AppendASCII(
358       kHighResAvatarFolderName).AppendASCII(file_name);
359 }
360 
GetDefaultAvatarIconUrl(size_t index)361 std::string GetDefaultAvatarIconUrl(size_t index) {
362   DCHECK(IsDefaultAvatarIconIndex(index));
363   return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index);
364 }
365 
IsDefaultAvatarIconIndex(size_t index)366 bool IsDefaultAvatarIconIndex(size_t index) {
367   return index < kDefaultAvatarIconsCount;
368 }
369 
IsDefaultAvatarIconUrl(const std::string & url,size_t * icon_index)370 bool IsDefaultAvatarIconUrl(const std::string& url, size_t* icon_index) {
371   DCHECK(icon_index);
372   if (url.find(kDefaultUrlPrefix) != 0)
373     return false;
374 
375   int int_value = -1;
376   if (base::StringToInt(base::StringPiece(url.begin() +
377                                           strlen(kDefaultUrlPrefix),
378                                           url.end()),
379                         &int_value)) {
380     if (int_value < 0 ||
381         int_value >= static_cast<int>(kDefaultAvatarIconsCount))
382       return false;
383     *icon_index = int_value;
384     return true;
385   }
386 
387   return false;
388 }
389 
390 }  // namespace profiles
391