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. 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 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 92 AvatarImageSource::~AvatarImageSource() { 93 } 94 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 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 230 gfx::Image GetAvatarIconForMenu(const gfx::Image& image, 231 bool is_rectangle) { 232 return GetSizedAvatarIcon( 233 image, is_rectangle, kAvatarIconWidth, kAvatarIconHeight); 234 } 235 236 gfx::Image GetAvatarIconForWebUI(const gfx::Image& image, 237 bool is_rectangle) { 238 return GetSizedAvatarIcon(image, is_rectangle, 239 kAvatarIconWidth, kAvatarIconHeight); 240 } 241 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 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. 288 size_t GetDefaultAvatarIconCount() { 289 return kDefaultAvatarIconsCount; 290 } 291 292 size_t GetGenericAvatarIconCount() { 293 return kGenericAvatarIconsCount; 294 } 295 296 int GetPlaceholderAvatarIndex() { 297 return kPlaceholderAvatarIcon; 298 } 299 300 int GetPlaceholderAvatarIconResourceID() { 301 return IDR_PROFILE_AVATAR_26; 302 } 303 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 337 int GetDefaultAvatarIconResourceIDAtIndex(size_t index) { 338 DCHECK(IsDefaultAvatarIconIndex(index)); 339 return GetDefaultAvatarIconResourceInfo(index)->resource_id; 340 } 341 342 const char* GetDefaultAvatarIconFileNameAtIndex(size_t index) { 343 DCHECK(index < kDefaultAvatarIconsCount); 344 return GetDefaultAvatarIconResourceInfo(index)->filename; 345 } 346 347 const char* GetNoHighResAvatarFileName() { 348 return kNoHighResAvatar; 349 } 350 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 359 std::string GetDefaultAvatarIconUrl(size_t index) { 360 DCHECK(IsDefaultAvatarIconIndex(index)); 361 return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index); 362 } 363 364 bool IsDefaultAvatarIconIndex(size_t index) { 365 return index < kDefaultAvatarIconsCount; 366 } 367 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