Home | History | Annotate | Download | only in profiles
      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.
     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 
     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 
     94 AvatarImageSource::~AvatarImageSource() {
     95 }
     96 
     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 
    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 
    232 gfx::Image GetAvatarIconForMenu(const gfx::Image& image,
    233                                 bool is_rectangle) {
    234   return GetSizedAvatarIcon(
    235       image, is_rectangle, kAvatarIconWidth, kAvatarIconHeight);
    236 }
    237 
    238 gfx::Image GetAvatarIconForWebUI(const gfx::Image& image,
    239                                  bool is_rectangle) {
    240   return GetSizedAvatarIcon(image, is_rectangle,
    241                             kAvatarIconWidth, kAvatarIconHeight);
    242 }
    243 
    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 
    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.
    290 size_t GetDefaultAvatarIconCount() {
    291   return kDefaultAvatarIconsCount;
    292 }
    293 
    294 size_t GetGenericAvatarIconCount() {
    295   return kGenericAvatarIconsCount;
    296 }
    297 
    298 int GetPlaceholderAvatarIndex() {
    299   return kPlaceholderAvatarIcon;
    300 }
    301 
    302 int GetPlaceholderAvatarIconResourceID() {
    303   return IDR_PROFILE_AVATAR_26;
    304 }
    305 
    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 
    339 int GetDefaultAvatarIconResourceIDAtIndex(size_t index) {
    340   DCHECK(IsDefaultAvatarIconIndex(index));
    341   return GetDefaultAvatarIconResourceInfo(index)->resource_id;
    342 }
    343 
    344 const char* GetDefaultAvatarIconFileNameAtIndex(size_t index) {
    345   DCHECK(index < kDefaultAvatarIconsCount);
    346   return GetDefaultAvatarIconResourceInfo(index)->filename;
    347 }
    348 
    349 const char* GetNoHighResAvatarFileName() {
    350   return kNoHighResAvatar;
    351 }
    352 
    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 
    361 std::string GetDefaultAvatarIconUrl(size_t index) {
    362   DCHECK(IsDefaultAvatarIconIndex(index));
    363   return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index);
    364 }
    365 
    366 bool IsDefaultAvatarIconIndex(size_t index) {
    367   return index < kDefaultAvatarIconsCount;
    368 }
    369 
    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