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/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/geometry/size.h"
14 #include "ui/gfx/gfx_paths.h"
15 #include "ui/gfx/icon_util_unittests_resource.h"
16 #include "ui/gfx/image/image.h"
17 #include "ui/gfx/image/image_family.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 void SetUp() override
{
30 gfx::RegisterPathProvider();
31 ASSERT_TRUE(PathService::Get(gfx::DIR_TEST_DATA
, &test_data_directory_
));
32 temp_directory_
.CreateUniqueTempDir();
35 static const int kSmallIconWidth
= 16;
36 static const int kSmallIconHeight
= 16;
37 static const int kLargeIconWidth
= 128;
38 static const int kLargeIconHeight
= 128;
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(),
49 LR_LOADTRANSPARENT
| LR_LOADFROMFILE
));
53 SkBitmap
CreateBlackSkBitmap(int width
, int height
) {
55 bitmap
.allocN32Pixels(width
, height
);
56 // Setting the pixels to transparent-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
->setInfo(SkImageInfo::MakeA8(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
->setInfo(SkImageInfo::MakeN32Premul(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
->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth
,
192 icon
= IconUtil::CreateHICONFromSkBitmap(*bitmap
);
193 EXPECT_TRUE(icon
== NULL
);
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(
203 base::FilePath invalid_icon_filename
= temp_directory_
.path().AppendASCII(
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
));
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
));
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
,
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
));
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
));
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(
256 // Empty image family.
257 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(),
259 EXPECT_FALSE(base::PathExists(icon_filename
));
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
,
266 EXPECT_FALSE(base::PathExists(icon_filename
));
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(
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
);
287 base::FilePath large_icon_filename
= test_data_directory_
.AppendASCII(
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
);
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
));
310 ASSERT_TRUE(::GetIconInfo(icon
, &icon_info
));
311 EXPECT_TRUE(icon_info
.fIcon
);
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.
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
,
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
);
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
);
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
,
353 CheckAllIconSizes(icon_filename
, 48);
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
,
359 CheckAllIconSizes(icon_filename
, 48);
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
,
365 CheckAllIconSizes(icon_filename
, 256);
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
,
372 CheckAllIconSizes(icon_filename
, 256);
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
,
380 CheckAllIconSizes(icon_filename
, 256);
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
,
388 CheckAllIconSizes(icon_filename
, 48);
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
,
395 CheckAllIconSizes(icon_filename
, 256);
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
,
404 CheckAllIconSizes(icon_filename
, 48);
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());
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());
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]);