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