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
.allocN32Pixels(width
, height
);
55 // Setting the pixels to transparent-black.
56 memset(bitmap
.getPixels(), 0, width
* height
* 4);
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
,
67 // The root directory for test files. This should be treated as read-only.
68 base::FilePath test_data_directory_
;
70 // Directory for creating files by this test.
71 base::ScopedTempDir temp_directory_
;
74 void IconUtilTest::CheckAllIconSizes(const base::FilePath
& icon_filename
,
76 ASSERT_TRUE(base::PathExists(icon_filename
));
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
)
86 // First, use the Windows API to load the icon, a basic validity test.
87 HICON icon
= LoadIconFromFile(icon_filename
, kSmallIconWidth
,
89 EXPECT_NE(static_cast<HICON
>(NULL
), icon
);
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
));
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
);
123 if (max_icon_size
>= 256) {
124 ASSERT_TRUE(png_entry
);
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());
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
,
148 ASSERT_TRUE(icon
!= NULL
);
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
));
156 EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(NULL
, icon_size
),
157 static_cast<SkBitmap
*>(NULL
));
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
));
166 // The following test case makes sure IconUtil::CreateHICONFromSkBitmap fails
167 // gracefully when called with invalid input parameters.
168 TEST_F(IconUtilTest
, TestBitmapToIconInvalidParameters
) {
170 scoped_ptr
<SkBitmap
> bitmap
;
172 // Wrong bitmap format.
173 bitmap
.reset(new SkBitmap
);
174 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
175 bitmap
->setInfo(SkImageInfo::MakeA8(kSmallIconWidth
, kSmallIconHeight
));
176 icon
= IconUtil::CreateHICONFromSkBitmap(*bitmap
);
177 EXPECT_EQ(icon
, static_cast<HICON
>(NULL
));
179 // Invalid bitmap size.
180 bitmap
.reset(new SkBitmap
);
181 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
182 bitmap
->setInfo(SkImageInfo::MakeN32Premul(0, 0));
183 icon
= IconUtil::CreateHICONFromSkBitmap(*bitmap
);
184 EXPECT_EQ(icon
, static_cast<HICON
>(NULL
));
186 // Valid bitmap configuration but no pixels allocated.
187 bitmap
.reset(new SkBitmap
);
188 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
189 bitmap
->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth
,
191 icon
= IconUtil::CreateHICONFromSkBitmap(*bitmap
);
192 EXPECT_TRUE(icon
== NULL
);
195 // The following test case makes sure IconUtil::CreateIconFileFromImageFamily
196 // fails gracefully when called with invalid input parameters.
197 TEST_F(IconUtilTest
, TestCreateIconFileInvalidParameters
) {
198 scoped_ptr
<SkBitmap
> bitmap
;
199 gfx::ImageFamily image_family
;
200 base::FilePath valid_icon_filename
= temp_directory_
.path().AppendASCII(
202 base::FilePath invalid_icon_filename
= temp_directory_
.path().AppendASCII(
205 // Wrong bitmap format.
206 bitmap
.reset(new SkBitmap
);
207 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
208 // Must allocate pixels or else ImageSkia will ignore the bitmap and just
209 // return an empty image.
210 bitmap
->allocPixels(SkImageInfo::MakeA8(kSmallIconWidth
, kSmallIconHeight
));
211 memset(bitmap
->getPixels(), 0, bitmap
->width() * bitmap
->height());
212 image_family
.Add(gfx::Image::CreateFrom1xBitmap(*bitmap
));
213 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family
,
214 valid_icon_filename
));
215 EXPECT_FALSE(base::PathExists(valid_icon_filename
));
217 // Invalid bitmap size.
218 image_family
.clear();
219 bitmap
.reset(new SkBitmap
);
220 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
221 bitmap
->allocPixels(SkImageInfo::MakeN32Premul(0, 0));
222 image_family
.Add(gfx::Image::CreateFrom1xBitmap(*bitmap
));
223 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family
,
224 valid_icon_filename
));
225 EXPECT_FALSE(base::PathExists(valid_icon_filename
));
227 // Bitmap with no allocated pixels.
228 image_family
.clear();
229 bitmap
.reset(new SkBitmap
);
230 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
231 bitmap
->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth
,
233 image_family
.Add(gfx::Image::CreateFrom1xBitmap(*bitmap
));
234 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family
,
235 valid_icon_filename
));
236 EXPECT_FALSE(base::PathExists(valid_icon_filename
));
238 // Invalid file name.
239 image_family
.clear();
240 bitmap
->allocPixels();
241 // Setting the pixels to black.
242 memset(bitmap
->getPixels(), 0, bitmap
->width() * bitmap
->height() * 4);
243 image_family
.Add(gfx::Image::CreateFrom1xBitmap(*bitmap
));
244 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family
,
245 invalid_icon_filename
));
246 EXPECT_FALSE(base::PathExists(invalid_icon_filename
));
249 // This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if
250 // the image family is empty or invalid.
251 TEST_F(IconUtilTest
, TestCreateIconFileEmptyImageFamily
) {
252 base::FilePath icon_filename
= temp_directory_
.path().AppendASCII(
255 // Empty image family.
256 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(),
258 EXPECT_FALSE(base::PathExists(icon_filename
));
260 // Image family with only an empty image.
261 gfx::ImageFamily image_family
;
262 image_family
.Add(gfx::Image());
263 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family
,
265 EXPECT_FALSE(base::PathExists(icon_filename
));
268 // This test case makes sure that when we load an icon from disk and convert
269 // the HICON into a bitmap, the bitmap has the expected format and dimensions.
270 TEST_F(IconUtilTest
, TestCreateSkBitmapFromHICON
) {
271 scoped_ptr
<SkBitmap
> bitmap
;
272 base::FilePath small_icon_filename
= test_data_directory_
.AppendASCII(
274 gfx::Size
small_icon_size(kSmallIconWidth
, kSmallIconHeight
);
275 HICON small_icon
= LoadIconFromFile(small_icon_filename
,
276 small_icon_size
.width(),
277 small_icon_size
.height());
278 ASSERT_NE(small_icon
, static_cast<HICON
>(NULL
));
279 bitmap
.reset(IconUtil::CreateSkBitmapFromHICON(small_icon
, small_icon_size
));
280 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
281 EXPECT_EQ(bitmap
->width(), small_icon_size
.width());
282 EXPECT_EQ(bitmap
->height(), small_icon_size
.height());
283 EXPECT_EQ(bitmap
->colorType(), kN32_SkColorType
);
284 ::DestroyIcon(small_icon
);
286 base::FilePath large_icon_filename
= test_data_directory_
.AppendASCII(
288 gfx::Size
large_icon_size(kLargeIconWidth
, kLargeIconHeight
);
289 HICON large_icon
= LoadIconFromFile(large_icon_filename
,
290 large_icon_size
.width(),
291 large_icon_size
.height());
292 ASSERT_NE(large_icon
, static_cast<HICON
>(NULL
));
293 bitmap
.reset(IconUtil::CreateSkBitmapFromHICON(large_icon
, large_icon_size
));
294 ASSERT_NE(bitmap
.get(), static_cast<SkBitmap
*>(NULL
));
295 EXPECT_EQ(bitmap
->width(), large_icon_size
.width());
296 EXPECT_EQ(bitmap
->height(), large_icon_size
.height());
297 EXPECT_EQ(bitmap
->colorType(), kN32_SkColorType
);
298 ::DestroyIcon(large_icon
);
301 // This test case makes sure that when an HICON is created from an SkBitmap,
302 // the returned handle is valid and refers to an icon with the expected
303 // dimensions color depth etc.
304 TEST_F(IconUtilTest
, TestBasicCreateHICONFromSkBitmap
) {
305 SkBitmap bitmap
= CreateBlackSkBitmap(kSmallIconWidth
, kSmallIconHeight
);
306 HICON icon
= IconUtil::CreateHICONFromSkBitmap(bitmap
);
307 EXPECT_NE(icon
, static_cast<HICON
>(NULL
));
309 ASSERT_TRUE(::GetIconInfo(icon
, &icon_info
));
310 EXPECT_TRUE(icon_info
.fIcon
);
312 // Now that have the icon information, we should obtain the specification of
313 // the icon's bitmap and make sure it matches the specification of the
314 // SkBitmap we started with.
316 // The bitmap handle contained in the icon information is a handle to a
317 // compatible bitmap so we need to call ::GetDIBits() in order to retrieve
318 // the bitmap's header information.
319 BITMAPINFO bitmap_info
;
320 ::ZeroMemory(&bitmap_info
, sizeof(BITMAPINFO
));
321 bitmap_info
.bmiHeader
.biSize
= sizeof(BITMAPINFO
);
322 HDC hdc
= ::GetDC(NULL
);
323 int result
= ::GetDIBits(hdc
,
330 ASSERT_GT(result
, 0);
331 EXPECT_EQ(bitmap_info
.bmiHeader
.biWidth
, kSmallIconWidth
);
332 EXPECT_EQ(bitmap_info
.bmiHeader
.biHeight
, kSmallIconHeight
);
333 EXPECT_EQ(bitmap_info
.bmiHeader
.biPlanes
, 1);
334 EXPECT_EQ(bitmap_info
.bmiHeader
.biBitCount
, 32);
335 ::ReleaseDC(NULL
, hdc
);
339 // This test case makes sure that CreateIconFileFromImageFamily creates a
340 // valid .ico file given an ImageFamily, and appropriately creates all icon
341 // sizes from the given input.
342 TEST_F(IconUtilTest
, TestCreateIconFileFromImageFamily
) {
343 gfx::ImageFamily image_family
;
344 base::FilePath icon_filename
=
345 temp_directory_
.path().AppendASCII(kTempIconFilename
);
347 // Test with only a 16x16 icon. Should only scale up to 48x48.
348 image_family
.Add(gfx::Image::CreateFrom1xBitmap(
349 CreateBlackSkBitmap(kSmallIconWidth
, kSmallIconHeight
)));
350 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
352 CheckAllIconSizes(icon_filename
, 48);
354 // Test with a 48x48 icon. Should only scale down.
355 image_family
.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(48, 48)));
356 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
358 CheckAllIconSizes(icon_filename
, 48);
360 // Test with a 64x64 icon. Should scale up to 256x256.
361 image_family
.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(64, 64)));
362 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
364 CheckAllIconSizes(icon_filename
, 256);
366 // Test with a 256x256 icon. Should include the 256x256 in the output.
367 image_family
.Add(gfx::Image::CreateFrom1xBitmap(
368 CreateBlackSkBitmap(256, 256)));
369 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
371 CheckAllIconSizes(icon_filename
, 256);
373 // Test with a 49x49 icon. Should scale up to 256x256, but exclude the
374 // original 49x49 representation from the output.
375 image_family
.clear();
376 image_family
.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(49, 49)));
377 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
379 CheckAllIconSizes(icon_filename
, 256);
381 // Test with a non-square 16x32 icon. Should scale up to 48, but exclude the
382 // original 16x32 representation from the output.
383 image_family
.clear();
384 image_family
.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 32)));
385 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
387 CheckAllIconSizes(icon_filename
, 48);
389 // Test with a non-square 32x49 icon. Should scale up to 256, but exclude the
390 // original 32x49 representation from the output.
391 image_family
.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(32, 49)));
392 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
394 CheckAllIconSizes(icon_filename
, 256);
396 // Test with an empty and non-empty image.
397 // The empty image should be ignored.
398 image_family
.clear();
399 image_family
.Add(gfx::Image());
400 image_family
.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 16)));
401 ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family
,
403 CheckAllIconSizes(icon_filename
, 48);
406 TEST_F(IconUtilTest
, TestCreateSkBitmapFromIconResource48x48
) {
407 HMODULE module
= GetModuleHandle(NULL
);
408 scoped_ptr
<SkBitmap
> bitmap(
409 IconUtil::CreateSkBitmapFromIconResource(module
, IDR_MAINFRAME
, 48));
410 ASSERT_TRUE(bitmap
.get());
411 EXPECT_EQ(48, bitmap
->width());
412 EXPECT_EQ(48, bitmap
->height());
415 TEST_F(IconUtilTest
, TestCreateSkBitmapFromIconResource256x256
) {
416 HMODULE module
= GetModuleHandle(NULL
);
417 scoped_ptr
<SkBitmap
> bitmap(
418 IconUtil::CreateSkBitmapFromIconResource(module
, IDR_MAINFRAME
, 256));
419 ASSERT_TRUE(bitmap
.get());
420 EXPECT_EQ(256, bitmap
->width());
421 EXPECT_EQ(256, bitmap
->height());
424 // This tests that kNumIconDimensionsUpToMediumSize has the correct value.
425 TEST_F(IconUtilTest
, TestNumIconDimensionsUpToMediumSize
) {
426 ASSERT_LE(IconUtil::kNumIconDimensionsUpToMediumSize
,
427 IconUtil::kNumIconDimensions
);
428 EXPECT_EQ(IconUtil::kMediumIconSize
,
429 IconUtil::kIconDimensions
[
430 IconUtil::kNumIconDimensionsUpToMediumSize
- 1]);