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.
28namespace {
29
30// Determine what the scaled height of the avatar icon should be for a
31// specified width, to preserve the aspect ratio.
32int 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.
42class 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
76AvatarImageSource::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
92AvatarImageSource::~AvatarImageSource() {
93}
94
95void 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
180namespace profiles {
181
182struct IconResourceInfo {
183  int resource_id;
184  const char* filename;
185};
186
187const int kAvatarIconWidth = 38;
188const int kAvatarIconHeight = 31;
189const SkColor kAvatarTutorialBackgroundColor = SkColorSetRGB(0x42, 0x85, 0xf4);
190const SkColor kAvatarTutorialContentTextColor = SkColorSetRGB(0xc6, 0xda, 0xfc);
191const SkColor kAvatarBubbleAccountsBackgroundColor =
192    SkColorSetRGB(0xf3, 0xf3, 0xf3);
193
194const char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
195const char kGAIAPictureFileName[] = "Google Profile Picture.png";
196const char kHighResAvatarFolderName[] = "Avatars";
197
198// This avatar does not exist on the server, the high res copy is in the build.
199const char kNoHighResAvatar[] = "NothingToDownload";
200
201// The size of the function-static kDefaultAvatarIconResources array below.
202const size_t kDefaultAvatarIconsCount = 27;
203
204// The first 8 icons are generic.
205const size_t kGenericAvatarIconsCount = 8;
206
207// The avatar used as a placeholder (grey silhouette).
208const int kPlaceholderAvatarIcon = 26;
209
210gfx::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
230gfx::Image GetAvatarIconForMenu(const gfx::Image& image,
231                                bool is_rectangle) {
232  return GetSizedAvatarIcon(
233      image, is_rectangle, kAvatarIconWidth, kAvatarIconHeight);
234}
235
236gfx::Image GetAvatarIconForWebUI(const gfx::Image& image,
237                                 bool is_rectangle) {
238  return GetSizedAvatarIcon(image, is_rectangle,
239                            kAvatarIconWidth, kAvatarIconHeight);
240}
241
242gfx::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
268SkBitmap 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.
288size_t GetDefaultAvatarIconCount() {
289  return kDefaultAvatarIconsCount;
290}
291
292size_t GetGenericAvatarIconCount() {
293  return kGenericAvatarIconsCount;
294}
295
296int GetPlaceholderAvatarIndex() {
297  return kPlaceholderAvatarIcon;
298}
299
300int GetPlaceholderAvatarIconResourceID() {
301  return IDR_PROFILE_AVATAR_26;
302}
303
304const 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
337int GetDefaultAvatarIconResourceIDAtIndex(size_t index) {
338  DCHECK(IsDefaultAvatarIconIndex(index));
339  return GetDefaultAvatarIconResourceInfo(index)->resource_id;
340}
341
342const char* GetDefaultAvatarIconFileNameAtIndex(size_t index) {
343  DCHECK(index < kDefaultAvatarIconsCount);
344  return GetDefaultAvatarIconResourceInfo(index)->filename;
345}
346
347const char* GetNoHighResAvatarFileName() {
348  return kNoHighResAvatar;
349}
350
351base::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
359std::string GetDefaultAvatarIconUrl(size_t index) {
360  DCHECK(IsDefaultAvatarIconIndex(index));
361  return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index);
362}
363
364bool IsDefaultAvatarIconIndex(size_t index) {
365  return index < kDefaultAvatarIconsCount;
366}
367
368bool 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