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