1 // Copyright (c) 2012 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/logging.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/win/scoped_gdi_object.h"
11 #include "base/win/scoped_handle.h"
12 #include "base/win/scoped_hdc.h"
13 #include "skia/ext/image_operations.h"
14 #include "third_party/skia/include/core/SkBitmap.h"
15 #include "ui/gfx/gdi_util.h"
16 #include "ui/gfx/size.h"
19 struct ScopedICONINFO
: ICONINFO
{
27 ::DeleteObject(hbmColor
);
29 ::DeleteObject(hbmMask
);
34 // Defining the dimensions for the icon images. We store only one value because
35 // we always resize to a square image; that is, the value 48 means that we are
36 // going to resize the given bitmap to a 48 by 48 pixels bitmap.
38 // The icon images appear in the icon file in same order in which their
39 // corresponding dimensions appear in the |icon_dimensions_| array, so it is
40 // important to keep this array sorted. Also note that the maximum icon image
41 // size we can handle is 255 by 255.
42 const int IconUtil::icon_dimensions_
[] = {
43 8, // Recommended by the MSDN as a nice to have icon size.
44 10, // Used by the Shell (e.g. for shortcuts).
45 14, // Recommended by the MSDN as a nice to have icon size.
46 16, // Toolbar, Application and Shell icon sizes.
47 22, // Recommended by the MSDN as a nice to have icon size.
48 24, // Used by the Shell (e.g. for shortcuts).
49 32, // Toolbar, Dialog and Wizard icon size.
51 48, // Alt+Tab icon size.
52 64, // Recommended by the MSDN as a nice to have icon size.
53 96, // Recommended by the MSDN as a nice to have icon size.
54 128 // Used by the Shell (e.g. for shortcuts).
57 HICON
IconUtil::CreateHICONFromSkBitmap(const SkBitmap
& bitmap
) {
58 // Only 32 bit ARGB bitmaps are supported. We also try to perform as many
59 // validations as we can on the bitmap.
60 SkAutoLockPixels
bitmap_lock(bitmap
);
61 if ((bitmap
.config() != SkBitmap::kARGB_8888_Config
) ||
62 (bitmap
.width() <= 0) || (bitmap
.height() <= 0) ||
63 (bitmap
.getPixels() == NULL
))
66 // We start by creating a DIB which we'll use later on in order to create
67 // the HICON. We use BITMAPV5HEADER since the bitmap we are about to convert
68 // may contain an alpha channel and the V5 header allows us to specify the
69 // alpha mask for the DIB.
70 BITMAPV5HEADER bitmap_header
;
71 InitializeBitmapHeader(&bitmap_header
, bitmap
.width(), bitmap
.height());
73 HDC hdc
= ::GetDC(NULL
);
75 dib
= ::CreateDIBSection(hdc
, reinterpret_cast<BITMAPINFO
*>(&bitmap_header
),
76 DIB_RGB_COLORS
, &bits
, NULL
, 0);
78 ::ReleaseDC(NULL
, hdc
);
79 memcpy(bits
, bitmap
.getPixels(), bitmap
.width() * bitmap
.height() * 4);
81 // Icons are generally created using an AND and XOR masks where the AND
82 // specifies boolean transparency (the pixel is either opaque or
83 // transparent) and the XOR mask contains the actual image pixels. If the XOR
84 // mask bitmap has an alpha channel, the AND monochrome bitmap won't
85 // actually be used for computing the pixel transparency. Even though all our
86 // bitmap has an alpha channel, Windows might not agree when all alpha values
87 // are zero. So the monochrome bitmap is created with all pixels transparent
88 // for this case. Otherwise, it is created with all pixels opaque.
89 bool bitmap_has_alpha_channel
= PixelsHaveAlpha(
90 static_cast<const uint32
*>(bitmap
.getPixels()),
91 bitmap
.width() * bitmap
.height());
93 scoped_array
<uint8
> mask_bits
;
94 if (!bitmap_has_alpha_channel
) {
95 // Bytes per line with paddings to make it word alignment.
96 size_t bytes_per_line
= (bitmap
.width() + 0xF) / 16 * 2;
97 size_t mask_bits_size
= bytes_per_line
* bitmap
.height();
99 mask_bits
.reset(new uint8
[mask_bits_size
]);
100 DCHECK(mask_bits
.get());
102 // Make all pixels transparent.
103 memset(mask_bits
.get(), 0xFF, mask_bits_size
);
106 HBITMAP mono_bitmap
= ::CreateBitmap(bitmap
.width(), bitmap
.height(), 1, 1,
107 reinterpret_cast<LPVOID
>(mask_bits
.get()));
111 icon_info
.fIcon
= TRUE
;
112 icon_info
.xHotspot
= 0;
113 icon_info
.yHotspot
= 0;
114 icon_info
.hbmMask
= mono_bitmap
;
115 icon_info
.hbmColor
= dib
;
116 HICON icon
= ::CreateIconIndirect(&icon_info
);
118 ::DeleteObject(mono_bitmap
);
122 SkBitmap
* IconUtil::CreateSkBitmapFromHICON(HICON icon
, const gfx::Size
& s
) {
123 // We start with validating parameters.
124 if (!icon
|| s
.IsEmpty())
126 ScopedICONINFO icon_info
;
127 if (!::GetIconInfo(icon
, &icon_info
))
129 if (!icon_info
.fIcon
)
131 return new SkBitmap(CreateSkBitmapFromHICONHelper(icon
, s
));
134 SkBitmap
* IconUtil::CreateSkBitmapFromHICON(HICON icon
) {
135 // We start with validating parameters.
139 ScopedICONINFO icon_info
;
140 BITMAP bitmap_info
= { 0 };
142 if (!::GetIconInfo(icon
, &icon_info
))
145 if (!::GetObject(icon_info
.hbmMask
, sizeof(bitmap_info
), &bitmap_info
))
148 gfx::Size
icon_size(bitmap_info
.bmWidth
, bitmap_info
.bmHeight
);
149 return new SkBitmap(CreateSkBitmapFromHICONHelper(icon
, icon_size
));
152 HICON
IconUtil::CreateCursorFromDIB(const gfx::Size
& icon_size
,
153 const gfx::Point
& hotspot
,
154 const void* dib_bits
,
156 BITMAPINFO icon_bitmap_info
= {0};
157 gfx::CreateBitmapHeader(
160 reinterpret_cast<BITMAPINFOHEADER
*>(&icon_bitmap_info
));
162 base::win::ScopedGetDC
dc(NULL
);
163 base::win::ScopedCreateDC
working_dc(CreateCompatibleDC(dc
));
164 base::win::ScopedGDIObject
<HBITMAP
> bitmap_handle(
181 HBITMAP old_bitmap
= reinterpret_cast<HBITMAP
>(
182 SelectObject(working_dc
, bitmap_handle
));
183 SetBkMode(working_dc
, TRANSPARENT
);
184 SelectObject(working_dc
, old_bitmap
);
186 base::win::ScopedGDIObject
<HBITMAP
> mask(
187 CreateBitmap(icon_size
.width(),
194 ii
.xHotspot
= hotspot
.x();
195 ii
.yHotspot
= hotspot
.y();
197 ii
.hbmColor
= bitmap_handle
;
199 return CreateIconIndirect(&ii
);
202 SkBitmap
IconUtil::CreateSkBitmapFromHICONHelper(HICON icon
,
203 const gfx::Size
& s
) {
205 DCHECK(!s
.IsEmpty());
207 // Allocating memory for the SkBitmap object. We are going to create an ARGB
208 // bitmap so we should set the configuration appropriately.
210 bitmap
.setConfig(SkBitmap::kARGB_8888_Config
, s
.width(), s
.height());
211 bitmap
.allocPixels();
212 bitmap
.eraseARGB(0, 0, 0, 0);
213 SkAutoLockPixels
bitmap_lock(bitmap
);
215 // Now we should create a DIB so that we can use ::DrawIconEx in order to
216 // obtain the icon's image.
218 InitializeBitmapHeader(&h
, s
.width(), s
.height());
219 HDC hdc
= ::GetDC(NULL
);
221 HBITMAP dib
= ::CreateDIBSection(hdc
, reinterpret_cast<BITMAPINFO
*>(&h
),
222 DIB_RGB_COLORS
, reinterpret_cast<void**>(&bits
), NULL
, 0);
224 HDC dib_dc
= CreateCompatibleDC(hdc
);
225 ::ReleaseDC(NULL
, hdc
);
227 HGDIOBJ old_obj
= ::SelectObject(dib_dc
, dib
);
229 // Windows icons are defined using two different masks. The XOR mask, which
230 // represents the icon image and an AND mask which is a monochrome bitmap
231 // which indicates the transparency of each pixel.
233 // To make things more complex, the icon image itself can be an ARGB bitmap
234 // and therefore contain an alpha channel which specifies the transparency
235 // for each pixel. Unfortunately, there is no easy way to determine whether
236 // or not a bitmap has an alpha channel and therefore constructing the bitmap
237 // for the icon is nothing but straightforward.
239 // The idea is to read the AND mask but use it only if we know for sure that
240 // the icon image does not have an alpha channel. The only way to tell if the
241 // bitmap has an alpha channel is by looking through the pixels and checking
242 // whether there are non-zero alpha bytes.
244 // We start by drawing the AND mask into our DIB.
245 size_t num_pixels
= s
.GetArea();
246 memset(bits
, 0, num_pixels
* 4);
247 ::DrawIconEx(dib_dc
, 0, 0, icon
, s
.width(), s
.height(), 0, NULL
, DI_MASK
);
249 // Capture boolean opacity. We may not use it if we find out the bitmap has
251 scoped_array
<bool> opaque(new bool[num_pixels
]);
252 for (size_t i
= 0; i
< num_pixels
; ++i
)
253 opaque
[i
] = !bits
[i
];
255 // Then draw the image itself which is really the XOR mask.
256 memset(bits
, 0, num_pixels
* 4);
257 ::DrawIconEx(dib_dc
, 0, 0, icon
, s
.width(), s
.height(), 0, NULL
, DI_NORMAL
);
258 memcpy(bitmap
.getPixels(), static_cast<void*>(bits
), num_pixels
* 4);
260 // Finding out whether the bitmap has an alpha channel.
261 bool bitmap_has_alpha_channel
= PixelsHaveAlpha(
262 static_cast<const uint32
*>(bitmap
.getPixels()), num_pixels
);
264 // If the bitmap does not have an alpha channel, we need to build it using
265 // the previously captured AND mask. Otherwise, we are done.
266 if (!bitmap_has_alpha_channel
) {
267 uint32
* p
= static_cast<uint32
*>(bitmap
.getPixels());
268 for (size_t i
= 0; i
< num_pixels
; ++p
, ++i
) {
269 DCHECK_EQ((*p
& 0xff000000), 0u);
277 ::SelectObject(dib_dc
, old_obj
);
284 bool IconUtil::CreateIconFileFromSkBitmap(const SkBitmap
& bitmap
,
285 const FilePath
& icon_path
) {
286 // Only 32 bit ARGB bitmaps are supported. We also make sure the bitmap has
287 // been properly initialized.
288 SkAutoLockPixels
bitmap_lock(bitmap
);
289 if ((bitmap
.config() != SkBitmap::kARGB_8888_Config
) ||
290 (bitmap
.height() <= 0) || (bitmap
.width() <= 0) ||
291 (bitmap
.getPixels() == NULL
))
294 // We start by creating the file.
295 base::win::ScopedHandle
icon_file(::CreateFile(icon_path
.value().c_str(),
296 GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
));
298 if (!icon_file
.IsValid())
301 // Creating a set of bitmaps corresponding to the icon images we'll end up
302 // storing in the icon file. Each bitmap is created by resizing the given
303 // bitmap to the desired size.
304 std::vector
<SkBitmap
> bitmaps
;
305 CreateResizedBitmapSet(bitmap
, &bitmaps
);
306 DCHECK(!bitmaps
.empty());
307 size_t bitmap_count
= bitmaps
.size();
309 // Computing the total size of the buffer we need in order to store the
310 // images in the desired icon format.
311 size_t buffer_size
= ComputeIconFileBufferSize(bitmaps
);
312 unsigned char* buffer
= new unsigned char[buffer_size
];
313 DCHECK(buffer
!= NULL
);
314 memset(buffer
, 0, buffer_size
);
316 // Setting the information in the structures residing within the buffer.
317 // First, we set the information which doesn't require iterating through the
318 // bitmap set and then we set the bitmap specific structures. In the latter
319 // step we also copy the actual bits.
320 ICONDIR
* icon_dir
= reinterpret_cast<ICONDIR
*>(buffer
);
321 icon_dir
->idType
= kResourceTypeIcon
;
322 icon_dir
->idCount
= bitmap_count
;
323 size_t icon_dir_count
= bitmap_count
- 1; // Note DCHECK(!bitmaps.empty())!
324 size_t offset
= sizeof(ICONDIR
) + (sizeof(ICONDIRENTRY
) * icon_dir_count
);
325 for (size_t i
= 0; i
< bitmap_count
; i
++) {
326 ICONIMAGE
* image
= reinterpret_cast<ICONIMAGE
*>(buffer
+ offset
);
327 DCHECK_LT(offset
, buffer_size
);
328 size_t icon_image_size
= 0;
329 SetSingleIconImageInformation(bitmaps
[i
], i
, icon_dir
, image
, offset
,
331 DCHECK_GT(icon_image_size
, 0U);
332 offset
+= icon_image_size
;
334 DCHECK_EQ(offset
, buffer_size
);
336 // Finally, writing the data info the file.
338 bool delete_file
= false;
339 if (!WriteFile(icon_file
.Get(), buffer
, buffer_size
, &bytes_written
, NULL
) ||
340 bytes_written
!= buffer_size
)
343 ::CloseHandle(icon_file
.Take());
346 bool success
= file_util::Delete(icon_path
, false);
353 bool IconUtil::PixelsHaveAlpha(const uint32
* pixels
, size_t num_pixels
) {
354 for (const uint32
* end
= pixels
+ num_pixels
; pixels
!= end
; ++pixels
) {
355 if ((*pixels
& 0xff000000) != 0)
362 void IconUtil::InitializeBitmapHeader(BITMAPV5HEADER
* header
, int width
,
365 memset(header
, 0, sizeof(BITMAPV5HEADER
));
366 header
->bV5Size
= sizeof(BITMAPV5HEADER
);
368 // Note that icons are created using top-down DIBs so we must negate the
369 // value used for the icon's height.
370 header
->bV5Width
= width
;
371 header
->bV5Height
= -height
;
372 header
->bV5Planes
= 1;
373 header
->bV5Compression
= BI_RGB
;
375 // Initializing the bitmap format to 32 bit ARGB.
376 header
->bV5BitCount
= 32;
377 header
->bV5RedMask
= 0x00FF0000;
378 header
->bV5GreenMask
= 0x0000FF00;
379 header
->bV5BlueMask
= 0x000000FF;
380 header
->bV5AlphaMask
= 0xFF000000;
382 // Use the system color space. The default value is LCS_CALIBRATED_RGB, which
383 // causes us to crash if we don't specify the approprite gammas, etc. See
384 // <http://msdn.microsoft.com/en-us/library/ms536531(VS.85).aspx> and
385 // <http://b/1283121>.
386 header
->bV5CSType
= LCS_WINDOWS_COLOR_SPACE
;
388 // Use a valid value for bV5Intent as 0 is not a valid one.
389 // <http://msdn.microsoft.com/en-us/library/dd183381(VS.85).aspx>
390 header
->bV5Intent
= LCS_GM_IMAGES
;
393 void IconUtil::SetSingleIconImageInformation(const SkBitmap
& bitmap
,
396 ICONIMAGE
* icon_image
,
398 size_t* image_byte_count
) {
399 DCHECK(icon_dir
!= NULL
);
400 DCHECK(icon_image
!= NULL
);
401 DCHECK_GT(image_offset
, 0U);
402 DCHECK(image_byte_count
!= NULL
);
404 // We start by computing certain image values we'll use later on.
405 size_t xor_mask_size
, bytes_in_resource
;
406 ComputeBitmapSizeComponents(bitmap
,
410 icon_dir
->idEntries
[index
].bWidth
= static_cast<BYTE
>(bitmap
.width());
411 icon_dir
->idEntries
[index
].bHeight
= static_cast<BYTE
>(bitmap
.height());
412 icon_dir
->idEntries
[index
].wPlanes
= 1;
413 icon_dir
->idEntries
[index
].wBitCount
= 32;
414 icon_dir
->idEntries
[index
].dwBytesInRes
= bytes_in_resource
;
415 icon_dir
->idEntries
[index
].dwImageOffset
= image_offset
;
416 icon_image
->icHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
418 // The width field in the BITMAPINFOHEADER structure accounts for the height
419 // of both the AND mask and the XOR mask so we need to multiply the bitmap's
420 // height by 2. The same does NOT apply to the width field.
421 icon_image
->icHeader
.biHeight
= bitmap
.height() * 2;
422 icon_image
->icHeader
.biWidth
= bitmap
.width();
423 icon_image
->icHeader
.biPlanes
= 1;
424 icon_image
->icHeader
.biBitCount
= 32;
426 // We use a helper function for copying to actual bits from the SkBitmap
427 // object into the appropriate space in the buffer. We use a helper function
428 // (rather than just copying the bits) because there is no way to specify the
429 // orientation (bottom-up vs. top-down) of a bitmap residing in a .ico file.
430 // Thus, if we just copy the bits, we'll end up with a bottom up bitmap in
431 // the .ico file which will result in the icon being displayed upside down.
432 // The helper function copies the image into the buffer one scanline at a
435 // Note that we don't need to initialize the AND mask since the memory
436 // allocated for the icon data buffer was initialized to zero. The icon we
437 // create will therefore use an AND mask containing only zeros, which is OK
438 // because the underlying image has an alpha channel. An AND mask containing
439 // only zeros essentially means we'll initially treat all the pixels as
441 unsigned char* image_addr
= reinterpret_cast<unsigned char*>(icon_image
);
442 unsigned char* xor_mask_addr
= image_addr
+ sizeof(BITMAPINFOHEADER
);
443 CopySkBitmapBitsIntoIconBuffer(bitmap
, xor_mask_addr
, xor_mask_size
);
444 *image_byte_count
= bytes_in_resource
;
447 void IconUtil::CopySkBitmapBitsIntoIconBuffer(const SkBitmap
& bitmap
,
448 unsigned char* buffer
,
449 size_t buffer_size
) {
450 SkAutoLockPixels
bitmap_lock(bitmap
);
451 unsigned char* bitmap_ptr
= static_cast<unsigned char*>(bitmap
.getPixels());
452 size_t bitmap_size
= bitmap
.height() * bitmap
.width() * 4;
453 DCHECK_EQ(buffer_size
, bitmap_size
);
454 for (size_t i
= 0; i
< bitmap_size
; i
+= bitmap
.width() * 4) {
455 memcpy(buffer
+ bitmap_size
- bitmap
.width() * 4 - i
,
461 void IconUtil::CreateResizedBitmapSet(const SkBitmap
& bitmap_to_resize
,
462 std::vector
<SkBitmap
>* bitmaps
) {
463 DCHECK(bitmaps
!= NULL
);
464 DCHECK(bitmaps
->empty());
466 bool inserted_original_bitmap
= false;
467 for (size_t i
= 0; i
< arraysize(icon_dimensions_
); i
++) {
468 // If the dimensions of the bitmap we are resizing are the same as the
469 // current dimensions, then we should insert the bitmap and not a resized
470 // bitmap. If the bitmap's dimensions are smaller, we insert our bitmap
471 // first so that the bitmaps we return in the vector are sorted based on
473 if (!inserted_original_bitmap
) {
474 if ((bitmap_to_resize
.width() == icon_dimensions_
[i
]) &&
475 (bitmap_to_resize
.height() == icon_dimensions_
[i
])) {
476 bitmaps
->push_back(bitmap_to_resize
);
477 inserted_original_bitmap
= true;
481 if ((bitmap_to_resize
.width() < icon_dimensions_
[i
]) &&
482 (bitmap_to_resize
.height() < icon_dimensions_
[i
])) {
483 bitmaps
->push_back(bitmap_to_resize
);
484 inserted_original_bitmap
= true;
487 bitmaps
->push_back(skia::ImageOperations::Resize(
488 bitmap_to_resize
, skia::ImageOperations::RESIZE_LANCZOS3
,
489 icon_dimensions_
[i
], icon_dimensions_
[i
]));
492 if (!inserted_original_bitmap
)
493 bitmaps
->push_back(bitmap_to_resize
);
496 size_t IconUtil::ComputeIconFileBufferSize(const std::vector
<SkBitmap
>& set
) {
497 DCHECK(!set
.empty());
499 // We start by counting the bytes for the structures that don't depend on the
500 // number of icon images. Note that sizeof(ICONDIR) already accounts for a
501 // single ICONDIRENTRY structure, which is why we subtract one from the
502 // number of bitmaps.
503 size_t total_buffer_size
= sizeof(ICONDIR
);
504 size_t bitmap_count
= set
.size();
505 total_buffer_size
+= sizeof(ICONDIRENTRY
) * (bitmap_count
- 1);
506 DCHECK_GE(bitmap_count
, arraysize(icon_dimensions_
));
508 // Add the bitmap specific structure sizes.
509 for (size_t i
= 0; i
< bitmap_count
; i
++) {
510 size_t xor_mask_size
, bytes_in_resource
;
511 ComputeBitmapSizeComponents(set
[i
],
514 total_buffer_size
+= bytes_in_resource
;
516 return total_buffer_size
;
519 void IconUtil::ComputeBitmapSizeComponents(const SkBitmap
& bitmap
,
520 size_t* xor_mask_size
,
521 size_t* bytes_in_resource
) {
522 // The XOR mask size is easy to calculate since we only deal with 32bpp
524 *xor_mask_size
= bitmap
.width() * bitmap
.height() * 4;
526 // Computing the AND mask is a little trickier since it is a monochrome
527 // bitmap (regardless of the number of bits per pixels used in the XOR mask).
528 // There are two things we must make sure we do when computing the AND mask
531 // 1. Make sure the right number of bytes is allocated for each AND mask
532 // scan line in case the number of pixels in the image is not divisible by
533 // 8. For example, in a 15X15 image, 15 / 8 is one byte short of
534 // containing the number of bits we need in order to describe a single
535 // image scan line so we need to add a byte. Thus, we need 2 bytes instead
536 // of 1 for each scan line.
538 // 2. Make sure each scan line in the AND mask is 4 byte aligned (so that the
539 // total icon image has a 4 byte alignment). In the 15X15 image example
540 // above, we can not use 2 bytes so we increase it to the next multiple of
543 // Once we compute the size for a singe AND mask scan line, we multiply that
544 // number by the image height in order to get the total number of bytes for
545 // the AND mask. Thus, for a 15X15 image, we need 15 * 4 which is 60 bytes
546 // for the monochrome bitmap representing the AND mask.
547 size_t and_line_length
= (bitmap
.width() + 7) >> 3;
548 and_line_length
= (and_line_length
+ 3) & ~3;
549 size_t and_mask_size
= and_line_length
* bitmap
.height();
550 size_t masks_size
= *xor_mask_size
+ and_mask_size
;
551 *bytes_in_resource
= masks_size
+ sizeof(BITMAPINFOHEADER
);