icon_util.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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. 4 5#include "ui/gfx/icon_util.h" 6 7#include "base/file_util.h" 8#include "base/logging.h" 9#include "base/memory/scoped_ptr.h" 10#include "base/win/scoped_handle.h" 11#include "skia/ext/image_operations.h" 12#include "third_party/skia/include/core/SkBitmap.h" 13#include "ui/gfx/size.h" 14 15namespace { 16struct ScopedICONINFO : ICONINFO { 17 ScopedICONINFO() { 18 hbmColor = NULL; 19 hbmMask = NULL; 20 } 21 22 ~ScopedICONINFO() { 23 if (hbmColor) 24 ::DeleteObject(hbmColor); 25 if (hbmMask) 26 ::DeleteObject(hbmMask); 27 } 28}; 29} 30 31// Defining the dimensions for the icon images. We store only one value because 32// we always resize to a square image; that is, the value 48 means that we are 33// going to resize the given bitmap to a 48 by 48 pixels bitmap. 34// 35// The icon images appear in the icon file in same order in which their 36// corresponding dimensions appear in the |icon_dimensions_| array, so it is 37// important to keep this array sorted. Also note that the maximum icon image 38// size we can handle is 255 by 255. 39const int IconUtil::icon_dimensions_[] = { 40 8, // Recommended by the MSDN as a nice to have icon size. 41 10, // Used by the Shell (e.g. for shortcuts). 42 14, // Recommended by the MSDN as a nice to have icon size. 43 16, // Toolbar, Application and Shell icon sizes. 44 22, // Recommended by the MSDN as a nice to have icon size. 45 24, // Used by the Shell (e.g. for shortcuts). 46 32, // Toolbar, Dialog and Wizard icon size. 47 40, // Quick Launch. 48 48, // Alt+Tab icon size. 49 64, // Recommended by the MSDN as a nice to have icon size. 50 96, // Recommended by the MSDN as a nice to have icon size. 51 128 // Used by the Shell (e.g. for shortcuts). 52}; 53 54HICON IconUtil::CreateHICONFromSkBitmap(const SkBitmap& bitmap) { 55 // Only 32 bit ARGB bitmaps are supported. We also try to perform as many 56 // validations as we can on the bitmap. 57 SkAutoLockPixels bitmap_lock(bitmap); 58 if ((bitmap.config() != SkBitmap::kARGB_8888_Config) || 59 (bitmap.width() <= 0) || (bitmap.height() <= 0) || 60 (bitmap.getPixels() == NULL)) 61 return NULL; 62 63 // We start by creating a DIB which we'll use later on in order to create 64 // the HICON. We use BITMAPV5HEADER since the bitmap we are about to convert 65 // may contain an alpha channel and the V5 header allows us to specify the 66 // alpha mask for the DIB. 67 BITMAPV5HEADER bitmap_header; 68 InitializeBitmapHeader(&bitmap_header, bitmap.width(), bitmap.height()); 69 void* bits; 70 HDC hdc = ::GetDC(NULL); 71 HBITMAP dib; 72 dib = ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&bitmap_header), 73 DIB_RGB_COLORS, &bits, NULL, 0); 74 DCHECK(dib); 75 ::ReleaseDC(NULL, hdc); 76 memcpy(bits, bitmap.getPixels(), bitmap.width() * bitmap.height() * 4); 77 78 // Icons are generally created using an AND and XOR masks where the AND 79 // specifies boolean transparency (the pixel is either opaque or 80 // transparent) and the XOR mask contains the actual image pixels. If the XOR 81 // mask bitmap has an alpha channel, the AND monochrome bitmap won't 82 // actually be used for computing the pixel transparency. Even though all our 83 // bitmap has an alpha channel, Windows might not agree when all alpha values 84 // are zero. So the monochrome bitmap is created with all pixels transparent 85 // for this case. Otherwise, it is created with all pixels opaque. 86 bool bitmap_has_alpha_channel = PixelsHaveAlpha( 87 static_cast<const uint32*>(bitmap.getPixels()), 88 bitmap.width() * bitmap.height()); 89 90 scoped_array<uint8> mask_bits; 91 if (!bitmap_has_alpha_channel) { 92 // Bytes per line with paddings to make it word alignment. 93 size_t bytes_per_line = (bitmap.width() + 0xF) / 16 * 2; 94 size_t mask_bits_size = bytes_per_line * bitmap.height(); 95 96 mask_bits.reset(new uint8[mask_bits_size]); 97 DCHECK(mask_bits.get()); 98 99 // Make all pixels transparent. 100 memset(mask_bits.get(), 0xFF, mask_bits_size); 101 } 102 103 HBITMAP mono_bitmap = ::CreateBitmap(bitmap.width(), bitmap.height(), 1, 1, 104 reinterpret_cast<LPVOID>(mask_bits.get())); 105 DCHECK(mono_bitmap); 106 107 ICONINFO icon_info; 108 icon_info.fIcon = TRUE; 109 icon_info.xHotspot = 0; 110 icon_info.yHotspot = 0; 111 icon_info.hbmMask = mono_bitmap; 112 icon_info.hbmColor = dib; 113 HICON icon = ::CreateIconIndirect(&icon_info); 114 ::DeleteObject(dib); 115 ::DeleteObject(mono_bitmap); 116 return icon; 117} 118 119SkBitmap* IconUtil::CreateSkBitmapFromHICON(HICON icon, const gfx::Size& s) { 120 // We start with validating parameters. 121 if (!icon || s.IsEmpty()) 122 return NULL; 123 ScopedICONINFO icon_info; 124 if (!::GetIconInfo(icon, &icon_info)) 125 return NULL; 126 if (!icon_info.fIcon) 127 return NULL; 128 return new SkBitmap(CreateSkBitmapFromHICONHelper(icon, s)); 129} 130 131SkBitmap* IconUtil::CreateSkBitmapFromHICON(HICON icon) { 132 // We start with validating parameters. 133 if (!icon) 134 return NULL; 135 136 ScopedICONINFO icon_info; 137 BITMAP bitmap_info = { 0 }; 138 139 if (!::GetIconInfo(icon, &icon_info)) 140 return NULL; 141 142 if (!::GetObject(icon_info.hbmMask, sizeof(bitmap_info), &bitmap_info)) 143 return NULL; 144 145 gfx::Size icon_size(bitmap_info.bmWidth, bitmap_info.bmHeight); 146 return new SkBitmap(CreateSkBitmapFromHICONHelper(icon, icon_size)); 147} 148 149SkBitmap IconUtil::CreateSkBitmapFromHICONHelper(HICON icon, 150 const gfx::Size& s) { 151 DCHECK(icon); 152 DCHECK(!s.IsEmpty()); 153 154 // Allocating memory for the SkBitmap object. We are going to create an ARGB 155 // bitmap so we should set the configuration appropriately. 156 SkBitmap bitmap; 157 bitmap.setConfig(SkBitmap::kARGB_8888_Config, s.width(), s.height()); 158 bitmap.allocPixels(); 159 bitmap.eraseARGB(0, 0, 0, 0); 160 SkAutoLockPixels bitmap_lock(bitmap); 161 162 // Now we should create a DIB so that we can use ::DrawIconEx in order to 163 // obtain the icon's image. 164 BITMAPV5HEADER h; 165 InitializeBitmapHeader(&h, s.width(), s.height()); 166 HDC hdc = ::GetDC(NULL); 167 uint32* bits; 168 HBITMAP dib = ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&h), 169 DIB_RGB_COLORS, reinterpret_cast<void**>(&bits), NULL, 0); 170 DCHECK(dib); 171 HDC dib_dc = CreateCompatibleDC(hdc); 172 ::ReleaseDC(NULL, hdc); 173 DCHECK(dib_dc); 174 HGDIOBJ old_obj = ::SelectObject(dib_dc, dib); 175 176 // Windows icons are defined using two different masks. The XOR mask, which 177 // represents the icon image and an AND mask which is a monochrome bitmap 178 // which indicates the transparency of each pixel. 179 // 180 // To make things more complex, the icon image itself can be an ARGB bitmap 181 // and therefore contain an alpha channel which specifies the transparency 182 // for each pixel. Unfortunately, there is no easy way to determine whether 183 // or not a bitmap has an alpha channel and therefore constructing the bitmap 184 // for the icon is nothing but straightforward. 185 // 186 // The idea is to read the AND mask but use it only if we know for sure that 187 // the icon image does not have an alpha channel. The only way to tell if the 188 // bitmap has an alpha channel is by looking through the pixels and checking 189 // whether there are non-zero alpha bytes. 190 // 191 // We start by drawing the AND mask into our DIB. 192 size_t num_pixels = s.GetArea(); 193 memset(bits, 0, num_pixels * 4); 194 ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_MASK); 195 196 // Capture boolean opacity. We may not use it if we find out the bitmap has 197 // an alpha channel. 198 scoped_array<bool> opaque(new bool[num_pixels]); 199 for (size_t i = 0; i < num_pixels; ++i) 200 opaque[i] = !bits[i]; 201 202 // Then draw the image itself which is really the XOR mask. 203 memset(bits, 0, num_pixels * 4); 204 ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_NORMAL); 205 memcpy(bitmap.getPixels(), static_cast<void*>(bits), num_pixels * 4); 206 207 // Finding out whether the bitmap has an alpha channel. 208 bool bitmap_has_alpha_channel = PixelsHaveAlpha( 209 static_cast<const uint32*>(bitmap.getPixels()), num_pixels); 210 211 // If the bitmap does not have an alpha channel, we need to build it using 212 // the previously captured AND mask. Otherwise, we are done. 213 if (!bitmap_has_alpha_channel) { 214 uint32* p = static_cast<uint32*>(bitmap.getPixels()); 215 for (size_t i = 0; i < num_pixels; ++p, ++i) { 216 DCHECK_EQ((*p & 0xff000000), 0u); 217 if (opaque[i]) 218 *p |= 0xff000000; 219 else 220 *p &= 0x00ffffff; 221 } 222 } 223 224 ::SelectObject(dib_dc, old_obj); 225 ::DeleteObject(dib); 226 ::DeleteDC(dib_dc); 227 228 return bitmap; 229} 230 231bool IconUtil::CreateIconFileFromSkBitmap(const SkBitmap& bitmap, 232 const FilePath& icon_path) { 233 // Only 32 bit ARGB bitmaps are supported. We also make sure the bitmap has 234 // been properly initialized. 235 SkAutoLockPixels bitmap_lock(bitmap); 236 if ((bitmap.config() != SkBitmap::kARGB_8888_Config) || 237 (bitmap.height() <= 0) || (bitmap.width() <= 0) || 238 (bitmap.getPixels() == NULL)) 239 return false; 240 241 // We start by creating the file. 242 base::win::ScopedHandle icon_file(::CreateFile(icon_path.value().c_str(), 243 GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); 244 245 if (!icon_file.IsValid()) 246 return false; 247 248 // Creating a set of bitmaps corresponding to the icon images we'll end up 249 // storing in the icon file. Each bitmap is created by resizing the given 250 // bitmap to the desired size. 251 std::vector<SkBitmap> bitmaps; 252 CreateResizedBitmapSet(bitmap, &bitmaps); 253 DCHECK(!bitmaps.empty()); 254 size_t bitmap_count = bitmaps.size(); 255 256 // Computing the total size of the buffer we need in order to store the 257 // images in the desired icon format. 258 size_t buffer_size = ComputeIconFileBufferSize(bitmaps); 259 unsigned char* buffer = new unsigned char[buffer_size]; 260 DCHECK(buffer != NULL); 261 memset(buffer, 0, buffer_size); 262 263 // Setting the information in the structures residing within the buffer. 264 // First, we set the information which doesn't require iterating through the 265 // bitmap set and then we set the bitmap specific structures. In the latter 266 // step we also copy the actual bits. 267 ICONDIR* icon_dir = reinterpret_cast<ICONDIR*>(buffer); 268 icon_dir->idType = kResourceTypeIcon; 269 icon_dir->idCount = bitmap_count; 270 size_t icon_dir_count = bitmap_count - 1; // Note DCHECK(!bitmaps.empty())! 271 size_t offset = sizeof(ICONDIR) + (sizeof(ICONDIRENTRY) * icon_dir_count); 272 for (size_t i = 0; i < bitmap_count; i++) { 273 ICONIMAGE* image = reinterpret_cast<ICONIMAGE*>(buffer + offset); 274 DCHECK_LT(offset, buffer_size); 275 size_t icon_image_size = 0; 276 SetSingleIconImageInformation(bitmaps[i], i, icon_dir, image, offset, 277 &icon_image_size); 278 DCHECK_GT(icon_image_size, 0U); 279 offset += icon_image_size; 280 } 281 DCHECK_EQ(offset, buffer_size); 282 283 // Finally, writing the data info the file. 284 DWORD bytes_written; 285 bool delete_file = false; 286 if (!WriteFile(icon_file.Get(), buffer, buffer_size, &bytes_written, NULL) || 287 bytes_written != buffer_size) 288 delete_file = true; 289 290 ::CloseHandle(icon_file.Take()); 291 delete [] buffer; 292 if (delete_file) { 293 bool success = file_util::Delete(icon_path, false); 294 DCHECK(success); 295 } 296 297 return !delete_file; 298} 299 300bool IconUtil::PixelsHaveAlpha(const uint32* pixels, size_t num_pixels) { 301 for (const uint32* end = pixels + num_pixels; pixels != end; ++pixels) { 302 if ((*pixels & 0xff000000) != 0) 303 return true; 304 } 305 306 return false; 307} 308 309void IconUtil::InitializeBitmapHeader(BITMAPV5HEADER* header, int width, 310 int height) { 311 DCHECK(header); 312 memset(header, 0, sizeof(BITMAPV5HEADER)); 313 header->bV5Size = sizeof(BITMAPV5HEADER); 314 315 // Note that icons are created using top-down DIBs so we must negate the 316 // value used for the icon's height. 317 header->bV5Width = width; 318 header->bV5Height = -height; 319 header->bV5Planes = 1; 320 header->bV5Compression = BI_RGB; 321 322 // Initializing the bitmap format to 32 bit ARGB. 323 header->bV5BitCount = 32; 324 header->bV5RedMask = 0x00FF0000; 325 header->bV5GreenMask = 0x0000FF00; 326 header->bV5BlueMask = 0x000000FF; 327 header->bV5AlphaMask = 0xFF000000; 328 329 // Use the system color space. The default value is LCS_CALIBRATED_RGB, which 330 // causes us to crash if we don't specify the approprite gammas, etc. See 331 // <http://msdn.microsoft.com/en-us/library/ms536531(VS.85).aspx> and 332 // <http://b/1283121>. 333 header->bV5CSType = LCS_WINDOWS_COLOR_SPACE; 334 335 // Use a valid value for bV5Intent as 0 is not a valid one. 336 // <http://msdn.microsoft.com/en-us/library/dd183381(VS.85).aspx> 337 header->bV5Intent = LCS_GM_IMAGES; 338} 339 340void IconUtil::SetSingleIconImageInformation(const SkBitmap& bitmap, 341 size_t index, 342 ICONDIR* icon_dir, 343 ICONIMAGE* icon_image, 344 size_t image_offset, 345 size_t* image_byte_count) { 346 DCHECK(icon_dir != NULL); 347 DCHECK(icon_image != NULL); 348 DCHECK_GT(image_offset, 0U); 349 DCHECK(image_byte_count != NULL); 350 351 // We start by computing certain image values we'll use later on. 352 size_t xor_mask_size, bytes_in_resource; 353 ComputeBitmapSizeComponents(bitmap, 354 &xor_mask_size, 355 &bytes_in_resource); 356 357 icon_dir->idEntries[index].bWidth = static_cast<BYTE>(bitmap.width()); 358 icon_dir->idEntries[index].bHeight = static_cast<BYTE>(bitmap.height()); 359 icon_dir->idEntries[index].wPlanes = 1; 360 icon_dir->idEntries[index].wBitCount = 32; 361 icon_dir->idEntries[index].dwBytesInRes = bytes_in_resource; 362 icon_dir->idEntries[index].dwImageOffset = image_offset; 363 icon_image->icHeader.biSize = sizeof(BITMAPINFOHEADER); 364 365 // The width field in the BITMAPINFOHEADER structure accounts for the height 366 // of both the AND mask and the XOR mask so we need to multiply the bitmap's 367 // height by 2. The same does NOT apply to the width field. 368 icon_image->icHeader.biHeight = bitmap.height() * 2; 369 icon_image->icHeader.biWidth = bitmap.width(); 370 icon_image->icHeader.biPlanes = 1; 371 icon_image->icHeader.biBitCount = 32; 372 373 // We use a helper function for copying to actual bits from the SkBitmap 374 // object into the appropriate space in the buffer. We use a helper function 375 // (rather than just copying the bits) because there is no way to specify the 376 // orientation (bottom-up vs. top-down) of a bitmap residing in a .ico file. 377 // Thus, if we just copy the bits, we'll end up with a bottom up bitmap in 378 // the .ico file which will result in the icon being displayed upside down. 379 // The helper function copies the image into the buffer one scanline at a 380 // time. 381 // 382 // Note that we don't need to initialize the AND mask since the memory 383 // allocated for the icon data buffer was initialized to zero. The icon we 384 // create will therefore use an AND mask containing only zeros, which is OK 385 // because the underlying image has an alpha channel. An AND mask containing 386 // only zeros essentially means we'll initially treat all the pixels as 387 // opaque. 388 unsigned char* image_addr = reinterpret_cast<unsigned char*>(icon_image); 389 unsigned char* xor_mask_addr = image_addr + sizeof(BITMAPINFOHEADER); 390 CopySkBitmapBitsIntoIconBuffer(bitmap, xor_mask_addr, xor_mask_size); 391 *image_byte_count = bytes_in_resource; 392} 393 394void IconUtil::CopySkBitmapBitsIntoIconBuffer(const SkBitmap& bitmap, 395 unsigned char* buffer, 396 size_t buffer_size) { 397 SkAutoLockPixels bitmap_lock(bitmap); 398 unsigned char* bitmap_ptr = static_cast<unsigned char*>(bitmap.getPixels()); 399 size_t bitmap_size = bitmap.height() * bitmap.width() * 4; 400 DCHECK_EQ(buffer_size, bitmap_size); 401 for (size_t i = 0; i < bitmap_size; i += bitmap.width() * 4) { 402 memcpy(buffer + bitmap_size - bitmap.width() * 4 - i, 403 bitmap_ptr + i, 404 bitmap.width() * 4); 405 } 406} 407 408void IconUtil::CreateResizedBitmapSet(const SkBitmap& bitmap_to_resize, 409 std::vector<SkBitmap>* bitmaps) { 410 DCHECK(bitmaps != NULL); 411 DCHECK(bitmaps->empty()); 412 413 bool inserted_original_bitmap = false; 414 for (size_t i = 0; i < arraysize(icon_dimensions_); i++) { 415 // If the dimensions of the bitmap we are resizing are the same as the 416 // current dimensions, then we should insert the bitmap and not a resized 417 // bitmap. If the bitmap's dimensions are smaller, we insert our bitmap 418 // first so that the bitmaps we return in the vector are sorted based on 419 // their dimensions. 420 if (!inserted_original_bitmap) { 421 if ((bitmap_to_resize.width() == icon_dimensions_[i]) && 422 (bitmap_to_resize.height() == icon_dimensions_[i])) { 423 bitmaps->push_back(bitmap_to_resize); 424 inserted_original_bitmap = true; 425 continue; 426 } 427 428 if ((bitmap_to_resize.width() < icon_dimensions_[i]) && 429 (bitmap_to_resize.height() < icon_dimensions_[i])) { 430 bitmaps->push_back(bitmap_to_resize); 431 inserted_original_bitmap = true; 432 } 433 } 434 bitmaps->push_back(skia::ImageOperations::Resize( 435 bitmap_to_resize, skia::ImageOperations::RESIZE_LANCZOS3, 436 icon_dimensions_[i], icon_dimensions_[i])); 437 } 438 439 if (!inserted_original_bitmap) 440 bitmaps->push_back(bitmap_to_resize); 441} 442 443size_t IconUtil::ComputeIconFileBufferSize(const std::vector<SkBitmap>& set) { 444 DCHECK(!set.empty()); 445 446 // We start by counting the bytes for the structures that don't depend on the 447 // number of icon images. Note that sizeof(ICONDIR) already accounts for a 448 // single ICONDIRENTRY structure, which is why we subtract one from the 449 // number of bitmaps. 450 size_t total_buffer_size = sizeof(ICONDIR); 451 size_t bitmap_count = set.size(); 452 total_buffer_size += sizeof(ICONDIRENTRY) * (bitmap_count - 1); 453 DCHECK_GE(bitmap_count, arraysize(icon_dimensions_)); 454 455 // Add the bitmap specific structure sizes. 456 for (size_t i = 0; i < bitmap_count; i++) { 457 size_t xor_mask_size, bytes_in_resource; 458 ComputeBitmapSizeComponents(set[i], 459 &xor_mask_size, 460 &bytes_in_resource); 461 total_buffer_size += bytes_in_resource; 462 } 463 return total_buffer_size; 464} 465 466void IconUtil::ComputeBitmapSizeComponents(const SkBitmap& bitmap, 467 size_t* xor_mask_size, 468 size_t* bytes_in_resource) { 469 // The XOR mask size is easy to calculate since we only deal with 32bpp 470 // images. 471 *xor_mask_size = bitmap.width() * bitmap.height() * 4; 472 473 // Computing the AND mask is a little trickier since it is a monochrome 474 // bitmap (regardless of the number of bits per pixels used in the XOR mask). 475 // There are two things we must make sure we do when computing the AND mask 476 // size: 477 // 478 // 1. Make sure the right number of bytes is allocated for each AND mask 479 // scan line in case the number of pixels in the image is not divisible by 480 // 8. For example, in a 15X15 image, 15 / 8 is one byte short of 481 // containing the number of bits we need in order to describe a single 482 // image scan line so we need to add a byte. Thus, we need 2 bytes instead 483 // of 1 for each scan line. 484 // 485 // 2. Make sure each scan line in the AND mask is 4 byte aligned (so that the 486 // total icon image has a 4 byte alignment). In the 15X15 image example 487 // above, we can not use 2 bytes so we increase it to the next multiple of 488 // 4 which is 4. 489 // 490 // Once we compute the size for a singe AND mask scan line, we multiply that 491 // number by the image height in order to get the total number of bytes for 492 // the AND mask. Thus, for a 15X15 image, we need 15 * 4 which is 60 bytes 493 // for the monochrome bitmap representing the AND mask. 494 size_t and_line_length = (bitmap.width() + 7) >> 3; 495 and_line_length = (and_line_length + 3) & ~3; 496 size_t and_mask_size = and_line_length * bitmap.height(); 497 size_t masks_size = *xor_mask_size + and_mask_size; 498 *bytes_in_resource = masks_size + sizeof(BITMAPINFOHEADER); 499} 500