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/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 PathService::Get(gfx::DIR_TEST_DATA, &test_data_directory_); 31 temp_directory_.CreateUniqueTempDir(); 32 } 33 34 static const int kSmallIconWidth = 16; 35 static const int kSmallIconHeight = 16; 36 static const int kLargeIconWidth = 128; 37 static const int kLargeIconHeight = 128; 38 39 // Given a file name for an .ico file and an image dimensions, this 40 // function loads the icon and returns an HICON handle. 41 HICON LoadIconFromFile(const base::FilePath& filename, 42 int width, int height) { 43 HICON icon = static_cast<HICON>(LoadImage(NULL, 44 filename.value().c_str(), 45 IMAGE_ICON, 46 width, 47 height, 48 LR_LOADTRANSPARENT | LR_LOADFROMFILE)); 49 return icon; 50 } 51 52 SkBitmap CreateBlackSkBitmap(int width, int height) { 53 SkBitmap bitmap; 54 bitmap.allocN32Pixels(width, height); 55 // Setting the pixels to transparent-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->setInfo(SkImageInfo::MakeA8(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->setInfo(SkImageInfo::MakeN32Premul(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->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth, 190 kSmallIconHeight)); 191 icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); 192 EXPECT_TRUE(icon == NULL); 193 } 194 195 // The following test case makes sure IconUtil::CreateIconFileFromImageFamily 196 // fails gracefully when called with invalid input parameters. 197 TEST_F(IconUtilTest, TestCreateIconFileInvalidParameters) { 198 scoped_ptr<SkBitmap> bitmap; 199 gfx::ImageFamily image_family; 200 base::FilePath valid_icon_filename = temp_directory_.path().AppendASCII( 201 kTempIconFilename); 202 base::FilePath invalid_icon_filename = temp_directory_.path().AppendASCII( 203 "<>?.ico"); 204 205 // Wrong bitmap format. 206 bitmap.reset(new SkBitmap); 207 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 208 // Must allocate pixels or else ImageSkia will ignore the bitmap and just 209 // return an empty image. 210 bitmap->allocPixels(SkImageInfo::MakeA8(kSmallIconWidth, kSmallIconHeight)); 211 memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height()); 212 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 213 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 214 valid_icon_filename)); 215 EXPECT_FALSE(base::PathExists(valid_icon_filename)); 216 217 // Invalid bitmap size. 218 image_family.clear(); 219 bitmap.reset(new SkBitmap); 220 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 221 bitmap->allocPixels(SkImageInfo::MakeN32Premul(0, 0)); 222 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 223 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 224 valid_icon_filename)); 225 EXPECT_FALSE(base::PathExists(valid_icon_filename)); 226 227 // Bitmap with no allocated pixels. 228 image_family.clear(); 229 bitmap.reset(new SkBitmap); 230 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 231 bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth, 232 kSmallIconHeight)); 233 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 234 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 235 valid_icon_filename)); 236 EXPECT_FALSE(base::PathExists(valid_icon_filename)); 237 238 // Invalid file name. 239 image_family.clear(); 240 bitmap->allocPixels(); 241 // Setting the pixels to black. 242 memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height() * 4); 243 image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); 244 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 245 invalid_icon_filename)); 246 EXPECT_FALSE(base::PathExists(invalid_icon_filename)); 247 } 248 249 // This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if 250 // the image family is empty or invalid. 251 TEST_F(IconUtilTest, TestCreateIconFileEmptyImageFamily) { 252 base::FilePath icon_filename = temp_directory_.path().AppendASCII( 253 kTempIconFilename); 254 255 // Empty image family. 256 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(), 257 icon_filename)); 258 EXPECT_FALSE(base::PathExists(icon_filename)); 259 260 // Image family with only an empty image. 261 gfx::ImageFamily image_family; 262 image_family.Add(gfx::Image()); 263 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, 264 icon_filename)); 265 EXPECT_FALSE(base::PathExists(icon_filename)); 266 } 267 268 // This test case makes sure that when we load an icon from disk and convert 269 // the HICON into a bitmap, the bitmap has the expected format and dimensions. 270 TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) { 271 scoped_ptr<SkBitmap> bitmap; 272 base::FilePath small_icon_filename = test_data_directory_.AppendASCII( 273 kSmallIconName); 274 gfx::Size small_icon_size(kSmallIconWidth, kSmallIconHeight); 275 HICON small_icon = LoadIconFromFile(small_icon_filename, 276 small_icon_size.width(), 277 small_icon_size.height()); 278 ASSERT_NE(small_icon, static_cast<HICON>(NULL)); 279 bitmap.reset(IconUtil::CreateSkBitmapFromHICON(small_icon, small_icon_size)); 280 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 281 EXPECT_EQ(bitmap->width(), small_icon_size.width()); 282 EXPECT_EQ(bitmap->height(), small_icon_size.height()); 283 EXPECT_EQ(bitmap->colorType(), kPMColor_SkColorType); 284 ::DestroyIcon(small_icon); 285 286 base::FilePath large_icon_filename = test_data_directory_.AppendASCII( 287 kLargeIconName); 288 gfx::Size large_icon_size(kLargeIconWidth, kLargeIconHeight); 289 HICON large_icon = LoadIconFromFile(large_icon_filename, 290 large_icon_size.width(), 291 large_icon_size.height()); 292 ASSERT_NE(large_icon, static_cast<HICON>(NULL)); 293 bitmap.reset(IconUtil::CreateSkBitmapFromHICON(large_icon, large_icon_size)); 294 ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); 295 EXPECT_EQ(bitmap->width(), large_icon_size.width()); 296 EXPECT_EQ(bitmap->height(), large_icon_size.height()); 297 EXPECT_EQ(bitmap->colorType(), kPMColor_SkColorType); 298 ::DestroyIcon(large_icon); 299 } 300 301 // This test case makes sure that when an HICON is created from an SkBitmap, 302 // the returned handle is valid and refers to an icon with the expected 303 // dimensions color depth etc. 304 TEST_F(IconUtilTest, TestBasicCreateHICONFromSkBitmap) { 305 SkBitmap bitmap = CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight); 306 HICON icon = IconUtil::CreateHICONFromSkBitmap(bitmap); 307 EXPECT_NE(icon, static_cast<HICON>(NULL)); 308 ICONINFO icon_info; 309 ASSERT_TRUE(::GetIconInfo(icon, &icon_info)); 310 EXPECT_TRUE(icon_info.fIcon); 311 312 // Now that have the icon information, we should obtain the specification of 313 // the icon's bitmap and make sure it matches the specification of the 314 // SkBitmap we started with. 315 // 316 // The bitmap handle contained in the icon information is a handle to a 317 // compatible bitmap so we need to call ::GetDIBits() in order to retrieve 318 // the bitmap's header information. 319 BITMAPINFO bitmap_info; 320 ::ZeroMemory(&bitmap_info, sizeof(BITMAPINFO)); 321 bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFO); 322 HDC hdc = ::GetDC(NULL); 323 int result = ::GetDIBits(hdc, 324 icon_info.hbmColor, 325 0, 326 kSmallIconWidth, 327 NULL, 328 &bitmap_info, 329 DIB_RGB_COLORS); 330 ASSERT_GT(result, 0); 331 EXPECT_EQ(bitmap_info.bmiHeader.biWidth, kSmallIconWidth); 332 EXPECT_EQ(bitmap_info.bmiHeader.biHeight, kSmallIconHeight); 333 EXPECT_EQ(bitmap_info.bmiHeader.biPlanes, 1); 334 EXPECT_EQ(bitmap_info.bmiHeader.biBitCount, 32); 335 ::ReleaseDC(NULL, hdc); 336 ::DestroyIcon(icon); 337 } 338 339 // This test case makes sure that CreateIconFileFromImageFamily creates a 340 // valid .ico file given an ImageFamily, and appropriately creates all icon 341 // sizes from the given input. 342 TEST_F(IconUtilTest, TestCreateIconFileFromImageFamily) { 343 gfx::ImageFamily image_family; 344 base::FilePath icon_filename = 345 temp_directory_.path().AppendASCII(kTempIconFilename); 346 347 // Test with only a 16x16 icon. Should only scale up to 48x48. 348 image_family.Add(gfx::Image::CreateFrom1xBitmap( 349 CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight))); 350 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 351 icon_filename)); 352 CheckAllIconSizes(icon_filename, 48); 353 354 // Test with a 48x48 icon. Should only scale down. 355 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(48, 48))); 356 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 357 icon_filename)); 358 CheckAllIconSizes(icon_filename, 48); 359 360 // Test with a 64x64 icon. Should scale up to 256x256. 361 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(64, 64))); 362 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 363 icon_filename)); 364 CheckAllIconSizes(icon_filename, 256); 365 366 // Test with a 256x256 icon. Should include the 256x256 in the output. 367 image_family.Add(gfx::Image::CreateFrom1xBitmap( 368 CreateBlackSkBitmap(256, 256))); 369 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 370 icon_filename)); 371 CheckAllIconSizes(icon_filename, 256); 372 373 // Test with a 49x49 icon. Should scale up to 256x256, but exclude the 374 // original 49x49 representation from the output. 375 image_family.clear(); 376 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(49, 49))); 377 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 378 icon_filename)); 379 CheckAllIconSizes(icon_filename, 256); 380 381 // Test with a non-square 16x32 icon. Should scale up to 48, but exclude the 382 // original 16x32 representation from the output. 383 image_family.clear(); 384 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 32))); 385 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 386 icon_filename)); 387 CheckAllIconSizes(icon_filename, 48); 388 389 // Test with a non-square 32x49 icon. Should scale up to 256, but exclude the 390 // original 32x49 representation from the output. 391 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(32, 49))); 392 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 393 icon_filename)); 394 CheckAllIconSizes(icon_filename, 256); 395 396 // Test with an empty and non-empty image. 397 // The empty image should be ignored. 398 image_family.clear(); 399 image_family.Add(gfx::Image()); 400 image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 16))); 401 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, 402 icon_filename)); 403 CheckAllIconSizes(icon_filename, 48); 404 } 405 406 TEST_F(IconUtilTest, TestCreateSkBitmapFromIconResource48x48) { 407 HMODULE module = GetModuleHandle(NULL); 408 scoped_ptr<SkBitmap> bitmap( 409 IconUtil::CreateSkBitmapFromIconResource(module, IDR_MAINFRAME, 48)); 410 ASSERT_TRUE(bitmap.get()); 411 EXPECT_EQ(48, bitmap->width()); 412 EXPECT_EQ(48, bitmap->height()); 413 } 414 415 TEST_F(IconUtilTest, TestCreateSkBitmapFromIconResource256x256) { 416 HMODULE module = GetModuleHandle(NULL); 417 scoped_ptr<SkBitmap> bitmap( 418 IconUtil::CreateSkBitmapFromIconResource(module, IDR_MAINFRAME, 256)); 419 ASSERT_TRUE(bitmap.get()); 420 EXPECT_EQ(256, bitmap->width()); 421 EXPECT_EQ(256, bitmap->height()); 422 } 423 424 // This tests that kNumIconDimensionsUpToMediumSize has the correct value. 425 TEST_F(IconUtilTest, TestNumIconDimensionsUpToMediumSize) { 426 ASSERT_LE(IconUtil::kNumIconDimensionsUpToMediumSize, 427 IconUtil::kNumIconDimensions); 428 EXPECT_EQ(IconUtil::kMediumIconSize, 429 IconUtil::kIconDimensions[ 430 IconUtil::kNumIconDimensionsUpToMediumSize - 1]); 431 } 432