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