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