blob: 079f3df03e0e469e68394d0344b4ae05282c90a4 [file] [log] [blame]
// Copyright 2010-2015, 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 "renderer/win32/win32_image_util.h"
#include <Shlwapi.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 <algorithm>
#include <bitset>
#include <vector>
#include "base/stl_util.h"
#include "base/util.h"
// remove annoying macros
#ifdef min
#undef min
#endif // min
#ifdef max
#undef max
#endif // max
namespace mozc {
namespace renderer {
namespace win32 {
namespace {
using ::mozc::renderer::win32::internal::GaussianBlur;
using ::mozc::renderer::win32::internal::SafeFrameBuffer;
using ::mozc::renderer::win32::internal::SubdivisionalPixel;
using ::mozc::renderer::win32::internal::TextLabel;
using ::WTL::CBitmap;
using ::WTL::CBitmapHandle;
using ::WTL::CDC;
using ::WTL::CFont;
using ::WTL::CFontHandle;
using ::WTL::CLogFont;
using ::WTL::CPoint;
using ::WTL::CRect;
using ::WTL::CSize;
Rect GetBalloonBoundingRect(
double left, double top, double width, double height,
double balloon_tail_height, double balloon_tail_width,
BalloonImage::BalloonImageInfo::TailDirection balloon_tail) {
double real_left = left;
if (balloon_tail == BalloonImage::BalloonImageInfo::kLeft) {
real_left -= balloon_tail_height;
}
const int int_left = static_cast<int>(floor(real_left));
double real_top = top;
if (balloon_tail == BalloonImage::BalloonImageInfo::kTop) {
real_top -= balloon_tail_height;
}
const int int_top = static_cast<int>(floor(real_top));
double real_right = left + width;
if (balloon_tail == BalloonImage::BalloonImageInfo::kRight) {
real_right += balloon_tail_height;
}
const int int_right = static_cast<int>(ceil(real_right));
double real_bottom = top + height;
if (balloon_tail == BalloonImage::BalloonImageInfo::kBottom) {
real_bottom += balloon_tail_height;
}
const int int_bottom = static_cast<int>(ceil(real_bottom));
return Rect(int_left, int_top, int_right - int_left, int_bottom - int_top);
}
class Balloon {
public:
Balloon(double left,
double top,
double width,
double height,
double frame_thickness,
double corner_radius,
double balloon_tail_height,
double balloon_tail_width,
RGBColor frame_color,
RGBColor inside_color,
BalloonImage::BalloonImageInfo::TailDirection balloon_tail)
: left_(left),
top_(top),
width_(width),
height_(height),
frame_thickness_(frame_thickness),
corner_radius_(corner_radius),
balloon_tail_height_(balloon_tail_height),
balloon_tail_width_(balloon_tail_width),
frame_color_(frame_color),
inside_color_(inside_color),
balloon_tail_(balloon_tail),
bounding_rect_(GetBalloonBoundingRect(
left, top, width, height, balloon_tail_height, balloon_tail_width,
balloon_tail)) {}
void RenderPixel(int x, int y, SubdivisionalPixel *pixel) const {
{
const PixelType fast_check_type = GetPixelTypeInternalFast(x, y);
switch (fast_check_type) {
case kInside:
pixel->SetPixel(inside_color_);
break;
case kOutside:
return;
}
}
for (SubdivisionalPixel::SubdivisionalPixelIterator it(x, y); !it.Done();
it.Next()) {
const PixelType type = GetPixelTypeInternal(it.GetX(), it.GetY());
switch (type) {
case kFrame:
pixel->SetSubdivisionalPixel(it.GetFraction(), frame_color_);
break;
case kInside:
pixel->SetSubdivisionalPixel(it.GetFraction(), inside_color_);
break;
}
}
}
const Rect &bounding_rect() const {
return bounding_rect_;
}
double GetTailX() const {
switch (balloon_tail_) {
case BalloonImage::BalloonImageInfo::kTop:
return left_ + width_ / 2.0;
case BalloonImage::BalloonImageInfo::kRight:
return left_ + width_ + balloon_tail_height_;
case BalloonImage::BalloonImageInfo::kBottom:
return left_ + width_ / 2.0;
case BalloonImage::BalloonImageInfo::kLeft:
return left_ - balloon_tail_height_;
default:
CHECK(false) << "Do not reach here";
return 0.0;
}
}
double GetTailY() const {
switch (balloon_tail_) {
case BalloonImage::BalloonImageInfo::kTop:
return top_ - balloon_tail_height_;
case BalloonImage::BalloonImageInfo::kRight:
return top_ + height_ / 2.0;
case BalloonImage::BalloonImageInfo::kBottom:
return top_ + height_ + balloon_tail_height_;
case BalloonImage::BalloonImageInfo::kLeft:
return top_ + height_ / 2.0;
default:
CHECK(false) << "Do not reach here";
return 0.0;
}
}
private:
enum PixelType {
kUnknown,
kOutside,
kFrame,
kInside,
};
// Convert |x|, |y|, |width| and |height| as if the balloon tail is upside
// and the center of the rectangle is at the origin.
void Normalize(double *x, double *y, double *width, double *height) const {
const double src_x = *x;
const double src_y = *y;
const double src_width = *width;
const double src_height = *height;
switch (balloon_tail_) {
case BalloonImage::BalloonImageInfo::kTop:
*x = (left_ + width_ / 2) - src_x;
*y = (top_ + height_ / 2) - src_y;
break;
case BalloonImage::BalloonImageInfo::kRight:
*x = (top_ + height_ / 2) - src_y;
*y = src_x - (left_ + width_ / 2);
*width = src_height;
*height = src_width;
break;
case BalloonImage::BalloonImageInfo::kBottom:
*x = src_x - (left_ + width_ / 2);
*y = src_y - (top_ + height_ / 2);
break;
case BalloonImage::BalloonImageInfo::kLeft:
*x = src_y - (top_ + height_ / 2);
*y = (left_ + width_ / 2) - src_x;
*width = src_height;
*height = src_width;
break;
}
}
// Returns PixelType as a quick determination. If kUnknown is returned, you
// need to investigate the pixel type more precisely.
PixelType GetPixelTypeInternalFast(int x, int y) const {
const double frame = max(corner_radius_, frame_thickness_);
if ((left_ + frame) < x &&
(x + 1) < (left_ + width_ - frame) &&
(top_ + frame) < y &&
(y + 1) < (top_ + height_ - frame)) {
return kInside;
}
switch (balloon_tail_) {
case BalloonImage::BalloonImageInfo::kTop:
if (x < left_ || left_ + width_ < x) {
return kOutside;
}
if (y < top_ - balloon_tail_height_ || top_ + height_ < y) {
return kOutside;
}
break;
case BalloonImage::BalloonImageInfo::kRight:
if (x < left_ || left_ + width_ + balloon_tail_height_ < x) {
return kOutside;
}
if (y < top_ || top_ + height_ < y) {
return kOutside;
}
break;
case BalloonImage::BalloonImageInfo::kBottom:
if (x < left_ || left_ + width_ < x) {
return kOutside;
}
if (y < top_ || top_ + height_ + balloon_tail_height_ < y) {
return kOutside;
}
break;
case BalloonImage::BalloonImageInfo::kLeft:
if (x < left_ - balloon_tail_height_ || left_ + width_ < x) {
return kOutside;
}
if (y < top_ || top_ + height_ < y) {
return kOutside;
}
break;
}
return kUnknown;
}
// Returns PixelType as full determination. This function is slow but works
// for all cases.
PixelType GetPixelTypeInternal(double x, double y) const {
double w = width_;
double h = height_;
// Normalize parameters so that we can determine the param as if the
// balloon's tail is always on top.
Normalize(&x, &y, &w, &h);
const double half_width = w / 2.0;
const double half_height = h / 2.0;
const double half_tail_width = balloon_tail_width_ / 2.0;
// From symmetry
const double abs_x = abs(x);
if (abs_x > half_width) {
return kOutside;
}
// Check if (x, y) is on the balloon's tail.
if (balloon_tail_height_ > 0 && half_tail_width > 0) {
if ((abs_x < half_tail_width) &&
(y > (half_height - frame_thickness_)) &&
(y < (half_height + balloon_tail_height_))) {
const double ratio = balloon_tail_height_ / half_tail_width;
const double nx = abs_x;
const double ny = y - half_height - balloon_tail_height_;
const double outer_line = -ratio * nx;
const double inner_line =
outer_line - frame_thickness_ * sqrt(1.0 + ratio * ratio);
if (ny > outer_line) {
return kOutside;
}
if (ny < inner_line) {
return kInside;
}
return kFrame;
}
}
// Check if (x, y) is not on tail. So |y| can be normalized from symmetry.
const double abs_y = abs(y);
if (abs_y > half_height) {
return kOutside;
}
// Check if (x, y) is just outside at the corner.
if (corner_radius_ > 0.0) {
const double rx = abs_x - (half_width - corner_radius_);
if (rx > 0.0) {
const double ry = abs_y - (half_height - corner_radius_);
if (ry > 0.0) {
const double radius_sq = rx * rx + ry * ry;
if (radius_sq > corner_radius_ * corner_radius_) {
return kOutside;
} else {
const double inner_radius = (corner_radius_ - frame_thickness_);
if (radius_sq < inner_radius * inner_radius) {
return kInside;
}
return kFrame;
}
}
}
}
// Check if (x, y) is on boarder or not.
if (abs_x > (half_width - frame_thickness_)) {
return kFrame;
}
if (abs_y > (half_height - frame_thickness_)) {
return kFrame;
}
// (x, y) is inside.
return kInside;
}
const double left_;
const double top_;
const double width_;
const double height_;
const double frame_thickness_;
const double corner_radius_;
const double balloon_tail_height_;
const double balloon_tail_width_;
const RGBColor frame_color_;
const RGBColor inside_color_;
const BalloonImage::BalloonImageInfo::TailDirection balloon_tail_;
const Rect bounding_rect_;
DISALLOW_COPY_AND_ASSIGN(Balloon);
};
Rect GetBoundingRect(double left, double top, double width, double height) {
const int int_left = static_cast<int>(floor(left));
const int int_top = static_cast<int>(floor(top));
const int int_right = static_cast<int>(ceil(left + width));
const int int_bottom = static_cast<int>(ceil(top + height));
return Rect(int_left, int_top, int_right - int_left, int_bottom - int_top);
}
// Core logic to render 1-bit text glyphs for sub-pixel rendering. Caller takes
// the ownerships of the returned pointers.
vector<TextLabel::BinarySubdivisionalPixel *> Get1bitGlyph(
double left,
double top,
double width,
double height,
const string &text,
const string &fontname,
size_t font_point) {
const size_t kDivision = SubdivisionalPixel::kDivision;
const Rect &bounding_rect = GetBoundingRect(left, top, width, height);
const int pix_width = bounding_rect.Width();
const int pix_height = bounding_rect.Height();
vector<TextLabel::BinarySubdivisionalPixel *> pixels;
pixels.resize(pix_width * pix_height, nullptr);
if (text.empty()) {
return pixels;
}
const int bitmap_width = pix_width * kDivision;
const int bitmap_height = pix_height * kDivision;
struct MonochromeBitmapInfo {
BITMAPINFOHEADER header;
RGBQUAD color_palette[2];
};
const RGBQUAD kBackgroundColor = {0x00, 0x00, 0x00, 0x00};
const RGBQUAD kForegroundColor = {0xff, 0xff, 0xff, 0x00};
MonochromeBitmapInfo bitmap_info = {};
bitmap_info.header.biSize = sizeof(BITMAPINFOHEADER);
bitmap_info.header.biWidth = bitmap_width;
bitmap_info.header.biHeight = -bitmap_height; // top-down BMP
bitmap_info.header.biPlanes = 1;
bitmap_info.header.biBitCount = 1; // Color palettes must have 2 entries.
bitmap_info.header.biCompression = BI_RGB;
bitmap_info.header.biSizeImage = 0;
bitmap_info.color_palette[0] = kBackgroundColor; // black
bitmap_info.color_palette[1] = kForegroundColor; // white
uint8 *buffer = nullptr;
CBitmap dib;
dib.CreateDIBSection(
nullptr, reinterpret_cast<const BITMAPINFO *>(&bitmap_info),
DIB_RGB_COLORS, reinterpret_cast<void **>(&buffer), nullptr, 0);
CDC dc;
dc.CreateCompatibleDC(nullptr);
CBitmapHandle old_bitmap = dc.SelectBitmap(dib);
wstring wide_fontname;
Util::UTF8ToWide(fontname, &wide_fontname);
CLogFont logfont;
logfont.lfWeight = FW_NORMAL;
logfont.lfCharSet = DEFAULT_CHARSET;
logfont.SetHeightFromDeciPoint(font_point * 10 * kDivision, dc);
logfont.lfQuality = NONANTIALIASED_QUALITY;
const errno_t error = wcscpy_s(logfont.lfFaceName, wide_fontname.c_str());
if (error != 0) {
LOG(ERROR) << "Failed to copy fontname: " << fontname;
return pixels;
}
CFont font_handle;
font_handle.CreateFontIndirectW(&logfont);
const CFontHandle old_font = dc.SelectFont(font_handle);
const CPoint lefttop(
static_cast<int>(floor((left - bounding_rect.Left()) * kDivision)),
static_cast<int>(floor((top - bounding_rect.Top()) * kDivision)));
const CSize size(static_cast<int>(ceil(width * kDivision)),
static_cast<int>(ceil(height * kDivision)));
CRect rect(lefttop, size);
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(RGB(255, 255, 255));
wstring wide_text;
Util::UTF8ToWide(text, &wide_text);
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();
// DIB section requires 32-bit alignment for each line.
const size_t bitmap_stride = ((bitmap_width + 31) / 32) * 32;
for (size_t pix_y = 0; pix_y < pix_height; ++pix_y) {
for (size_t pix_x = 0; pix_x < pix_width; ++pix_x) {
const size_t pix_index = pix_y * pix_width + pix_x;
TextLabel::BinarySubdivisionalPixel *sub_pixels = nullptr;
for (size_t subpix_y = 0; subpix_y < kDivision; ++subpix_y) {
const size_t y = pix_y * kDivision + subpix_y;
for (size_t subpix_x = 0; subpix_x < kDivision; ++subpix_x) {
const size_t x = pix_x * kDivision + subpix_x;
const size_t index = y * bitmap_stride + x;
const size_t byte_index = index / 8;
const size_t bit_index = index - byte_index * 8;
if (((buffer[byte_index] >> bit_index) & 1) == 0) {
continue;
}
if (sub_pixels == nullptr) {
sub_pixels = new TextLabel::BinarySubdivisionalPixel();
pixels[pix_index] = sub_pixels;
}
sub_pixels->set(subpix_y * kDivision + subpix_x);
}
}
}
}
return pixels;
}
double Gauss(double sigma, double x, double y) {
const double sigma_coef = 0.5 / (sigma * sigma);
const double kInvPi = 0.31830988618379067153776752674503;
return kInvPi * sigma_coef * exp(-(x * x + y * y) * sigma_coef);
}
} // namespace
RGBColor::RGBColor()
: r(ValueType()),
g(ValueType()),
b(ValueType()) {}
RGBColor::RGBColor(ValueType red, ValueType green, ValueType blue)
: r(red),
g(green),
b(blue) {}
bool RGBColor::operator==(const RGBColor &that) const {
return r == that.r && g == that.g && b == that.b;
}
bool RGBColor::operator!=(const RGBColor &that) const {
return !(*this == that);
}
const RGBColor RGBColor::kBlack(0, 0, 0);
const RGBColor RGBColor::kWhite(255, 255, 255);
ARGBColor::ARGBColor()
: a(0),
r(0),
g(0),
b(0) {}
ARGBColor::ARGBColor(
ValueType alpha, ValueType red, ValueType green, ValueType blue)
: a(alpha),
r(red),
g(green),
b(blue) {}
ARGBColor::ARGBColor(const RGBColor &color, ValueType alpha)
: a(alpha),
r(color.r),
g(color.g),
b(color.b) {}
bool ARGBColor::operator==(const ARGBColor &that) const {
return a == that.a && r == that.r && g == that.g && b == that.b;
}
bool ARGBColor::operator!=(const ARGBColor &that) const {
return !(*this == that);
}
const ARGBColor ARGBColor::kBlack(255, 0, 0, 0);
const ARGBColor ARGBColor::kWhite(255, 255, 255, 255);
BalloonImage::BalloonImageInfo::BalloonImageInfo()
: frame_color(RGBColor(1, 122, 204)),
inside_color(RGBColor::kWhite),
blur_color(RGBColor(196, 196, 255)),
blur_alpha(1.0),
label_size(13),
rect_width(45.0),
rect_height(45.0),
frame_thickness(1.0),
corner_radius(0.0),
tail_height(5.0),
tail_width(10.0),
tail_direction(BalloonImage::BalloonImageInfo::kTop),
blur_sigma(3.0),
blur_offset_x(0),
blur_offset_y(0) {}
// static
HBITMAP BalloonImage::Create(const BalloonImageInfo &info, POINT *tail_offset) {
return CreateInternal(info, tail_offset, nullptr, nullptr);
}
// static
HBITMAP BalloonImage::CreateInternal(const BalloonImageInfo &info,
POINT *tail_offset,
SIZE *size,
vector<ARGBColor> *arbg_buffer) {
// Base point. You can set arbitrary position.
const double kLeft = 10.0;
const double kTop = 10.0;
const Balloon balloon(
kLeft, kTop, max(info.rect_width, 0.0), max(info.rect_height, 0.0),
max(info.frame_thickness, 0.0), max(info.corner_radius, 0.0),
max(info.tail_height, 0.0), max(info.tail_width, 0.0),
info.frame_color, info.inside_color, info.tail_direction);
const TextLabel label(kLeft + info.frame_thickness,
kTop + info.frame_thickness,
info.rect_width - 2.0 * info.frame_thickness,
info.rect_height - 2.0 * info.frame_thickness,
info.label,
info.label_font,
info.label_size,
info.label_color);
// Render image into a temporary buffer |frame_buffer|.
const Rect &rect = balloon.bounding_rect();
SafeFrameBuffer frame_buffer(rect);
for (int y = rect.Top(); y < rect.Bottom(); ++y) {
for (int x = rect.Left(); x < rect.Right(); ++x) {
SubdivisionalPixel sub_pixel;
balloon.RenderPixel(x, y, &sub_pixel);
label.RenderPixel(x, y, &sub_pixel);
const RGBColor color = sub_pixel.GetPixelColor();
const double coverage = sub_pixel.GetCoverage();
// As ARGBColor::ValueType is integer type, add +0.5 for rounding off.
static_assert(ARGBColor::ValueType(0.5) == ARGBColor::ValueType(),
"ARGBColor::ValueType should be integer type");
const ARGBColor::ValueType alpha =
static_cast<ARGBColor::ValueType>(coverage * 255.0 + 0.5);
frame_buffer.SetPixel(x, y, ARGBColor(color, alpha));
}
}
// Hereafter, we apply Gaussian blur.
GaussianBlur blur(info.blur_sigma);
const int begin_x =
rect.Left() - max(blur.cutoff_length() - info.blur_offset_x, 0);
const int begin_y =
rect.Top() - max(blur.cutoff_length() - info.blur_offset_y, 0);
const int end_x =
rect.Right() + max(blur.cutoff_length() + info.blur_offset_x, 0);
const int end_y =
rect.Bottom() + max(blur.cutoff_length() + info.blur_offset_y, 0);
const int bmp_width = end_x - begin_x;
const int bmp_height = end_y - begin_y;
if (size != nullptr) {
size->cx = bmp_width;
size->cy = bmp_height;
}
if (arbg_buffer != nullptr) {
arbg_buffer->clear();
arbg_buffer->resize(bmp_width * bmp_height);
}
// Note that +0.5 for rounding off is not necessary here.
if (tail_offset != nullptr) {
tail_offset->x =
static_cast<int>(floor(balloon.GetTailX() - begin_x));
tail_offset->y =
static_cast<int>(floor(balloon.GetTailY() - begin_y));
}
// GDI native alpha image is Premultiplied BGRA.
struct PBGRA {
uint8 b;
uint8 g;
uint8 r;
uint8 a;
};
BITMAPINFO bitmap_info = {};
bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmap_info.bmiHeader.biWidth = bmp_width;
bitmap_info.bmiHeader.biHeight = -bmp_height; // top-down BMP
bitmap_info.bmiHeader.biPlanes = 1;
bitmap_info.bmiHeader.biBitCount = 32;
bitmap_info.bmiHeader.biCompression = BI_RGB;
bitmap_info.bmiHeader.biSizeImage = 0;
PBGRA *buffer = nullptr;
CBitmap dib;
dib.CreateDIBSection(nullptr, &bitmap_info, DIB_RGB_COLORS,
reinterpret_cast<void **>(&buffer), nullptr, 0);
struct Accessor {
public:
Accessor(const SafeFrameBuffer &frame_buffer, int offset_x, int offset_y)
: frame_buffer_(frame_buffer),
offset_x_(offset_x),
offset_y_(offset_y) {}
double operator()(int x, int y) const {
return frame_buffer_.GetPixel(x + offset_x_, y + offset_y_).a;
}
private:
const SafeFrameBuffer &frame_buffer_;
const int offset_x_;
const int offset_y_;
};
const double normalized_blur_alpha = min(max(info.blur_alpha, 0.0), 1.0);
Accessor accessor(frame_buffer, -info.blur_offset_x, -info.blur_offset_y);
for (int y = begin_y; y < begin_y + bmp_height; ++y) {
for (int x = begin_x; x < begin_x + bmp_width; ++x) {
const int bmp_x = x - begin_x;
const int bmp_y = y - begin_y;
const size_t index = bmp_y * bmp_width + bmp_x;
PBGRA *dest = &buffer[index];
const ARGBColor fore_color = frame_buffer.GetPixel(x, y);
double alpha = 0.0;
double r = 0.0;
double g = 0.0;
double b = 0.0;
if (fore_color.a == 255) {
// Foreground color only.
alpha = fore_color.a;
r = fore_color.r;
g = fore_color.g;
b = fore_color.b;
} else if (fore_color.a == 0) {
// Background (blur) color only.
if (info.blur_sigma > 0.0) {
r = info.blur_color.r;
g = info.blur_color.g;
b = info.blur_color.b;
} else {
// Do not set blur color.
r = 0.0;
g = 0.0;
b = 0.0;
}
alpha = normalized_blur_alpha * blur.Apply(x, y, accessor);
} else {
// Foreground color and background blur are mixed.
const double fore_alpha = fore_color.a / 255.0;
const double bg_alpha =
normalized_blur_alpha * blur.Apply(x, y, accessor) / 255.0;
const double norm = fore_alpha + bg_alpha - fore_alpha * bg_alpha;
const double factor = (1.0 - fore_alpha) * bg_alpha;
alpha = norm * 255.0;
r = (factor * info.blur_color.r + fore_alpha * fore_color.r) / norm;
g = (factor * info.blur_color.g + fore_alpha * fore_color.g) / norm;
b = (factor * info.blur_color.b + fore_alpha * fore_color.b) / norm;
}
// Store BGRA image
{
const double norm_alpha = alpha / 255.0;
// As ARGBColor::ValueType is integer type, add +0.5 for rounding off.
static_assert(ARGBColor::ValueType(0.5) == ARGBColor::ValueType(),
"ARGBColor::ValueType should be integer type");
dest->b = static_cast<RGBColor::ValueType>(norm_alpha * b + 0.5);
dest->g = static_cast<RGBColor::ValueType>(norm_alpha * g + 0.5);
dest->r = static_cast<RGBColor::ValueType>(norm_alpha * r + 0.5);
dest->a = static_cast<RGBColor::ValueType>(alpha + 0.5);
}
// Store the original ARGB image for unit test.
if (arbg_buffer != nullptr) {
// As ARGBColor::ValueType is integer type, add +0.5 for rounding off.
static_assert(ARGBColor::ValueType(0.5) == ARGBColor::ValueType(),
"ARGBColor::ValueType should be integer type");
arbg_buffer->at(index) = ARGBColor(
static_cast<ARGBColor::ValueType>(alpha + 0.5),
static_cast<ARGBColor::ValueType>(r + 0.5),
static_cast<ARGBColor::ValueType>(g + 0.5),
static_cast<ARGBColor::ValueType>(b + 0.5));
}
}
}
return dib.Detach();
}
namespace internal {
SubdivisionalPixel::Fraction2D::Fraction2D()
: x(0),
y(0) {}
SubdivisionalPixel::Fraction2D::Fraction2D(size_t x_frac, size_t y_frac)
: x(x_frac),
y(y_frac) {}
SubdivisionalPixel::SubdivisionalPixelIterator::SubdivisionalPixelIterator(
int base_x, int base_y)
: base_x_(base_x),
base_y_(base_y),
numerator_x_(0),
numerator_y_(0) {}
SubdivisionalPixel::Fraction2D
SubdivisionalPixel::SubdivisionalPixelIterator::GetFraction() const {
return Fraction2D(numerator_x_, numerator_y_);
}
double SubdivisionalPixel::SubdivisionalPixelIterator::GetX() const {
return base_x_ + (numerator_x_ + 0.5) / kDivision;
}
double SubdivisionalPixel::SubdivisionalPixelIterator::GetY() const {
return base_y_ + (numerator_y_ + 0.5) / kDivision;
}
size_t SubdivisionalPixel::SubdivisionalPixelIterator::GetIndex() const {
return numerator_y_ * kDivision + numerator_x_;
}
void SubdivisionalPixel::SubdivisionalPixelIterator::Next() {
++numerator_x_;
if (numerator_x_ == kDivision) {
numerator_x_ = 0;
++numerator_y_;
}
}
bool SubdivisionalPixel::SubdivisionalPixelIterator::Done() const {
return numerator_y_ >= kDivision;
}
SubdivisionalPixel::SubdivisionalPixel()
: single_color_(ColorType::kBlack) {}
const double SubdivisionalPixel::GetCoverage() const {
switch (GetFillType()) {
case kEmpty:
return 0.0;
case kSingleColor:
return filled_.count() / static_cast<double>(kTotalPixels);
case kMultipleColors:
return filled_.count() / static_cast<double>(kTotalPixels);
default:
return 0.0;
}
}
const SubdivisionalPixel::ColorType SubdivisionalPixel::GetPixelColor() const {
switch (GetFillType()) {
case kEmpty:
return ColorType::kBlack;
case kSingleColor:
return single_color_;
case kMultipleColors: {
double r = 0;
double g = 0;
double b = 0;
for (size_t i = 0; i < filled_.size(); ++i) {
if (!filled_.test(i)) {
continue;
}
r += colors_[i].r;
g += colors_[i].g;
b += colors_[i].b;
}
// As ColorType::ValueType is integer type, add +0.5 for rounding off.
static_assert(ARGBColor::ValueType(0.5) == ARGBColor::ValueType(),
"ARGBColor::ValueType should be integer type");
return ColorType(ColorType::ValueType(r / filled_.count() + 0.5),
ColorType::ValueType(g / filled_.count() + 0.5),
ColorType::ValueType(b / filled_.count() + 0.5));
}
default:
return ColorType::kBlack;
}
}
void SubdivisionalPixel::SetPixel(const ColorType &color) {
filled_.set();
colors_.reset();
single_color_ = color;
}
void SubdivisionalPixel::SetSubdivisionalPixel(const Fraction2D &frac,
const ColorType &color) {
const size_t index = GetIndex(frac);
switch (GetFillType()) {
case kEmpty:
filled_.set(index);
colors_.reset();
single_color_ = color;
break;
case kSingleColor:
if (single_color_ != color) {
colors_.reset(new ColorType[kTotalPixels]);
for (size_t i = 0; i < filled_.size(); ++i) {
if (filled_.test(i)) {
colors_[i] = single_color_;
}
}
single_color_ = ColorType::kBlack;
colors_[index] = color;
}
filled_.set(index);
break;
case kMultipleColors:
filled_.set(index);
colors_[index] = color;
break;
}
}
void SubdivisionalPixel::SetColorToFilledPixels(const ColorType &color) {
switch (GetFillType()) {
case kSingleColor:
single_color_ = color;
break;
case kMultipleColors:
colors_.reset();
single_color_ = color;
break;
}
}
SubdivisionalPixel::FillType SubdivisionalPixel::GetFillType() const {
if (filled_.none()) {
return kEmpty;
}
if (colors_.get() == nullptr) {
return kSingleColor;
}
return kMultipleColors;
}
// static
size_t SubdivisionalPixel::GetIndex(const Fraction2D &offset) {
return kDivision * offset.y + offset.x;
}
GaussianBlur::GaussianBlur(double sigma)
: sigma_(sigma),
cutoff_length_(static_cast<int>(ceil(3.0 * sigma))) {
if (sigma <= 0.0) {
matrix_.push_back(MatrixElement(0, 0, 1.0));
return;
}
matrix_.reserve(GetMatrixLength() * GetMatrixLength());
for (int y = -cutoff_length_; y <= cutoff_length_; ++y) {
for (int x = -cutoff_length_; x <= cutoff_length_; ++x) {
matrix_.push_back(MatrixElement(x, y, Gauss(sigma_, x, y)));
}
}
// To minimize the loss of trailing digits, sort coefficients just in case.
// For most cases, this doesn't make a difference though.
struct MatrixElementSorter {
bool operator()(const MatrixElement &l, const MatrixElement &r) const {
return l.coefficient < r.coefficient;
}
};
MatrixElementSorter sorter;
sort(matrix_.begin(), matrix_.end(), sorter);
double sum = 0.0;
for (Matrix::const_iterator it = matrix_.begin(); it != matrix_.end(); ++it) {
sum += it->coefficient;
}
// Normalize
for (Matrix::iterator it = matrix_.begin(); it != matrix_.end(); ++it) {
it->coefficient /= sum;
}
}
int GaussianBlur::cutoff_length() const {
return cutoff_length_;
}
GaussianBlur::MatrixElement::MatrixElement()
: offset_x(0),
offset_y(0),
coefficient(0.0) {}
GaussianBlur::MatrixElement::MatrixElement(int x, int y, double c)
: offset_x(x),
offset_y(y),
coefficient(c) {}
size_t GaussianBlur::GetMatrixLength() const {
return 2 * cutoff_length_ + 1;
}
SafeFrameBuffer::SafeFrameBuffer(const Rect &rect)
: rect_(rect),
buffer_(new ARGBColor[rect.Width() * rect.Height()]) {
}
ARGBColor SafeFrameBuffer::GetPixel(int x, int y) const {
if (!IsInWindow(x, y)) {
return ARGBColor();
}
return buffer_[GetIndex(x, y)];
}
void SafeFrameBuffer::SetPixel(int x, int y, const ARGBColor &color) {
if (!IsInWindow(x, y)) {
return;
}
buffer_[GetIndex(x, y)] = color;
}
bool SafeFrameBuffer::IsInWindow(int x, int y) const {
if (x < rect_.Left() || rect_.Right() <= x) {
return false;
}
if (y < rect_.Top() || rect_.Bottom() <= y) {
return false;
}
return true;
}
size_t SafeFrameBuffer::GetIndex(int x, int y) const {
DCHECK(IsInWindow(x, y));
return (y - rect_.Top()) * rect_.Width() + (x - rect_.Left());
}
TextLabel::TextLabel(double left,
double top,
double width,
double height,
const string &text,
const string &font,
size_t font_point,
const RGBColor text_color)
: bounding_rect_(GetBoundingRect(left, top, width, height)),
pixels_(Get1bitGlyph(left, top, width, height, text, font, font_point)),
text_color_(text_color) {}
TextLabel::~TextLabel() {
STLDeleteContainerPointers(pixels_.begin(), pixels_.end());
}
void TextLabel::RenderPixel(int x, int y, SubdivisionalPixel *dest) const {
if (x < bounding_rect_.Left() || bounding_rect_.Right() <= x ||
y < bounding_rect_.Top() || bounding_rect_.Bottom() <= y) {
return;
}
const int pix_width = bounding_rect_.Width();
const size_t index = (y - bounding_rect_.Top()) * pix_width +
(x - bounding_rect_.Left());
const BinarySubdivisionalPixel *sub_pixels = pixels_[index];
if (sub_pixels == nullptr) {
return;
}
for (SubdivisionalPixel::SubdivisionalPixelIterator it(x, y);
!it.Done(); it.Next()) {
if (sub_pixels->test(it.GetIndex())) {
dest->SetSubdivisionalPixel(it.GetFraction(), text_color_);
}
}
}
const Rect &TextLabel::bounding_rect() const {
return bounding_rect_;
}
} // namespace internal
} // namespace win32
} // namespace renderer
} // namespace mozc