blob: 7d5412c650b66327018e81adcde55a26c7e50cd2 [file] [log] [blame]
// Copyright 2010-2014, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "win32/base/text_icon.h"
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
// Workaround against KB813540
#include <atlbase_mozc.h>
#include <atlapp.h>
#include <atlmisc.h>
#include <atlgdi.h>
#include <safeint.h>
#include <memory>
#include "base/port.h"
#include "base/util.h"
namespace mozc {
namespace win32 {
namespace {
using ::msl::utilities::SafeCast;
using ::msl::utilities::SafeMultiply;
using ::WTL::CBitmap;
using ::WTL::CBitmapHandle;
using ::WTL::CDC;
using ::WTL::CFont;
using ::WTL::CFontHandle;
using ::WTL::CLogFont;
using ::WTL::CRect;
using ::WTL::CSize;
using ::WTL::CIconHandle;
RGBQUAD ToRGBQuad(DWORD color_ref) {
const RGBQUAD rgbquad = {
GetBValue(color_ref),
GetGValue(color_ref),
GetRValue(color_ref),
0xff
};
return rgbquad;
}
CIconHandle CreateMonochromeIconInternal(
int bitmap_width,
int bitmap_height,
StringPiece text,
StringPiece fontname,
COLORREF text_color) {
struct MonochromeBitmapInfo {
BITMAPINFOHEADER header;
RGBQUAD color_palette[2];
};
uint8 *src_dib_buffer = nullptr;
CBitmap src_dib;
// Step 1. Create a src black-and-white DIB as follows.
// - This is a top-down DIB.
// - pixel bit is 0 if the pixel should be opaque for text image.
// - pixel bit is 1 if the pixel should be transparent.
// - |src_dib_buffer| is a 4-byte aligned bitmap image.
{
const DWORD kBackgroundColor = RGB(0x00, 0x00, 0x00);
const DWORD kForegroundColor = RGB(0xff, 0xff, 0xff);
MonochromeBitmapInfo info = {};
info.header.biSize = sizeof(info.header);
info.header.biWidth = bitmap_width;
info.header.biHeight = -bitmap_height; // negavive value for top-down BMP
info.header.biPlanes = 1;
info.header.biBitCount = 1;
info.header.biCompression = BI_RGB;
info.header.biSizeImage = 0;
// Note: these color is not directly used for the final output. All we need
// to do here is to use different color and to make sure all the background
// pixels are filled with 1.
info.color_palette[0] = ToRGBQuad(kForegroundColor);
info.color_palette[1] = ToRGBQuad(kBackgroundColor);
src_dib.CreateDIBSection(
nullptr, reinterpret_cast<const BITMAPINFO *>(&info),
DIB_RGB_COLORS, reinterpret_cast<void **>(&src_dib_buffer),
nullptr, 0);
if (src_dib.IsNull()) {
return nullptr;
}
CDC dc;
dc.CreateCompatibleDC(nullptr);
if (dc.IsNull()) {
return nullptr;
}
CBitmapHandle old_bitmap = dc.SelectBitmap(src_dib);
CLogFont logfont;
{
logfont.lfWeight = FW_NORMAL;
logfont.lfCharSet = DEFAULT_CHARSET;
logfont.lfHeight = bitmap_height;
logfont.lfQuality = NONANTIALIASED_QUALITY;
wstring wide_fontname;
Util::UTF8ToWide(fontname, &wide_fontname);
const errno_t error = wcscpy_s(logfont.lfFaceName, wide_fontname.c_str());
if (error != 0) {
return nullptr;
}
}
CFont font_handle;
font_handle.CreateFontIndirectW(&logfont);
if (font_handle.IsNull()) {
return nullptr;
}
const CFontHandle old_font = dc.SelectFont(font_handle);
dc.SetBkMode(OPAQUE);
dc.SetBkColor(kBackgroundColor);
dc.SetTextColor(kForegroundColor);
wstring wide_text;
Util::UTF8ToWide(text, &wide_text);
CRect rect(0, 0, bitmap_width, bitmap_height);
dc.FillSolidRect(rect, kBackgroundColor);
dc.DrawTextW(wide_text.c_str(), wide_text.size(), &rect,
DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX | DT_CENTER);
dc.SelectFont(old_font);
dc.SelectBitmap(old_bitmap);
::GdiFlush();
}
BITMAP src_bmp_info = {};
if (!src_dib.GetBitmap(src_bmp_info)) {
return nullptr;
}
// Step 2. Create XOR bitmap.
// - This is a top-down DIB.
// - pixel bit is 0 if the pixel should be opaque for text image.
// - color palette for this pixel should be |text_color|.
// - pixel bit is 1 if the pixel should be transparent.
// - color palette for this pixel should be RGB(0, 0, 0), which has null
// effect when XOR operation is done.
CBitmap xor_dib;
{
MonochromeBitmapInfo info = {};
info.header.biSize = sizeof(info.header);
info.header.biWidth = bitmap_width;
info.header.biHeight = -bitmap_height; // negavive value for top-down BMP
info.header.biPlanes = 1;
info.header.biBitCount = 1;
info.header.biCompression = BI_RGB;
info.header.biSizeImage = 0;
// Foreground pixel: should be initialized with the given |text_color|.
info.color_palette[0] = ToRGBQuad(text_color);
// Background pixel: should be 0, which has null effect in XOR operation.
info.color_palette[1] = ToRGBQuad(0);
uint8 *xor_dib_buffer = nullptr;
xor_dib.CreateDIBSection(
nullptr, reinterpret_cast<const BITMAPINFO *>(&info),
DIB_RGB_COLORS, reinterpret_cast<void **>(&xor_dib_buffer), nullptr, 0);
if (xor_dib.IsNull()) {
return nullptr;
}
// Make sure that |xor_dib| and |src_dib| have the same pixel format.
BITMAP xor_dib_info = {};
if (!xor_dib.GetBitmap(xor_dib_info) ||
(xor_dib_info.bmBitsPixel != src_bmp_info.bmBitsPixel) ||
(xor_dib_info.bmWidthBytes != src_bmp_info.bmWidthBytes) ||
(xor_dib_info.bmHeight != src_bmp_info.bmHeight)) {
return nullptr;
}
// Here we should be able to copy the buffer safely.
const size_t data_len = bitmap_height * src_bmp_info.bmWidthBytes;
::memcpy(xor_dib_buffer, src_dib_buffer, data_len);
}
// Step 3. Create AND bitmap.
// - This is a top-down DDB.
// - pixel bit is 0 if the pixel should be opaque for text image.
// - pixel bit is 1 if the pixel should be transparent.
CBitmap mask_ddb;
{
// Note: each line should be aligned with 2-byte for DDB while DIB uses
// 4-byte alignment. Here we need to do alignment conversion.
const size_t mask_buffer_stride = (bitmap_width + 0x0f) / 16 * 2;
const size_t mask_buffer_size = mask_buffer_stride * bitmap_width;
unique_ptr<uint8[]> mask_buffer(new uint8[mask_buffer_size]);
for (size_t y = 0; y < bitmap_height; ++y) {
for (size_t x = 0; x < bitmap_width; ++x) {
const uint8 *src_line_start =
src_dib_buffer + src_bmp_info.bmWidthBytes * y;
uint8 *dest_line_start = mask_buffer.get() + mask_buffer_stride * y;
::memcpy(dest_line_start, src_line_start, mask_buffer_stride);
}
}
mask_ddb.CreateBitmap(bitmap_width, bitmap_height, 1, 1, mask_buffer.get());
if (mask_ddb.IsNull()) {
return nullptr;
}
}
// Step 4. Create a GDI ICON object.
{
ICONINFO info = {};
info.fIcon = TRUE;
info.hbmColor = xor_dib;
info.hbmMask = mask_ddb;
info.xHotspot = 0;
info.yHotspot = 0;
return ::CreateIconIndirect(&info);
}
}
} // namespace
// static
HICON TextIcon::CreateMonochromeIcon(size_t width,
size_t height,
StringPiece text,
StringPiece fontname,
COLORREF text_color) {
int safe_width = 0;
int safe_height = 0;
int safe_num_pixels = 0;
if (!SafeCast(width, safe_width) ||
!SafeCast(height, safe_height) ||
!SafeMultiply(safe_width, safe_height, safe_num_pixels)) {
LOG(ERROR) << "Requested size is too large."
<< " width: " << width
<< " height: " << height;
return nullptr;
}
return CreateMonochromeIconInternal(
safe_width, safe_height, text, fontname, text_color);
}
} // namespace win32
} // namespace mozc