// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/gfx/icon_util.h" #include #include #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/gfx_paths.h" #include "ui/gfx/icon_util_unittests_resource.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_family.h" namespace { static const char kSmallIconName[] = "icon_util/16_X_16_icon.ico"; static const char kLargeIconName[] = "icon_util/128_X_128_icon.ico"; static const char kTempIconFilename[] = "temp_test_icon.ico"; } // namespace class IconUtilTest : public testing::Test { public: using ScopedHICON = base::win::ScopedHICON; void SetUp() override { gfx::RegisterPathProvider(); ASSERT_TRUE(PathService::Get(gfx::DIR_TEST_DATA, &test_data_directory_)); ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); } static const int kSmallIconWidth = 16; static const int kSmallIconHeight = 16; static const int kLargeIconWidth = 128; static const int kLargeIconHeight = 128; // Given a file name for an .ico file and an image dimensions, this // function loads the icon and returns an HICON handle. ScopedHICON LoadIconFromFile(const base::FilePath& filename, int width, int height) { HICON icon = static_cast(LoadImage(NULL, filename.value().c_str(), IMAGE_ICON, width, height, LR_LOADTRANSPARENT | LR_LOADFROMFILE)); return ScopedHICON(icon); } SkBitmap CreateBlackSkBitmap(int width, int height) { SkBitmap bitmap; bitmap.allocN32Pixels(width, height); // Setting the pixels to transparent-black. memset(bitmap.getPixels(), 0, width * height * 4); return bitmap; } // Loads an .ico file from |icon_filename| and asserts that it contains all of // the expected icon sizes up to and including |max_icon_size|, and no other // icons. If |max_icon_size| >= 256, this tests for a 256x256 PNG icon entry. void CheckAllIconSizes(const base::FilePath& icon_filename, int max_icon_size); protected: // The root directory for test files. This should be treated as read-only. base::FilePath test_data_directory_; // Directory for creating files by this test. base::ScopedTempDir temp_directory_; }; void IconUtilTest::CheckAllIconSizes(const base::FilePath& icon_filename, int max_icon_size) { ASSERT_TRUE(base::PathExists(icon_filename)); // Determine how many icons to expect, based on |max_icon_size|. int expected_num_icons = 0; for (size_t i = 0; i < IconUtil::kNumIconDimensions; ++i) { if (IconUtil::kIconDimensions[i] > max_icon_size) break; ++expected_num_icons; } // First, use the Windows API to load the icon, a basic validity test. EXPECT_TRUE(LoadIconFromFile(icon_filename, kSmallIconWidth, kSmallIconHeight) .is_valid()); // Read the file completely into memory. std::string icon_data; ASSERT_TRUE(base::ReadFileToString(icon_filename, &icon_data)); ASSERT_GE(icon_data.length(), sizeof(IconUtil::ICONDIR)); // Ensure that it has exactly the expected number and sizes of icons, in the // expected order. This matches each entry of the loaded file's icon directory // with the corresponding element of kIconDimensions. // Also extracts the 256x256 entry as png_entry. const IconUtil::ICONDIR* icon_dir = reinterpret_cast(icon_data.data()); EXPECT_EQ(expected_num_icons, icon_dir->idCount); ASSERT_GE(IconUtil::kNumIconDimensions, icon_dir->idCount); ASSERT_GE(icon_data.length(), sizeof(IconUtil::ICONDIR) + icon_dir->idCount * sizeof(IconUtil::ICONDIRENTRY)); const IconUtil::ICONDIRENTRY* png_entry = NULL; for (size_t i = 0; i < icon_dir->idCount; ++i) { const IconUtil::ICONDIRENTRY* entry = &icon_dir->idEntries[i]; // Mod 256 because as a special case in ICONDIRENTRY, the value 0 represents // a width or height of 256. int expected_size = IconUtil::kIconDimensions[i] % 256; EXPECT_EQ(expected_size, static_cast(entry->bWidth)); EXPECT_EQ(expected_size, static_cast(entry->bHeight)); if (entry->bWidth == 0 && entry->bHeight == 0) { EXPECT_EQ(NULL, png_entry); png_entry = entry; } } if (max_icon_size >= 256) { ASSERT_TRUE(png_entry); // Convert the PNG entry data back to a SkBitmap to ensure it's valid. ASSERT_GE(icon_data.length(), png_entry->dwImageOffset + png_entry->dwBytesInRes); const unsigned char* png_bytes = reinterpret_cast( icon_data.data() + png_entry->dwImageOffset); gfx::Image image = gfx::Image::CreateFrom1xPNGBytes( png_bytes, png_entry->dwBytesInRes); SkBitmap bitmap = image.AsBitmap(); EXPECT_EQ(256, bitmap.width()); EXPECT_EQ(256, bitmap.height()); } } // The following test case makes sure IconUtil::SkBitmapFromHICON fails // gracefully when called with invalid input parameters. TEST_F(IconUtilTest, TestIconToBitmapInvalidParameters) { base::FilePath icon_filename = test_data_directory_.AppendASCII(kSmallIconName); gfx::Size icon_size(kSmallIconWidth, kSmallIconHeight); ScopedHICON icon( LoadIconFromFile(icon_filename, icon_size.width(), icon_size.height())); ASSERT_TRUE(icon.is_valid()); // Invalid size parameter. gfx::Size invalid_icon_size(kSmallIconHeight, 0); EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(icon.get(), invalid_icon_size), static_cast(NULL)); // Invalid icon. EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(NULL, icon_size), static_cast(NULL)); // The following code should succeed. scoped_ptr bitmap; bitmap.reset(IconUtil::CreateSkBitmapFromHICON(icon.get(), icon_size)); EXPECT_NE(bitmap.get(), static_cast(NULL)); } // The following test case makes sure IconUtil::CreateHICONFromSkBitmap fails // gracefully when called with invalid input parameters. TEST_F(IconUtilTest, TestBitmapToIconInvalidParameters) { ScopedHICON icon; scoped_ptr bitmap; // Wrong bitmap format. bitmap.reset(new SkBitmap); ASSERT_NE(bitmap.get(), static_cast(NULL)); bitmap->setInfo(SkImageInfo::MakeA8(kSmallIconWidth, kSmallIconHeight)); icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); EXPECT_FALSE(icon.is_valid()); // Invalid bitmap size. bitmap.reset(new SkBitmap); ASSERT_NE(bitmap.get(), static_cast(NULL)); bitmap->setInfo(SkImageInfo::MakeN32Premul(0, 0)); icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); EXPECT_FALSE(icon.is_valid()); // Valid bitmap configuration but no pixels allocated. bitmap.reset(new SkBitmap); ASSERT_NE(bitmap.get(), static_cast(NULL)); bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth, kSmallIconHeight)); icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); EXPECT_FALSE(icon.is_valid()); } // The following test case makes sure IconUtil::CreateIconFileFromImageFamily // fails gracefully when called with invalid input parameters. TEST_F(IconUtilTest, TestCreateIconFileInvalidParameters) { scoped_ptr bitmap; gfx::ImageFamily image_family; base::FilePath valid_icon_filename = temp_directory_.path().AppendASCII( kTempIconFilename); base::FilePath invalid_icon_filename = temp_directory_.path().AppendASCII( "<>?.ico"); // Wrong bitmap format. bitmap.reset(new SkBitmap); ASSERT_NE(bitmap.get(), static_cast(NULL)); // Must allocate pixels or else ImageSkia will ignore the bitmap and just // return an empty image. bitmap->allocPixels(SkImageInfo::MakeA8(kSmallIconWidth, kSmallIconHeight)); memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height()); image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, valid_icon_filename)); EXPECT_FALSE(base::PathExists(valid_icon_filename)); // Invalid bitmap size. image_family.clear(); bitmap.reset(new SkBitmap); ASSERT_NE(bitmap.get(), static_cast(NULL)); bitmap->allocPixels(SkImageInfo::MakeN32Premul(0, 0)); image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, valid_icon_filename)); EXPECT_FALSE(base::PathExists(valid_icon_filename)); // Bitmap with no allocated pixels. image_family.clear(); bitmap.reset(new SkBitmap); ASSERT_NE(bitmap.get(), static_cast(NULL)); bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth, kSmallIconHeight)); image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, valid_icon_filename)); EXPECT_FALSE(base::PathExists(valid_icon_filename)); // Invalid file name. image_family.clear(); bitmap->allocPixels(); // Setting the pixels to black. memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height() * 4); image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap)); EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, invalid_icon_filename)); EXPECT_FALSE(base::PathExists(invalid_icon_filename)); } // This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if // the image family is empty or invalid. TEST_F(IconUtilTest, TestCreateIconFileEmptyImageFamily) { base::FilePath icon_filename = temp_directory_.path().AppendASCII( kTempIconFilename); // Empty image family. EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(), icon_filename)); EXPECT_FALSE(base::PathExists(icon_filename)); // Image family with only an empty image. gfx::ImageFamily image_family; image_family.Add(gfx::Image()); EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); EXPECT_FALSE(base::PathExists(icon_filename)); } // This test case makes sure that when we load an icon from disk and convert // the HICON into a bitmap, the bitmap has the expected format and dimensions. TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) { scoped_ptr bitmap; base::FilePath small_icon_filename = test_data_directory_.AppendASCII( kSmallIconName); gfx::Size small_icon_size(kSmallIconWidth, kSmallIconHeight); ScopedHICON small_icon(LoadIconFromFile( small_icon_filename, small_icon_size.width(), small_icon_size.height())); ASSERT_TRUE(small_icon.is_valid()); bitmap.reset( IconUtil::CreateSkBitmapFromHICON(small_icon.get(), small_icon_size)); ASSERT_NE(bitmap.get(), static_cast(NULL)); EXPECT_EQ(bitmap->width(), small_icon_size.width()); EXPECT_EQ(bitmap->height(), small_icon_size.height()); EXPECT_EQ(bitmap->colorType(), kN32_SkColorType); base::FilePath large_icon_filename = test_data_directory_.AppendASCII( kLargeIconName); gfx::Size large_icon_size(kLargeIconWidth, kLargeIconHeight); ScopedHICON large_icon(LoadIconFromFile( large_icon_filename, large_icon_size.width(), large_icon_size.height())); ASSERT_TRUE(large_icon.is_valid()); bitmap.reset( IconUtil::CreateSkBitmapFromHICON(large_icon.get(), large_icon_size)); ASSERT_NE(bitmap.get(), static_cast(NULL)); EXPECT_EQ(bitmap->width(), large_icon_size.width()); EXPECT_EQ(bitmap->height(), large_icon_size.height()); EXPECT_EQ(bitmap->colorType(), kN32_SkColorType); } // This test case makes sure that when an HICON is created from an SkBitmap, // the returned handle is valid and refers to an icon with the expected // dimensions color depth etc. TEST_F(IconUtilTest, TestBasicCreateHICONFromSkBitmap) { SkBitmap bitmap = CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight); ScopedHICON icon(IconUtil::CreateHICONFromSkBitmap(bitmap)); EXPECT_TRUE(icon.is_valid()); ICONINFO icon_info; ASSERT_TRUE(GetIconInfo(icon.get(), &icon_info)); EXPECT_TRUE(icon_info.fIcon); // Now that have the icon information, we should obtain the specification of // the icon's bitmap and make sure it matches the specification of the // SkBitmap we started with. // // The bitmap handle contained in the icon information is a handle to a // compatible bitmap so we need to call ::GetDIBits() in order to retrieve // the bitmap's header information. BITMAPINFO bitmap_info; ::ZeroMemory(&bitmap_info, sizeof(BITMAPINFO)); bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFO); HDC hdc = ::GetDC(NULL); int result = ::GetDIBits(hdc, icon_info.hbmColor, 0, kSmallIconWidth, NULL, &bitmap_info, DIB_RGB_COLORS); ASSERT_GT(result, 0); EXPECT_EQ(bitmap_info.bmiHeader.biWidth, kSmallIconWidth); EXPECT_EQ(bitmap_info.bmiHeader.biHeight, kSmallIconHeight); EXPECT_EQ(bitmap_info.bmiHeader.biPlanes, 1); EXPECT_EQ(bitmap_info.bmiHeader.biBitCount, 32); ::ReleaseDC(NULL, hdc); } // This test case makes sure that CreateIconFileFromImageFamily creates a // valid .ico file given an ImageFamily, and appropriately creates all icon // sizes from the given input. TEST_F(IconUtilTest, TestCreateIconFileFromImageFamily) { gfx::ImageFamily image_family; base::FilePath icon_filename = temp_directory_.path().AppendASCII(kTempIconFilename); // Test with only a 16x16 icon. Should only scale up to 48x48. image_family.Add(gfx::Image::CreateFrom1xBitmap( CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 48); // Test with a 48x48 icon. Should only scale down. image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(48, 48))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 48); // Test with a 64x64 icon. Should scale up to 256x256. image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(64, 64))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 256); // Test with a 256x256 icon. Should include the 256x256 in the output. image_family.Add(gfx::Image::CreateFrom1xBitmap( CreateBlackSkBitmap(256, 256))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 256); // Test with a 49x49 icon. Should scale up to 256x256, but exclude the // original 49x49 representation from the output. image_family.clear(); image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(49, 49))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 256); // Test with a non-square 16x32 icon. Should scale up to 48, but exclude the // original 16x32 representation from the output. image_family.clear(); image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 32))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 48); // Test with a non-square 32x49 icon. Should scale up to 256, but exclude the // original 32x49 representation from the output. image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(32, 49))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 256); // Test with an empty and non-empty image. // The empty image should be ignored. image_family.clear(); image_family.Add(gfx::Image()); image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 16))); ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); CheckAllIconSizes(icon_filename, 48); } TEST_F(IconUtilTest, TestCreateImageFamilyFromIconResource) { HMODULE module = GetModuleHandle(NULL); scoped_ptr family( IconUtil::CreateImageFamilyFromIconResource(module, IDR_MAINFRAME)); ASSERT_TRUE(family.get()); EXPECT_FALSE(family->empty()); std::vector images; for (const auto& image : *family) images.push_back(image); // Assert that the family contains all of the images from the icon resource. EXPECT_EQ(5u, images.size()); EXPECT_EQ(16, images[0].Width()); EXPECT_EQ(24, images[1].Width()); EXPECT_EQ(32, images[2].Width()); EXPECT_EQ(48, images[3].Width()); EXPECT_EQ(256, images[4].Width()); } // This tests that kNumIconDimensionsUpToMediumSize has the correct value. TEST_F(IconUtilTest, TestNumIconDimensionsUpToMediumSize) { ASSERT_LE(IconUtil::kNumIconDimensionsUpToMediumSize, IconUtil::kNumIconDimensions); EXPECT_EQ(IconUtil::kMediumIconSize, IconUtil::kIconDimensions[ IconUtil::kNumIconDimensionsUpToMediumSize - 1]); }