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