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