icon_util_unittest.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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
18namespace {
19
20static const char kSmallIconName[] = "icon_util/16_X_16_icon.ico";
21static const char kLargeIconName[] = "icon_util/128_X_128_icon.ico";
22static const char kTempIconFilename[] = "temp_test_icon.ico";
23
24}  // namespace
25
26class 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
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(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.
198TEST_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.
255TEST_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.
274TEST_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.
308TEST_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.
346TEST_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
410TEST_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
419TEST_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.
429TEST_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