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.
5 #include "ui/gfx/icon_util.h"
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"
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";
27 class IconUtilTest : public testing::Test {
29 virtual void SetUp() OVERRIDE {
30 PathService::Get(gfx::DIR_TEST_DATA, &test_data_directory_);
31 temp_directory_.CreateUniqueTempDir();
34 static const int kSmallIconWidth = 16;
35 static const int kSmallIconHeight = 16;
36 static const int kLargeIconWidth = 128;
37 static const int kLargeIconHeight = 128;
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(),
48 LR_LOADTRANSPARENT | LR_LOADFROMFILE));
52 SkBitmap CreateBlackSkBitmap(int width, int height) {
54 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
56 // Setting the pixels to black.
57 memset(bitmap.getPixels(), 0, width * height * 4);
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,
68 // The root directory for test files. This should be treated as read-only.
69 base::FilePath test_data_directory_;
71 // Directory for creating files by this test.
72 base::ScopedTempDir temp_directory_;
75 void IconUtilTest::CheckAllIconSizes(const base::FilePath& icon_filename,
77 ASSERT_TRUE(base::PathExists(icon_filename));
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)
87 // First, use the Windows API to load the icon, a basic validity test.
88 HICON icon = LoadIconFromFile(icon_filename, kSmallIconWidth,
90 EXPECT_NE(static_cast<HICON>(NULL), icon);
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));
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);
124 if (max_icon_size >= 256) {
125 ASSERT_TRUE(png_entry);
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());
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,
149 ASSERT_TRUE(icon != NULL);
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));
157 EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(NULL, icon_size),
158 static_cast<SkBitmap*>(NULL));
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));
167 // The following test case makes sure IconUtil::CreateHICONFromSkBitmap fails
168 // gracefully when called with invalid input parameters.
169 TEST_F(IconUtilTest, TestBitmapToIconInvalidParameters) {
171 scoped_ptr<SkBitmap> bitmap;
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));
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));
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,
193 icon = IconUtil::CreateHICONFromSkBitmap(*bitmap);
194 EXPECT_TRUE(icon == NULL);
197 // The following test case makes sure IconUtil::CreateIconFileFromImageFamily
198 // fails gracefully when called with invalid input parameters.
199 TEST_F(IconUtilTest, TestCreateIconFileInvalidParameters) {
200 scoped_ptr<SkBitmap> bitmap;
201 gfx::ImageFamily image_family;
202 base::FilePath valid_icon_filename = temp_directory_.path().AppendASCII(
204 base::FilePath invalid_icon_filename = temp_directory_.path().AppendASCII(
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));
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));
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,
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));
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));
254 // This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if
255 // the image family is empty or invalid.
256 TEST_F(IconUtilTest, TestCreateIconFileEmptyImageFamily) {
257 base::FilePath icon_filename = temp_directory_.path().AppendASCII(
260 // Empty image family.
261 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(),
263 EXPECT_FALSE(base::PathExists(icon_filename));
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,
270 EXPECT_FALSE(base::PathExists(icon_filename));
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.
275 TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) {
276 scoped_ptr<SkBitmap> bitmap;
277 base::FilePath small_icon_filename = test_data_directory_.AppendASCII(
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);
291 base::FilePath large_icon_filename = test_data_directory_.AppendASCII(
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);
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.
309 TEST_F(IconUtilTest, TestBasicCreateHICONFromSkBitmap) {
310 SkBitmap bitmap = CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight);
311 HICON icon = IconUtil::CreateHICONFromSkBitmap(bitmap);
312 EXPECT_NE(icon, static_cast<HICON>(NULL));
314 ASSERT_TRUE(::GetIconInfo(icon, &icon_info));
315 EXPECT_TRUE(icon_info.fIcon);
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.
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,
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);
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.
347 TEST_F(IconUtilTest, TestCreateIconFileFromImageFamily) {
348 gfx::ImageFamily image_family;
349 base::FilePath icon_filename =
350 temp_directory_.path().AppendASCII(kTempIconFilename);
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,
357 CheckAllIconSizes(icon_filename, 48);
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,
363 CheckAllIconSizes(icon_filename, 48);
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,
369 CheckAllIconSizes(icon_filename, 256);
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,
376 CheckAllIconSizes(icon_filename, 256);
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,
384 CheckAllIconSizes(icon_filename, 256);
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,
392 CheckAllIconSizes(icon_filename, 48);
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,
399 CheckAllIconSizes(icon_filename, 256);
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,
408 CheckAllIconSizes(icon_filename, 48);
411 TEST_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());
420 TEST_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());
429 // This tests that kNumIconDimensionsUpToMediumSize has the correct value.
430 TEST_F(IconUtilTest, TestNumIconDimensionsUpToMediumSize) {
431 ASSERT_LE(IconUtil::kNumIconDimensionsUpToMediumSize,
432 IconUtil::kNumIconDimensions);
433 EXPECT_EQ(IconUtil::kMediumIconSize,
434 IconUtil::kIconDimensions[
435 IconUtil::kNumIconDimensionsUpToMediumSize - 1]);