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
19 namespace {
20
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";
24
25 } // namespace
26
27 class IconUtilTest : public testing::Test {
28 public:
SetUp()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.
LoadIconFromFile(const base::FilePath & filename,int width,int height)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
CreateBlackSkBitmap(int width,int height)52 SkBitmap CreateBlackSkBitmap(int width, int height) {
53 SkBitmap bitmap;
54 bitmap.allocN32Pixels(width, height);
55 // Setting the pixels to transparent-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
CheckAllIconSizes(const base::FilePath & icon_filename,int max_icon_size)74 void 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.
TEST_F(IconUtilTest,TestIconToBitmapInvalidParameters)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,
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.
TEST_F(IconUtilTest,TestBitmapToIconInvalidParameters)168 TEST_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->setInfo(SkImageInfo::MakeA8(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->setInfo(SkImageInfo::MakeN32Premul(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->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth,
190 kSmallIconHeight));
191 icon = IconUtil::CreateHICONFromSkBitmap(*bitmap);
192 EXPECT_TRUE(icon == NULL);
193 }
194
195 // The following test case makes sure IconUtil::CreateIconFileFromImageFamily
196 // fails gracefully when called with invalid input parameters.
TEST_F(IconUtilTest,TestCreateIconFileInvalidParameters)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(
201 kTempIconFilename);
202 base::FilePath invalid_icon_filename = temp_directory_.path().AppendASCII(
203 "<>?.ico");
204
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));
216
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));
226
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,
232 kSmallIconHeight));
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));
237
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));
247 }
248
249 // This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if
250 // the image family is empty or invalid.
TEST_F(IconUtilTest,TestCreateIconFileEmptyImageFamily)251 TEST_F(IconUtilTest, TestCreateIconFileEmptyImageFamily) {
252 base::FilePath icon_filename = temp_directory_.path().AppendASCII(
253 kTempIconFilename);
254
255 // Empty image family.
256 EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(),
257 icon_filename));
258 EXPECT_FALSE(base::PathExists(icon_filename));
259
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,
264 icon_filename));
265 EXPECT_FALSE(base::PathExists(icon_filename));
266 }
267
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.
TEST_F(IconUtilTest,TestCreateSkBitmapFromHICON)270 TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) {
271 scoped_ptr<SkBitmap> bitmap;
272 base::FilePath small_icon_filename = test_data_directory_.AppendASCII(
273 kSmallIconName);
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(), kPMColor_SkColorType);
284 ::DestroyIcon(small_icon);
285
286 base::FilePath large_icon_filename = test_data_directory_.AppendASCII(
287 kLargeIconName);
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(), kPMColor_SkColorType);
298 ::DestroyIcon(large_icon);
299 }
300
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.
TEST_F(IconUtilTest,TestBasicCreateHICONFromSkBitmap)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));
308 ICONINFO icon_info;
309 ASSERT_TRUE(::GetIconInfo(icon, &icon_info));
310 EXPECT_TRUE(icon_info.fIcon);
311
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.
315 //
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,
324 icon_info.hbmColor,
325 0,
326 kSmallIconWidth,
327 NULL,
328 &bitmap_info,
329 DIB_RGB_COLORS);
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);
336 ::DestroyIcon(icon);
337 }
338
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.
TEST_F(IconUtilTest,TestCreateIconFileFromImageFamily)342 TEST_F(IconUtilTest, TestCreateIconFileFromImageFamily) {
343 gfx::ImageFamily image_family;
344 base::FilePath icon_filename =
345 temp_directory_.path().AppendASCII(kTempIconFilename);
346
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,
351 icon_filename));
352 CheckAllIconSizes(icon_filename, 48);
353
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,
357 icon_filename));
358 CheckAllIconSizes(icon_filename, 48);
359
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,
363 icon_filename));
364 CheckAllIconSizes(icon_filename, 256);
365
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,
370 icon_filename));
371 CheckAllIconSizes(icon_filename, 256);
372
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,
378 icon_filename));
379 CheckAllIconSizes(icon_filename, 256);
380
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,
386 icon_filename));
387 CheckAllIconSizes(icon_filename, 48);
388
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,
393 icon_filename));
394 CheckAllIconSizes(icon_filename, 256);
395
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,
402 icon_filename));
403 CheckAllIconSizes(icon_filename, 48);
404 }
405
TEST_F(IconUtilTest,TestCreateSkBitmapFromIconResource48x48)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());
413 }
414
TEST_F(IconUtilTest,TestCreateSkBitmapFromIconResource256x256)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());
422 }
423
424 // This tests that kNumIconDimensionsUpToMediumSize has the correct value.
TEST_F(IconUtilTest,TestNumIconDimensionsUpToMediumSize)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]);
431 }
432