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