1 // Copyright (c) 2011 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 "ui/gfx/icon_util.h" 6 7 #include "base/files/file_util.h" 8 #include "base/files/scoped_temp_dir.h" 9 #include "base/memory/scoped_ptr.h" 10 #include "base/path_service.h" 11 #include "testing/gtest/include/gtest/gtest.h" 12 #include "third_party/skia/include/core/SkBitmap.h" 13 #include "ui/gfx/gfx_paths.h" 14 #include "ui/gfx/icon_util_unittests_resource.h" 15 #include "ui/gfx/image/image.h" 16 #include "ui/gfx/image/image_family.h" 17 #include "ui/gfx/size.h" 18 19 namespace { 20 21 static const char kSmallIconName[] = "icon_util/16_X_16_icon.ico"; 22 static const char kLargeIconName[] = "icon_util/128_X_128_icon.ico"; 23 static const char kTempIconFilename[] = "temp_test_icon.ico"; 24 25 } // namespace 26 27 class IconUtilTest : public testing::Test { 28 public: 29 virtual void SetUp() OVERRIDE { 30 gfx::RegisterPathProvider(); 31 ASSERT_TRUE(PathService::Get(gfx::DIR_TEST_DATA, &test_data_directory_)); 32 temp_directory_.CreateUniqueTempDir(); 33 } 34 35 static const int kSmallIconWidth = 16; 36 static const int kSmallIconHeight = 16; 37 static const int kLargeIconWidth = 128; 38 static const int kLargeIconHeight = 128; 39 40 // Given a file name for an .ico file and an image dimensions, this 41 // function loads the icon and returns an HICON handle. 42 HICON LoadIconFromFile(const base::FilePath& filename, 43 int width, int height) { 44 HICON icon = static_cast<HICON>(LoadImage(NULL, 45 filename.value().c_str(), 46 IMAGE_ICON, 47 width, 48 height, 49 LR_LOADTRANSPARENT | LR_LOADFROMFILE)); 50 return icon; 51 } 52 53 SkBitmap CreateBlackSkBitmap(int width, int height) { 54 SkBitmap bitmap; 55 bitmap.allocN32Pixels(width, height); 56 // Setting the pixels to transparent-black. 57 memset(bitmap.getPixels(), 0, width * height * 4); 58 return bitmap; 59 } 60 61 // Loads an .ico file from |icon_filename| and asserts that it contains all of 62 // the expected icon sizes up to and including |max_icon_size|, and no other 63 // icons. If |max_icon_size| >= 256, this tests for a 256x256 PNG icon entry. 64 void CheckAllIconSizes(const base::FilePath& icon_filename, 65 int max_icon_size); 66 67 protected: 68 // The root directory for test files. This should be treated as read-only. 69 base::FilePath test_data_directory_; 70 71 // Directory for creating files by this test. 72 base::ScopedTempDir temp_directory_; 73 }; 74 75 void IconUtilTest::CheckAllIconSizes(const base::FilePath& icon_filename, 76 int max_icon_size) { 77 ASSERT_TRUE(base::PathExists(icon_filename)); 78 79 // Determine how many icons to expect, based on |max_icon_size|. 80 int expected_num_icons = 0; 81 for (size_t i = 0; i < IconUtil::kNumIconDimensions; ++i) { 82 if (IconUtil::kIconDimensions[i] > max_icon_size) 83 break; 84 ++expected_num_icons; 85 } 86 87 // First, use the Windows API to load the icon, a basic validity test. 88 HICON icon = LoadIconFromFile(icon_filename, kSmallIconWidth, 89 kSmallIconHeight); 90 EXPECT_NE(static_cast<HICON>(NULL), icon); 91 if (icon != NULL) 92 ::DestroyIcon(icon); 93 94 // Read the file completely into memory. 95 std::string icon_data; 96 ASSERT_TRUE(base::ReadFileToString(icon_filename, &icon_data)); 97 ASSERT_GE(icon_data.length(), sizeof(IconUtil::ICONDIR)); 98 99 // Ensure that it has exactly the expected number and sizes of icons, in the 100 // expected order. This matches each entry of the loaded file's icon directory 101 // with the corresponding element of kIconDimensions. 102 // Also extracts the 256x256 entry as png_entry. 103 const IconUtil::ICONDIR* icon_dir = 104 reinterpret_cast<const IconUtil::ICONDIR*>(icon_data.data()); 105 EXPECT_EQ(expected_num_icons, icon_dir->idCount); 106 ASSERT_GE(IconUtil::kNumIconDimensions, icon_dir->idCount); 107 ASSERT_GE(icon_data.length(), 108 sizeof(IconUtil::ICONDIR) + 109 icon_dir->idCount * sizeof(IconUtil::ICONDIRENTRY)); 110 const IconUtil::ICONDIRENTRY* png_entry = NULL; 111 for (size_t i = 0; i < icon_dir->idCount; ++i) { 112 const IconUtil::ICONDIRENTRY* entry = &icon_dir->idEntries[i]; 113 // Mod 256 because as a special case in ICONDIRENTRY, the value 0 represents 114 // a width or height of 256. 115 int expected_size = IconUtil::kIconDimensions[i] % 256; 116 EXPECT_EQ(expected_size, static_cast<int>(entry->bWidth)); 117 EXPECT_EQ(expected_size, static_cast<int>(entry->bHeight)); 118 if (entry->bWidth == 0 && entry->bHeight == 0) { 119 EXPECT_EQ(NULL, png_entry); 120 png_entry = entry; 121 } 122 } 123 124 if (max_icon_size >= 256) { 125 ASSERT_TRUE(png_entry); 126 127 // Convert the PNG entry data back to a SkBitmap to ensure it's valid. 128 ASSERT_GE(icon_data.length(), 129 png_entry->dwImageOffset + png_entry->dwBytesInRes); 130 const unsigned char* png_bytes = reinterpret_cast<const unsigned char*>( 131 icon_data.data() + png_entry->dwImageOffset); 132 gfx::Image image = gfx::Image::CreateFrom1xPNGBytes( 133 png_bytes, png_entry->dwBytesInRes); 134 SkBitmap bitmap = image.AsBitmap(); 135 EXPECT_EQ(256, bitmap.width()); 136 EXPECT_EQ(256, bitmap.height()); 137 } 138 } 139 140 // The following test case makes sure IconUtil::SkBitmapFromHICON fails 141 // gracefully when called with invalid input parameters. 142 TEST_F(IconUtilTest, TestIconToBitmapInvalidParameters) { 143 base::FilePath icon_filename = 144 test_data_directory_.AppendASCII(kSmallIconName); 145 gfx::Size icon_size(kSmallIconWidth, kSmallIconHeight); 146 HICON icon = LoadIconFromFile(icon_filename, 147 icon_size.width(), 148 icon_size.height()); 149 ASSERT_TRUE(icon != NULL); 150 151 // Invalid size parameter. 152 gfx::Size invalid_icon_size(kSmallIconHeight, 0); 153 EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(icon, invalid_icon_size), 154 static_cast<SkBitmap*>(NULL)); 155 156 // Invalid icon. 157 EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(NULL, icon_size), 158 static_cast<SkBitmap*>(NULL)); 159 160 // The following code should succeed. 161 scoped_ptr<SkBitmap> bitmap; 162 bitmap.reset(IconUtil::CreateSkBitmapFromHICON(icon, icon_size)); 163 EXPECT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 164 ::DestroyIcon(icon); 165 } 166 167 // The following test case makes sure IconUtil::CreateHICONFromSkBitmap fails 168 // gracefully when called with invalid input parameters. 169 TEST_F(IconUtilTest, TestBitmapToIconInvalidParameters) { 170 HICON icon = NULL; 171 scoped_ptr<SkBitmap> bitmap; 172 173 // Wrong bitmap format. 174 bitmap.reset(new SkBitmap); 175 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 176 bitmap->setInfo(SkImageInfo::MakeA8(kSmallIconWidth, kSmallIconHeight)); 177 icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); 178 EXPECT_EQ(icon, static_cast<HICON>(NULL)); 179 180 // Invalid bitmap size. 181 bitmap.reset(new SkBitmap); 182 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 183 bitmap->setInfo(SkImageInfo::MakeN32Premul(0, 0)); 184 icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); 185 EXPECT_EQ(icon, static_cast<HICON>(NULL)); 186 187 // Valid bitmap configuration but no pixels allocated. 188 bitmap.reset(new SkBitmap); 189 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 190 bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth, 191 kSmallIconHeight)); 192 icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); 193 EXPECT_TRUE(icon == NULL); 194 } 195 196 // The following test case makes sure IconUtil::CreateIconFileFromImageFamily 197 // fails gracefully when called with invalid input parameters. 198 TEST_F(IconUtilTest, TestCreateIconFileInvalidParameters) { 199 scoped_ptr<SkBitmap> bitmap; 200 gfx::ImageFamily image_family; 201 base::FilePath valid_icon_filename = temp_directory_.path().AppendASCII( 202 kTempIconFilename); 203 base::FilePath invalid_icon_filename = temp_directory_.path().AppendASCII( 204 "<>?.ico"); 205 206 // Wrong bitmap format. 207 bitmap.reset(new SkBitmap); 208 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 209 // Must allocate pixels or else ImageSkia will ignore the bitmap and just 210 // return an empty image. 211 bitmap->allocPixels(SkImageInfo::MakeA8(kSmallIconWidth, kSmallIconHeight)); 212 memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height()); 213 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 214 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 215 valid_icon_filename)); 216 EXPECT_FALSE(base::PathExists(valid_icon_filename)); 217 218 // Invalid bitmap size. 219 image_family.clear(); 220 bitmap.reset(new SkBitmap); 221 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 222 bitmap->allocPixels(SkImageInfo::MakeN32Premul(0, 0)); 223 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 224 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 225 valid_icon_filename)); 226 EXPECT_FALSE(base::PathExists(valid_icon_filename)); 227 228 // Bitmap with no allocated pixels. 229 image_family.clear(); 230 bitmap.reset(new SkBitmap); 231 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 232 bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth, 233 kSmallIconHeight)); 234 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 235 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 236 valid_icon_filename)); 237 EXPECT_FALSE(base::PathExists(valid_icon_filename)); 238 239 // Invalid file name. 240 image_family.clear(); 241 bitmap->allocPixels(); 242 // Setting the pixels to black. 243 memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height() * 4); 244 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 245 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 246 invalid_icon_filename)); 247 EXPECT_FALSE(base::PathExists(invalid_icon_filename)); 248 } 249 250 // This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if 251 // the image family is empty or invalid. 252 TEST_F(IconUtilTest, TestCreateIconFileEmptyImageFamily) { 253 base::FilePath icon_filename = temp_directory_.path().AppendASCII( 254 kTempIconFilename); 255 256 // Empty image family. 257 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(), 258 icon_filename)); 259 EXPECT_FALSE(base::PathExists(icon_filename)); 260 261 // Image family with only an empty image. 262 gfx::ImageFamily image_family; 263 image_family.Add(gfx::Image()); 264 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 265 icon_filename)); 266 EXPECT_FALSE(base::PathExists(icon_filename)); 267 } 268 269 // This test case makes sure that when we load an icon from disk and convert 270 // the HICON into a bitmap, the bitmap has the expected format and dimensions. 271 TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) { 272 scoped_ptr<SkBitmap> bitmap; 273 base::FilePath small_icon_filename = test_data_directory_.AppendASCII( 274 kSmallIconName); 275 gfx::Size small_icon_size(kSmallIconWidth, kSmallIconHeight); 276 HICON small_icon = LoadIconFromFile(small_icon_filename, 277 small_icon_size.width(), 278 small_icon_size.height()); 279 ASSERT_NE(small_icon, static_cast<HICON>(NULL)); 280 bitmap.reset(IconUtil::CreateSkBitmapFromHICON(small_icon, small_icon_size)); 281 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 282 EXPECT_EQ(bitmap->width(), small_icon_size.width()); 283 EXPECT_EQ(bitmap->height(), small_icon_size.height()); 284 EXPECT_EQ(bitmap->colorType(), kN32_SkColorType); 285 ::DestroyIcon(small_icon); 286 287 base::FilePath large_icon_filename = test_data_directory_.AppendASCII( 288 kLargeIconName); 289 gfx::Size large_icon_size(kLargeIconWidth, kLargeIconHeight); 290 HICON large_icon = LoadIconFromFile(large_icon_filename, 291 large_icon_size.width(), 292 large_icon_size.height()); 293 ASSERT_NE(large_icon, static_cast<HICON>(NULL)); 294 bitmap.reset(IconUtil::CreateSkBitmapFromHICON(large_icon, large_icon_size)); 295 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 296 EXPECT_EQ(bitmap->width(), large_icon_size.width()); 297 EXPECT_EQ(bitmap->height(), large_icon_size.height()); 298 EXPECT_EQ(bitmap->colorType(), kN32_SkColorType); 299 ::DestroyIcon(large_icon); 300 } 301 302 // This test case makes sure that when an HICON is created from an SkBitmap, 303 // the returned handle is valid and refers to an icon with the expected 304 // dimensions color depth etc. 305 TEST_F(IconUtilTest, TestBasicCreateHICONFromSkBitmap) { 306 SkBitmap bitmap = CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight); 307 HICON icon = IconUtil::CreateHICONFromSkBitmap(bitmap); 308 EXPECT_NE(icon, static_cast<HICON>(NULL)); 309 ICONINFO icon_info; 310 ASSERT_TRUE(::GetIconInfo(icon, &icon_info)); 311 EXPECT_TRUE(icon_info.fIcon); 312 313 // Now that have the icon information, we should obtain the specification of 314 // the icon's bitmap and make sure it matches the specification of the 315 // SkBitmap we started with. 316 // 317 // The bitmap handle contained in the icon information is a handle to a 318 // compatible bitmap so we need to call ::GetDIBits() in order to retrieve 319 // the bitmap's header information. 320 BITMAPINFO bitmap_info; 321 ::ZeroMemory(&bitmap_info, sizeof(BITMAPINFO)); 322 bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFO); 323 HDC hdc = ::GetDC(NULL); 324 int result = ::GetDIBits(hdc, 325 icon_info.hbmColor, 326 0, 327 kSmallIconWidth, 328 NULL, 329 &bitmap_info, 330 DIB_RGB_COLORS); 331 ASSERT_GT(result, 0); 332 EXPECT_EQ(bitmap_info.bmiHeader.biWidth, kSmallIconWidth); 333 EXPECT_EQ(bitmap_info.bmiHeader.biHeight, kSmallIconHeight); 334 EXPECT_EQ(bitmap_info.bmiHeader.biPlanes, 1); 335 EXPECT_EQ(bitmap_info.bmiHeader.biBitCount, 32); 336 ::ReleaseDC(NULL, hdc); 337 ::DestroyIcon(icon); 338 } 339 340 // This test case makes sure that CreateIconFileFromImageFamily creates a 341 // valid .ico file given an ImageFamily, and appropriately creates all icon 342 // sizes from the given input. 343 TEST_F(IconUtilTest, TestCreateIconFileFromImageFamily) { 344 gfx::ImageFamily image_family; 345 base::FilePath icon_filename = 346 temp_directory_.path().AppendASCII(kTempIconFilename); 347 348 // Test with only a 16x16 icon. Should only scale up to 48x48. 349 image_family.Add(gfx::Image::CreateFrom1xBitmap( 350 CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight))); 351 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 352 icon_filename)); 353 CheckAllIconSizes(icon_filename, 48); 354 355 // Test with a 48x48 icon. Should only scale down. 356 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(48, 48))); 357 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 358 icon_filename)); 359 CheckAllIconSizes(icon_filename, 48); 360 361 // Test with a 64x64 icon. Should scale up to 256x256. 362 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(64, 64))); 363 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 364 icon_filename)); 365 CheckAllIconSizes(icon_filename, 256); 366 367 // Test with a 256x256 icon. Should include the 256x256 in the output. 368 image_family.Add(gfx::Image::CreateFrom1xBitmap( 369 CreateBlackSkBitmap(256, 256))); 370 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 371 icon_filename)); 372 CheckAllIconSizes(icon_filename, 256); 373 374 // Test with a 49x49 icon. Should scale up to 256x256, but exclude the 375 // original 49x49 representation from the output. 376 image_family.clear(); 377 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(49, 49))); 378 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 379 icon_filename)); 380 CheckAllIconSizes(icon_filename, 256); 381 382 // Test with a non-square 16x32 icon. Should scale up to 48, but exclude the 383 // original 16x32 representation from the output. 384 image_family.clear(); 385 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 32))); 386 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 387 icon_filename)); 388 CheckAllIconSizes(icon_filename, 48); 389 390 // Test with a non-square 32x49 icon. Should scale up to 256, but exclude the 391 // original 32x49 representation from the output. 392 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(32, 49))); 393 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 394 icon_filename)); 395 CheckAllIconSizes(icon_filename, 256); 396 397 // Test with an empty and non-empty image. 398 // The empty image should be ignored. 399 image_family.clear(); 400 image_family.Add(gfx::Image()); 401 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 16))); 402 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 403 icon_filename)); 404 CheckAllIconSizes(icon_filename, 48); 405 } 406 407 TEST_F(IconUtilTest, TestCreateSkBitmapFromIconResource48x48) { 408 HMODULE module = GetModuleHandle(NULL); 409 scoped_ptr<SkBitmap> bitmap( 410 IconUtil::CreateSkBitmapFromIconResource(module, IDR_MAINFRAME, 48)); 411 ASSERT_TRUE(bitmap.get()); 412 EXPECT_EQ(48, bitmap->width()); 413 EXPECT_EQ(48, bitmap->height()); 414 } 415 416 TEST_F(IconUtilTest, TestCreateSkBitmapFromIconResource256x256) { 417 HMODULE module = GetModuleHandle(NULL); 418 scoped_ptr<SkBitmap> bitmap( 419 IconUtil::CreateSkBitmapFromIconResource(module, IDR_MAINFRAME, 256)); 420 ASSERT_TRUE(bitmap.get()); 421 EXPECT_EQ(256, bitmap->width()); 422 EXPECT_EQ(256, bitmap->height()); 423 } 424 425 // This tests that kNumIconDimensionsUpToMediumSize has the correct value. 426 TEST_F(IconUtilTest, TestNumIconDimensionsUpToMediumSize) { 427 ASSERT_LE(IconUtil::kNumIconDimensionsUpToMediumSize, 428 IconUtil::kNumIconDimensions); 429 EXPECT_EQ(IconUtil::kMediumIconSize, 430 IconUtil::kIconDimensions[ 431 IconUtil::kNumIconDimensionsUpToMediumSize - 1]); 432 } 433