279 lines
8.6 KiB
C++
279 lines
8.6 KiB
C++
/*
|
|
*******************************************************************************
|
|
* INCLUDE
|
|
*******************************************************************************
|
|
*/
|
|
#include "Image.h"
|
|
#include "Logger.h"
|
|
#include "util/Math.h"
|
|
#include <freetype/ftglyph.h>
|
|
#include <freetype/fterrors.h>
|
|
#include <Poco/Exception.h>
|
|
#include <Poco/Format.h>
|
|
#include <Poco/UnicodeConverter.h>
|
|
|
|
namespace Video
|
|
{
|
|
namespace Core
|
|
{
|
|
/*
|
|
*******************************************************************************
|
|
* CLASS DECLARE
|
|
*******************************************************************************
|
|
*/
|
|
void Image::drawText(const std::string& text, const std::string& font, int size, Color foreColor, Color bgColor)
|
|
{
|
|
get_shared_channel(0).fill(bgColor.r);
|
|
get_shared_channel(1).fill(bgColor.g);
|
|
get_shared_channel(2).fill(bgColor.b);
|
|
get_shared_channel(3).fill(bgColor.a);
|
|
|
|
int x = 0, y = 0;
|
|
|
|
y = height() - y;
|
|
|
|
std::wstring wtext;
|
|
convertText(text, wtext);
|
|
runOnFreeType(font, size,
|
|
[&](FT_Face& face) -> void
|
|
{
|
|
FT_BBox bbox;
|
|
|
|
computeStringBBox(wtext, face, bbox);
|
|
|
|
FT_Vector pen;
|
|
pen.x = (x - bbox.xMin) * 64;
|
|
pen.y = (y - bbox.yMax) * 64;
|
|
|
|
for (size_t i = 0; i < wtext.length(); i++)
|
|
{
|
|
FT_Set_Transform(face, 0, &pen);
|
|
FT_UInt glyph_index = FT_Get_Char_Index(face, wtext.at(i));
|
|
int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
|
|
if (error)
|
|
{
|
|
throw Poco::InvalidAccessException("字形加载失败", error);
|
|
}
|
|
error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
|
|
if (error)
|
|
{
|
|
throw Poco::InvalidAccessException("字形渲染失败", error);
|
|
}
|
|
|
|
int offsetX = face->glyph->bitmap_left;
|
|
int offsetY = height() - face->glyph->bitmap_top;
|
|
for (size_t y = 0; y < face->glyph->bitmap.rows; y++)
|
|
{
|
|
for (size_t x = 0; x < face->glyph->bitmap.width; x++)
|
|
{
|
|
uint8_t c = face->glyph->bitmap.buffer[y * face->glyph->bitmap.width + x];
|
|
if (c < 50)
|
|
continue;
|
|
|
|
(*this)(x + offsetX, y + offsetY, 0) = foreColor.r * c / 255;
|
|
(*this)(x + offsetX, y + offsetY, 1) = foreColor.g * c / 255;
|
|
(*this)(x + offsetX, y + offsetY, 2) = foreColor.b * c / 255;
|
|
(*this)(x + offsetX, y + offsetY, 3) = foreColor.a * c / 255;
|
|
}
|
|
}
|
|
|
|
pen.x += face->glyph->advance.x;
|
|
pen.y += face->glyph->advance.y;
|
|
}
|
|
});
|
|
}
|
|
|
|
void Image::drawRectangle(const Rect& rect, Color& color, int border)
|
|
{
|
|
if (border <= 0)
|
|
{
|
|
get_shared_channel(0).fill(color.r);
|
|
get_shared_channel(1).fill(color.g);
|
|
get_shared_channel(2).fill(color.b);
|
|
get_shared_channel(3).fill(color.a);
|
|
}
|
|
else
|
|
{
|
|
get_shared_channel(3).fill(0);
|
|
|
|
uint8_t c[4] = {color.r, color.g, color.b, color.a};
|
|
|
|
draw_rectangle(rect.x, rect.y, rect.x + rect.width, rect.y + border, c);
|
|
draw_rectangle(rect.x, rect.y + border, rect.x + border, rect.y + rect.height, c);
|
|
draw_rectangle(rect.x + rect.width - border, rect.y + border, rect.x + rect.width, rect.y + rect.height, c);
|
|
draw_rectangle(rect.x + border, rect.y + rect.height - border, rect.x + rect.width - border,
|
|
rect.y + rect.height, c);
|
|
}
|
|
}
|
|
|
|
void Image::drawImage(int x, int y, Ptr pImage)
|
|
{
|
|
const int width = pImage->width();
|
|
const int height = pImage->height();
|
|
|
|
for (int sy = 0; sy < height; ++sy)
|
|
{
|
|
for (int sx = 0; sx < width; ++sx)
|
|
{
|
|
const int dx = x + sx;
|
|
const int dy = y + sy;
|
|
|
|
if (dx >= 0 && dx < this->width() && dy >= 0 && dy < this->height())
|
|
{
|
|
Color color;
|
|
pImage->getPixel(sx, sy, color);
|
|
|
|
(*this)(dx, dy, 0, 0) = color.r;
|
|
(*this)(dx, dy, 0, 1) = color.g;
|
|
(*this)(dx, dy, 0, 2) = color.b;
|
|
(*this)(dx, dy, 0, 3) = color.a;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Image::getPixel(int x, int y, Color& color)
|
|
{
|
|
uint8_t originAlpha = 0xff;
|
|
|
|
if (spectrum() == 1)
|
|
{
|
|
color.r = (*this)(x, y, 0, 0);
|
|
color.g = color.r;
|
|
color.b = color.r;
|
|
}
|
|
else if (spectrum() == 2)
|
|
{
|
|
color.r = (*this)(x, y, 0, 0);
|
|
color.g = (*this)(x, y, 0, 1);
|
|
color.b = 0;
|
|
}
|
|
|
|
else if (spectrum() == 3)
|
|
{
|
|
color.r = (*this)(x, y, 0, 0);
|
|
color.g = (*this)(x, y, 0, 1);
|
|
color.b = (*this)(x, y, 0, 2);
|
|
}
|
|
else if (spectrum() == 4)
|
|
{
|
|
color.r = (*this)(x, y, 0, 0);
|
|
color.g = (*this)(x, y, 0, 1);
|
|
color.b = (*this)(x, y, 0, 2);
|
|
originAlpha = (*this)(x, y, 0, 3);
|
|
}
|
|
|
|
if (_forceAlphaEnabled && originAlpha > _forceAlphaValue)
|
|
color.a = _forceAlphaValue;
|
|
else
|
|
color.a = originAlpha;
|
|
}
|
|
|
|
void Image::forceAlpha(uint8_t alpha)
|
|
{
|
|
_forceAlphaEnabled = true;
|
|
_forceAlphaValue = alpha;
|
|
}
|
|
|
|
void Image::computeStringSize(const std::string& text, const std::string& font, int fontsz, Size& size)
|
|
{
|
|
runOnFreeType(font, fontsz,
|
|
[&](FT_Face& face) -> void
|
|
{
|
|
FT_BBox bbox;
|
|
std::wstring wstr;
|
|
convertText(text, wstr);
|
|
|
|
computeStringBBox(wstr, face, bbox);
|
|
size.width = bbox.xMax - bbox.xMin;
|
|
size.height = bbox.yMax - bbox.yMin;
|
|
|
|
size.width = Util::Math::alignup(size.width, 2);
|
|
size.height = Util::Math::alignup(size.height, 2);
|
|
});
|
|
}
|
|
|
|
int Image::computeStringBBox(std::wstring text, FT_Face face, FT_BBox& bbox)
|
|
{
|
|
size_t i;
|
|
int error;
|
|
FT_BBox glyph_bbox;
|
|
FT_Vector pen;
|
|
FT_Glyph glyph;
|
|
FT_GlyphSlot slot = face->glyph;
|
|
|
|
bbox.xMin = bbox.yMin = 32000;
|
|
bbox.xMax = bbox.yMax = -32000;
|
|
|
|
pen.x = 0;
|
|
pen.y = 0;
|
|
|
|
for (i = 0; i < text.length(); i++)
|
|
{
|
|
FT_Set_Transform(face, 0, &pen);
|
|
|
|
error = FT_Load_Char(face, text.at(i), FT_LOAD_RENDER);
|
|
if (error)
|
|
{
|
|
logger().error("FT_Load_Char error: %d(%s)", error, std::string(FT_Error_String(error)));
|
|
return -1;
|
|
}
|
|
|
|
error = FT_Get_Glyph(face->glyph, &glyph);
|
|
if (error)
|
|
{
|
|
logger().error("FT_Get_Glyph error: %d(%s)", error, std::string(FT_Error_String(error)));
|
|
return -1;
|
|
}
|
|
|
|
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);
|
|
|
|
if (glyph_bbox.xMin < bbox.xMin)
|
|
bbox.xMin = glyph_bbox.xMin;
|
|
|
|
if (glyph_bbox.yMin < bbox.yMin)
|
|
bbox.yMin = glyph_bbox.yMin;
|
|
|
|
if (glyph_bbox.xMax > bbox.xMax)
|
|
bbox.xMax = glyph_bbox.xMax;
|
|
|
|
if (glyph_bbox.yMax > bbox.yMax)
|
|
bbox.yMax = glyph_bbox.yMax;
|
|
|
|
FT_Done_Glyph(glyph);
|
|
|
|
pen.x += slot->advance.x;
|
|
pen.y += slot->advance.y;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Image::convertText(const std::string& from, std::wstring& to)
|
|
{
|
|
Poco::UnicodeConverter::toUTF16(from, to);
|
|
}
|
|
|
|
void Image::runOnFreeType(const std::string& font, int size, std::function<void(FT_Face&)> func)
|
|
{
|
|
FT_Library library;
|
|
FT_Face face;
|
|
FT_Error error;
|
|
|
|
error = FT_Init_FreeType(&library);
|
|
if (error)
|
|
throw Poco::InvalidAccessException(
|
|
Poco::format("FreeType init error: %d(%s)", error, std::string(FT_Error_String(error))), error);
|
|
error = FT_New_Face(library, font.c_str(), 0, &face);
|
|
if (error)
|
|
throw Poco::InvalidAccessException(
|
|
Poco::format("FreeType new face error: %d(%s)", error, std::string(FT_Error_String(error))), error);
|
|
|
|
FT_Set_Pixel_Sizes(face, 0, size);
|
|
func(face);
|
|
|
|
FT_Done_Face(face);
|
|
FT_Done_FreeType(library);
|
|
}
|
|
}
|
|
}
|