|
|
|
//
|
|
|
|
// Created by Matthew on 2024/1/4.
|
|
|
|
//
|
|
|
|
#include <ft2build.h>
|
|
|
|
#include FT_FREETYPE_H
|
|
|
|
#include FT_OUTLINE_H
|
|
|
|
#include FT_STROKER_H
|
|
|
|
#include FT_IMAGE_H
|
|
|
|
#include FT_BBOX_H
|
|
|
|
|
|
|
|
#include <locale>
|
|
|
|
#include <codecvt>
|
|
|
|
#include "CvText.h"
|
|
|
|
#include <opencv2/imgproc.hpp>
|
|
|
|
|
|
|
|
namespace cv {
|
|
|
|
namespace ft {
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
class FreeType2Impl : public FreeType2
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
FreeType2Impl();
|
|
|
|
~FreeType2Impl();
|
|
|
|
void loadFontData(String fontFileName, int idx);
|
|
|
|
void setSplitNumber(int num);
|
|
|
|
void putText(
|
|
|
|
InputOutputArray img, const String& text, Point org,
|
|
|
|
int fontHeight, Scalar color,
|
|
|
|
int thickness, int line_type, bool bottomLeftOrigin
|
|
|
|
);
|
|
|
|
Size getTextSize(
|
|
|
|
const String& text, int fontHeight, int thickness,
|
|
|
|
CV_OUT int* baseLine
|
|
|
|
);
|
|
|
|
|
|
|
|
private:
|
|
|
|
FT_Library mLibrary;
|
|
|
|
FT_Face mFace;
|
|
|
|
FT_Outline_Funcs mFn;
|
|
|
|
|
|
|
|
bool mIsFaceAvailable;
|
|
|
|
int mCtoL;
|
|
|
|
|
|
|
|
void putTextBitmapMono(
|
|
|
|
InputOutputArray img, const String& text, Point org,
|
|
|
|
int fontHeight, Scalar color,
|
|
|
|
int thickness, int line_type, bool bottomLeftOrigin
|
|
|
|
);
|
|
|
|
|
|
|
|
void putTextBitmapBlend(
|
|
|
|
InputOutputArray img, const String& text, Point org,
|
|
|
|
int fontHeight, Scalar color,
|
|
|
|
int thickness, int line_type, bool bottomLeftOrigin
|
|
|
|
);
|
|
|
|
|
|
|
|
void putTextOutline(
|
|
|
|
InputOutputArray img, const String& text, Point org,
|
|
|
|
int fontHeight, Scalar color,
|
|
|
|
int thickness, int line_type, bool bottomLeftOrigin
|
|
|
|
);
|
|
|
|
|
|
|
|
typedef void (putPixel_mono_fn)(Mat& _dst, const int _py, const int _px, const uint8_t *_col);
|
|
|
|
putPixel_mono_fn putPixel_8UC1_mono;
|
|
|
|
putPixel_mono_fn putPixel_8UC3_mono;
|
|
|
|
putPixel_mono_fn putPixel_8UC4_mono;
|
|
|
|
|
|
|
|
typedef void (putPixel_blend_fn)(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha);
|
|
|
|
putPixel_blend_fn putPixel_8UC1_blend;
|
|
|
|
putPixel_blend_fn putPixel_8UC3_blend;
|
|
|
|
putPixel_blend_fn putPixel_8UC4_blend;
|
|
|
|
|
|
|
|
static int mvFn(const FT_Vector *to, void * user);
|
|
|
|
static int lnFn(const FT_Vector *to, void * user);
|
|
|
|
static int coFn(const FT_Vector *cnt,
|
|
|
|
const FT_Vector *to,
|
|
|
|
void * user);
|
|
|
|
static int cuFn(const FT_Vector *cnt1,
|
|
|
|
const FT_Vector *cnt2,
|
|
|
|
const FT_Vector *to,
|
|
|
|
void * user);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert from FT_F26Dot6 to int(coodinate of OpenCV)
|
|
|
|
* (FT_F26Dot6 is signed 26.6 real)
|
|
|
|
*/
|
|
|
|
static int ftd(FT_F26Dot6 fixedInt) {
|
|
|
|
if (fixedInt > 0) {
|
|
|
|
return (fixedInt + 32) / 64;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return (fixedInt - 32) / 64;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PathUserData {
|
|
|
|
private:
|
|
|
|
public:
|
|
|
|
PathUserData(InputOutputArray _img) : mImg(_img) {};
|
|
|
|
|
|
|
|
InputOutputArray mImg;
|
|
|
|
Scalar mColor;
|
|
|
|
int mThickness;
|
|
|
|
int mLine_type;
|
|
|
|
FT_Vector mOldP;
|
|
|
|
int mCtoL;
|
|
|
|
std::vector < Point > mPts;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
FreeType2Impl::FreeType2Impl()
|
|
|
|
{
|
|
|
|
FT_Init_FreeType(&(this->mLibrary));
|
|
|
|
|
|
|
|
mCtoL = 16;
|
|
|
|
mFn.shift = 0;
|
|
|
|
mFn.delta = 0;
|
|
|
|
mFn.move_to = FreeType2Impl::mvFn;
|
|
|
|
mFn.line_to = FreeType2Impl::lnFn;
|
|
|
|
mFn.cubic_to = FreeType2Impl::cuFn;
|
|
|
|
mFn.conic_to = FreeType2Impl::coFn;
|
|
|
|
|
|
|
|
mIsFaceAvailable = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FreeType2Impl::~FreeType2Impl()
|
|
|
|
{
|
|
|
|
if (mIsFaceAvailable == true)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
hb_font_destroy(mHb_font);
|
|
|
|
#endif
|
|
|
|
CV_Assert(!FT_Done_Face(mFace));
|
|
|
|
mIsFaceAvailable = false;
|
|
|
|
}
|
|
|
|
CV_Assert(!FT_Done_FreeType(mLibrary));
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::loadFontData(String fontFileName, int idx)
|
|
|
|
{
|
|
|
|
CV_Assert(idx >= 0);
|
|
|
|
if (mIsFaceAvailable == true)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
hb_font_destroy(mHb_font);
|
|
|
|
#endif
|
|
|
|
CV_Assert(!FT_Done_Face(mFace));
|
|
|
|
}
|
|
|
|
|
|
|
|
mIsFaceAvailable = false;
|
|
|
|
CV_Assert(!FT_New_Face(mLibrary, fontFileName.c_str(), static_cast<FT_Long>(idx), &(mFace)));
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
mHb_font = hb_ft_font_create(mFace, NULL);
|
|
|
|
if (mHb_font == NULL)
|
|
|
|
{
|
|
|
|
CV_Assert(!FT_Done_Face(mFace));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
CV_Assert(mHb_font != NULL);
|
|
|
|
#endif
|
|
|
|
mIsFaceAvailable = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::setSplitNumber(int num) {
|
|
|
|
CV_Assert(num > 0);
|
|
|
|
mCtoL = num;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putText(
|
|
|
|
InputOutputArray _img, const String& _text, Point _org,
|
|
|
|
int _fontHeight, Scalar _color,
|
|
|
|
int _thickness, int _line_type, bool _bottomLeftOrigin
|
|
|
|
)
|
|
|
|
{
|
|
|
|
CV_Assert(mIsFaceAvailable == true);
|
|
|
|
CV_Assert(_img.empty() == false);
|
|
|
|
CV_Assert(_img.isMat() == true);
|
|
|
|
CV_Assert(_img.dims() == 2);
|
|
|
|
CV_Assert((_img.type() == CV_8UC1) ||
|
|
|
|
(_img.type() == CV_8UC3) ||
|
|
|
|
(_img.type() == CV_8UC4));
|
|
|
|
CV_Assert((_line_type == LINE_AA) ||
|
|
|
|
(_line_type == LINE_4) ||
|
|
|
|
(_line_type == LINE_8));
|
|
|
|
CV_Assert(_fontHeight >= 0);
|
|
|
|
|
|
|
|
if (_text.empty())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (_fontHeight == 0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CV_Assert(!FT_Set_Pixel_Sizes(mFace, _fontHeight, _fontHeight));
|
|
|
|
|
|
|
|
if (_thickness < 0) // CV_FILLED
|
|
|
|
{
|
|
|
|
if (_line_type == LINE_AA) {
|
|
|
|
putTextBitmapBlend(_img, _text, _org, _fontHeight, _color,
|
|
|
|
_thickness, _line_type, _bottomLeftOrigin);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
putTextBitmapMono(_img, _text, _org, _fontHeight, _color,
|
|
|
|
_thickness, _line_type, _bottomLeftOrigin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
putTextOutline(_img, _text, _org, _fontHeight, _color,
|
|
|
|
_thickness, _line_type, _bottomLeftOrigin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putTextOutline(
|
|
|
|
InputOutputArray _img, const String& _text, Point _org,
|
|
|
|
int _fontHeight, Scalar _color,
|
|
|
|
int _thickness, int _line_type, bool _bottomLeftOrigin)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
hb_buffer_t *hb_buffer = hb_buffer_create();
|
|
|
|
CV_Assert(hb_buffer != NULL);
|
|
|
|
|
|
|
|
hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
|
|
|
|
hb_buffer_guess_segment_properties(hb_buffer);
|
|
|
|
hb_shape(mHb_font, hb_buffer, NULL, 0);
|
|
|
|
|
|
|
|
unsigned int textLen = 0;
|
|
|
|
hb_glyph_info_t *info =
|
|
|
|
hb_buffer_get_glyph_infos(hb_buffer, &textLen);
|
|
|
|
CV_Assert(info != NULL);
|
|
|
|
#else
|
|
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
|
|
wstring wstr = converter.from_bytes(_text);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
PathUserData *userData = new PathUserData(_img);
|
|
|
|
userData->mColor = _color;
|
|
|
|
userData->mCtoL = mCtoL;
|
|
|
|
userData->mThickness = _thickness;
|
|
|
|
userData->mLine_type = _line_type;
|
|
|
|
|
|
|
|
// Initilize currentPosition ( in FreeType coordinates)
|
|
|
|
FT_Vector currentPos = { 0,0 };
|
|
|
|
currentPos.x = _org.x * 64;
|
|
|
|
currentPos.y = _org.y * 64;
|
|
|
|
|
|
|
|
// Update currentPosition with bottomLeftOrigin ( in FreeType coordinates)
|
|
|
|
if (_bottomLeftOrigin != true) {
|
|
|
|
currentPos.y += _fontHeight * 64;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(USING_HB)
|
|
|
|
for (unsigned int i = 0; i < textLen; i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
|
|
|
|
#else
|
|
|
|
for (unsigned int i = 0; i < wstr.size(); i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0));
|
|
|
|
#endif
|
|
|
|
FT_GlyphSlot slot = mFace->glyph;
|
|
|
|
FT_Outline outline = slot->outline;
|
|
|
|
|
|
|
|
// Flip ( in FreeType coordinates )
|
|
|
|
FT_Matrix mtx = { 1 << 16 , 0 , 0 , -(1 << 16) };
|
|
|
|
FT_Outline_Transform(&outline, &mtx);
|
|
|
|
|
|
|
|
// Move to current position ( in FreeType coordinates )
|
|
|
|
FT_Outline_Translate(&outline,
|
|
|
|
currentPos.x,
|
|
|
|
currentPos.y);
|
|
|
|
|
|
|
|
// Draw ( in FreeType coordinates )
|
|
|
|
CV_Assert(!FT_Outline_Decompose(&outline, &mFn, (void*)userData));
|
|
|
|
|
|
|
|
// Draw (Last Path) ( in FreeType coordinates )
|
|
|
|
mvFn(NULL, (void*)userData);
|
|
|
|
|
|
|
|
// Update current position ( in FreeType coordinates )
|
|
|
|
currentPos.x += mFace->glyph->advance.x;
|
|
|
|
currentPos.y += mFace->glyph->advance.y;
|
|
|
|
}
|
|
|
|
delete userData;
|
|
|
|
#if defined(USING_HB)
|
|
|
|
hb_buffer_destroy(hb_buffer);
|
|
|
|
#endif 0
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putPixel_8UC1_mono(Mat& _dst, const int _py, const int _px, const uint8_t *_col)
|
|
|
|
{
|
|
|
|
uint8_t* ptr = _dst.ptr<uint8_t>(_py, _px);
|
|
|
|
(*ptr) = _col[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putPixel_8UC3_mono(Mat& _dst, const int _py, const int _px, const uint8_t *_col)
|
|
|
|
{
|
|
|
|
cv::Vec3b* ptr = _dst.ptr<cv::Vec3b>(_py, _px);
|
|
|
|
(*ptr)[0] = _col[0];
|
|
|
|
(*ptr)[1] = _col[1];
|
|
|
|
(*ptr)[2] = _col[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putPixel_8UC4_mono(Mat& _dst, const int _py, const int _px, const uint8_t *_col)
|
|
|
|
{
|
|
|
|
cv::Vec4b* ptr = _dst.ptr<cv::Vec4b>(_py, _px);
|
|
|
|
(*ptr)[0] = _col[0];
|
|
|
|
(*ptr)[1] = _col[1];
|
|
|
|
(*ptr)[2] = _col[2];
|
|
|
|
(*ptr)[3] = _col[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putTextBitmapMono(
|
|
|
|
InputOutputArray _img, const String& _text, Point _org,
|
|
|
|
int _fontHeight, Scalar _color,
|
|
|
|
int _thickness, int _line_type, bool _bottomLeftOrigin)
|
|
|
|
{
|
|
|
|
CV_Assert(_thickness < 0);
|
|
|
|
CV_Assert(_line_type == LINE_4 || _line_type == LINE_8);
|
|
|
|
|
|
|
|
Mat dst = _img.getMat();
|
|
|
|
#if defined(USING_HB)
|
|
|
|
hb_buffer_t *hb_buffer = hb_buffer_create();
|
|
|
|
CV_Assert(hb_buffer != NULL);
|
|
|
|
|
|
|
|
hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
|
|
|
|
hb_buffer_guess_segment_properties(hb_buffer);
|
|
|
|
hb_shape(mHb_font, hb_buffer, NULL, 0);
|
|
|
|
|
|
|
|
unsigned int textLen = 0;
|
|
|
|
hb_glyph_info_t *info =
|
|
|
|
hb_buffer_get_glyph_infos(hb_buffer, &textLen);
|
|
|
|
CV_Assert(info != NULL);
|
|
|
|
#else
|
|
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
|
|
wstring wstr = converter.from_bytes(_text);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
_org.y += _fontHeight;
|
|
|
|
if (_bottomLeftOrigin == true) {
|
|
|
|
_org.y -= _fontHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t _colorUC8n[4] = {
|
|
|
|
static_cast<uint8_t>(_color[0]),
|
|
|
|
static_cast<uint8_t>(_color[1]),
|
|
|
|
static_cast<uint8_t>(_color[2]),
|
|
|
|
static_cast<uint8_t>(_color[3]) };
|
|
|
|
|
|
|
|
void (cv::ft::FreeType2Impl::*putPixel)(Mat&, const int, const int, const uint8_t*) =
|
|
|
|
(_img.type() == CV_8UC4) ? (&FreeType2Impl::putPixel_8UC4_mono) :
|
|
|
|
(_img.type() == CV_8UC3) ? (&FreeType2Impl::putPixel_8UC3_mono) :
|
|
|
|
(&FreeType2Impl::putPixel_8UC1_mono);
|
|
|
|
|
|
|
|
#if defined(USING_HB)
|
|
|
|
for (unsigned int i = 0; i < textLen; i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
|
|
|
|
#else
|
|
|
|
for (unsigned int i = 0; i < wstr.size(); i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0));
|
|
|
|
#endif
|
|
|
|
CV_Assert(!FT_Render_Glyph(mFace->glyph, FT_RENDER_MODE_MONO));
|
|
|
|
FT_Bitmap *bmp = &(mFace->glyph->bitmap);
|
|
|
|
|
|
|
|
Point gPos = _org;
|
|
|
|
gPos.y -= (mFace->glyph->metrics.horiBearingY >> 6);
|
|
|
|
gPos.x += (mFace->glyph->metrics.horiBearingX >> 6);
|
|
|
|
|
|
|
|
for (int row = 0; row < (int)bmp->rows; row++) {
|
|
|
|
if (gPos.y + row < 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (gPos.y + row >= dst.rows) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int col = 0; col < bmp->pitch; col++) {
|
|
|
|
int cl = bmp->buffer[row * bmp->pitch + col];
|
|
|
|
if (cl == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (int bit = 7; bit >= 0; bit--) {
|
|
|
|
if (gPos.x + col * 8 + (7 - bit) < 0)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (gPos.x + col * 8 + (7 - bit) >= dst.cols)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (((cl >> bit) & 0x01) == 1) {
|
|
|
|
(this->*putPixel)(dst, gPos.y + row, gPos.x + col * 8 + (7 - bit), _colorUC8n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_org.x += (mFace->glyph->advance.x) >> 6;
|
|
|
|
_org.y += (mFace->glyph->advance.y) >> 6;
|
|
|
|
}
|
|
|
|
#if defined(USING_HB)
|
|
|
|
hb_buffer_destroy(hb_buffer);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alpha composite algorithm is porting from imgproc.
|
|
|
|
// See https://github.com/opencv/opencv/blob/4.6.0/modules/imgproc/src/drawing.cpp
|
|
|
|
// static void LineAA( Mat& img, Point2l pt1, Point2l pt2, const void* color )
|
|
|
|
// ICV_PUT_POINT Macro.
|
|
|
|
|
|
|
|
void FreeType2Impl::putPixel_8UC1_blend(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha)
|
|
|
|
{
|
|
|
|
const int a = alpha;
|
|
|
|
const int cb = _col[0];
|
|
|
|
uint8_t* tptr = _dst.ptr<uint8_t>(_py, _px);
|
|
|
|
|
|
|
|
int _cb = static_cast<int>(tptr[0]);
|
|
|
|
_cb += ((cb - _cb)*a + 127) >> 8;
|
|
|
|
_cb += ((cb - _cb)*a + 127) >> 8;
|
|
|
|
|
|
|
|
tptr[0] = static_cast<uint8_t>(_cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putPixel_8UC3_blend(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha)
|
|
|
|
{
|
|
|
|
const int a = alpha;
|
|
|
|
const int cb = _col[0];
|
|
|
|
const int cg = _col[1];
|
|
|
|
const int cr = _col[2];
|
|
|
|
uint8_t* tptr = _dst.ptr<uint8_t>(_py, _px);
|
|
|
|
|
|
|
|
int _cb = static_cast<int>(tptr[0]);
|
|
|
|
_cb += ((cb - _cb)*a + 127) >> 8;
|
|
|
|
_cb += ((cb - _cb)*a + 127) >> 8;
|
|
|
|
|
|
|
|
int _cg = static_cast<int>(tptr[1]);
|
|
|
|
_cg += ((cg - _cg)*a + 127) >> 8;
|
|
|
|
_cg += ((cg - _cg)*a + 127) >> 8;
|
|
|
|
|
|
|
|
int _cr = static_cast<int>(tptr[2]);
|
|
|
|
_cr += ((cr - _cr)*a + 127) >> 8;
|
|
|
|
_cr += ((cr - _cr)*a + 127) >> 8;
|
|
|
|
|
|
|
|
tptr[0] = static_cast<uint8_t>(_cb);
|
|
|
|
tptr[1] = static_cast<uint8_t>(_cg);
|
|
|
|
tptr[2] = static_cast<uint8_t>(_cr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putPixel_8UC4_blend(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha)
|
|
|
|
{
|
|
|
|
const uint8_t a = alpha;
|
|
|
|
const int cb = _col[0];
|
|
|
|
const int cg = _col[1];
|
|
|
|
const int cr = _col[2];
|
|
|
|
const int ca = _col[3];
|
|
|
|
uint8_t* tptr = _dst.ptr<uint8_t>(_py, _px);
|
|
|
|
|
|
|
|
int _cb = static_cast<int>(tptr[0]);
|
|
|
|
_cb += ((cb - _cb)*a + 127) >> 8;
|
|
|
|
_cb += ((cb - _cb)*a + 127) >> 8;
|
|
|
|
|
|
|
|
int _cg = static_cast<int>(tptr[1]);
|
|
|
|
_cg += ((cg - _cg)*a + 127) >> 8;
|
|
|
|
_cg += ((cg - _cg)*a + 127) >> 8;
|
|
|
|
|
|
|
|
int _cr = static_cast<int>(tptr[2]);
|
|
|
|
_cr += ((cr - _cr)*a + 127) >> 8;
|
|
|
|
_cr += ((cr - _cr)*a + 127) >> 8;
|
|
|
|
|
|
|
|
int _ca = static_cast<int>(tptr[3]);
|
|
|
|
_ca += ((ca - _ca)*a + 127) >> 8;
|
|
|
|
_ca += ((ca - _ca)*a + 127) >> 8;
|
|
|
|
|
|
|
|
tptr[0] = static_cast<uint8_t>(_cb);
|
|
|
|
tptr[1] = static_cast<uint8_t>(_cg);
|
|
|
|
tptr[2] = static_cast<uint8_t>(_cr);
|
|
|
|
tptr[3] = static_cast<uint8_t>(_ca);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FreeType2Impl::putTextBitmapBlend(
|
|
|
|
InputOutputArray _img, const String& _text, Point _org,
|
|
|
|
int _fontHeight, Scalar _color,
|
|
|
|
int _thickness, int _line_type, bool _bottomLeftOrigin)
|
|
|
|
{
|
|
|
|
|
|
|
|
CV_Assert(_thickness < 0);
|
|
|
|
CV_Assert(_line_type == LINE_AA);
|
|
|
|
|
|
|
|
Mat dst = _img.getMat();
|
|
|
|
#if defined(USING_HB)
|
|
|
|
hb_buffer_t *hb_buffer = hb_buffer_create();
|
|
|
|
CV_Assert(hb_buffer != NULL);
|
|
|
|
|
|
|
|
hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
|
|
|
|
hb_buffer_guess_segment_properties(hb_buffer);
|
|
|
|
hb_shape(mHb_font, hb_buffer, NULL, 0);
|
|
|
|
|
|
|
|
unsigned int textLen = 0;
|
|
|
|
hb_glyph_info_t *info =
|
|
|
|
hb_buffer_get_glyph_infos(hb_buffer, &textLen);
|
|
|
|
CV_Assert(info != NULL);
|
|
|
|
#else
|
|
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
|
|
wstring wstr = converter.from_bytes(_text);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
_org.y += _fontHeight;
|
|
|
|
if (_bottomLeftOrigin == true) {
|
|
|
|
_org.y -= _fontHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t _colorUC8n[4] = {
|
|
|
|
static_cast<uint8_t>(_color[0]),
|
|
|
|
static_cast<uint8_t>(_color[1]),
|
|
|
|
static_cast<uint8_t>(_color[2]),
|
|
|
|
static_cast<uint8_t>(_color[3]) };
|
|
|
|
|
|
|
|
void (cv::ft::FreeType2Impl::*putPixel)(Mat&, const int, const int, const uint8_t*, const uint8_t) =
|
|
|
|
(_img.type() == CV_8UC4) ? (&FreeType2Impl::putPixel_8UC4_blend) :
|
|
|
|
(_img.type() == CV_8UC3) ? (&FreeType2Impl::putPixel_8UC3_blend) :
|
|
|
|
(&FreeType2Impl::putPixel_8UC1_blend);
|
|
|
|
|
|
|
|
#if defined(USING_HB)
|
|
|
|
for (unsigned int i = 0; i < textLen; i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
|
|
|
|
#else
|
|
|
|
for (unsigned int i = 0; i < wstr.size(); i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0));
|
|
|
|
#endif
|
|
|
|
CV_Assert(!FT_Render_Glyph(mFace->glyph, FT_RENDER_MODE_NORMAL));
|
|
|
|
FT_Bitmap *bmp = &(mFace->glyph->bitmap);
|
|
|
|
|
|
|
|
Point gPos = _org;
|
|
|
|
gPos.y -= (mFace->glyph->metrics.horiBearingY >> 6);
|
|
|
|
gPos.x += (mFace->glyph->metrics.horiBearingX >> 6);
|
|
|
|
|
|
|
|
for (int row = 0; row < (int)bmp->rows; row++) {
|
|
|
|
if (gPos.y + row < 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (gPos.y + row >= dst.rows) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int col = 0; col < bmp->pitch; col++) {
|
|
|
|
uint8_t cl = bmp->buffer[row * bmp->pitch + col];
|
|
|
|
if (cl == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (gPos.x + col < 0)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (gPos.x + col >= dst.cols)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
(this->*putPixel)(dst, gPos.y + row, gPos.x + col, _colorUC8n, cl);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_org.x += (mFace->glyph->advance.x) >> 6;
|
|
|
|
_org.y += (mFace->glyph->advance.y) >> 6;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(USING_HB)
|
|
|
|
hb_buffer_destroy(hb_buffer);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
Size FreeType2Impl::getTextSize(
|
|
|
|
const String& _text,
|
|
|
|
int _fontHeight,
|
|
|
|
int _thickness,
|
|
|
|
CV_OUT int* _baseLine)
|
|
|
|
{
|
|
|
|
if (_text.empty())
|
|
|
|
{
|
|
|
|
return Size(0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
CV_Assert(_fontHeight >= 0);
|
|
|
|
if (_fontHeight == 0)
|
|
|
|
{
|
|
|
|
return Size(0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
CV_Assert(!FT_Set_Pixel_Sizes(mFace, _fontHeight, _fontHeight));
|
|
|
|
|
|
|
|
FT_Vector currentPos = { 0,0 };
|
|
|
|
#if defined(USING_HB)
|
|
|
|
hb_buffer_t *hb_buffer = hb_buffer_create();
|
|
|
|
CV_Assert(hb_buffer != NULL);
|
|
|
|
|
|
|
|
hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
|
|
|
|
hb_buffer_guess_segment_properties(hb_buffer);
|
|
|
|
hb_shape(mHb_font, hb_buffer, NULL, 0);
|
|
|
|
|
|
|
|
unsigned int textLen = 0;
|
|
|
|
hb_glyph_info_t *info =
|
|
|
|
hb_buffer_get_glyph_infos(hb_buffer, &textLen);
|
|
|
|
CV_Assert(info != NULL);
|
|
|
|
#else
|
|
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
|
|
wstring wstr = converter.from_bytes(_text);
|
|
|
|
#endif
|
|
|
|
// Initilize BoundaryBox ( in OpenCV coordinates )
|
|
|
|
int xMin = INT_MAX, yMin = INT_MAX;
|
|
|
|
int xMax = INT_MIN, yMax = INT_MIN;
|
|
|
|
|
|
|
|
#if defined(USING_HB)
|
|
|
|
for (unsigned int i = 0; i < textLen; i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
|
|
|
|
#else
|
|
|
|
for (unsigned int i = 0; i < wstr.size(); i++) {
|
|
|
|
CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0));
|
|
|
|
#endif
|
|
|
|
FT_GlyphSlot slot = mFace->glyph;
|
|
|
|
FT_Outline outline = slot->outline;
|
|
|
|
FT_BBox bbox;
|
|
|
|
|
|
|
|
// Flip ( in FreeType coordinates )
|
|
|
|
FT_Matrix mtx = { 1 << 16 , 0 , 0 , -(1 << 16) };
|
|
|
|
FT_Outline_Transform(&outline, &mtx);
|
|
|
|
|
|
|
|
// Move to current position ( in FreeType coordinates )
|
|
|
|
FT_Outline_Translate(&outline,
|
|
|
|
currentPos.x,
|
|
|
|
currentPos.y);
|
|
|
|
|
|
|
|
// Get BoundaryBox ( in FreeType coordinatrs )
|
|
|
|
CV_Assert(!FT_Outline_Get_BBox(&outline, &bbox));
|
|
|
|
|
|
|
|
// If codepoint is space(0x20), it has no glyph.
|
|
|
|
// A dummy boundary box is needed when last code is space.
|
|
|
|
if (
|
|
|
|
(bbox.xMin == 0) && (bbox.xMax == 0) &&
|
|
|
|
(bbox.yMin == 0) && (bbox.yMax == 0)
|
|
|
|
) {
|
|
|
|
bbox.xMin = currentPos.x;
|
|
|
|
bbox.xMax = currentPos.x + (mFace->glyph->advance.x);
|
|
|
|
bbox.yMin = yMin;
|
|
|
|
bbox.yMax = yMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update current position ( in FreeType coordinates )
|
|
|
|
currentPos.x += mFace->glyph->advance.x;
|
|
|
|
currentPos.y += mFace->glyph->advance.y;
|
|
|
|
|
|
|
|
// Update BoundaryBox ( in OpenCV coordinates )
|
|
|
|
xMin = cv::min(xMin, ftd(bbox.xMin));
|
|
|
|
xMax = cv::max(xMax, ftd(bbox.xMax));
|
|
|
|
yMin = cv::min(yMin, ftd(bbox.yMin));
|
|
|
|
yMax = cv::max(yMax, ftd(bbox.yMax));
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(USING_HB)
|
|
|
|
hb_buffer_destroy(hb_buffer);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Calcurate width/height/baseline ( in OpenCV coordinates )
|
|
|
|
int width = xMax - xMin;
|
|
|
|
int height = -yMin;
|
|
|
|
|
|
|
|
if (_thickness > 0) {
|
|
|
|
width = cvRound(width + _thickness * 2);
|
|
|
|
height = cvRound(height + _thickness * 1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
width = cvRound(width + 1);
|
|
|
|
height = cvRound(height + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_baseLine) {
|
|
|
|
*_baseLine = yMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Size(width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
int FreeType2Impl::mvFn(const FT_Vector *to, void * user)
|
|
|
|
{
|
|
|
|
if (user == NULL) { return 1; }
|
|
|
|
PathUserData *p = (PathUserData*)user;
|
|
|
|
|
|
|
|
// Draw polylines( in OpenCV coordinates ).
|
|
|
|
if (p->mPts.size() > 0) {
|
|
|
|
Mat dst = p->mImg.getMat();
|
|
|
|
const Point *ptsList[] = { &(p->mPts[0]) };
|
|
|
|
int npt[1]; npt[0] = p->mPts.size();
|
|
|
|
polylines(
|
|
|
|
dst,
|
|
|
|
ptsList,
|
|
|
|
npt,
|
|
|
|
1,
|
|
|
|
false,
|
|
|
|
p->mColor,
|
|
|
|
p->mThickness,
|
|
|
|
p->mLine_type,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
p->mPts.clear();
|
|
|
|
|
|
|
|
if (to == NULL) { return 1; }
|
|
|
|
|
|
|
|
// Store points to draw( in OpenCV coordinates ).
|
|
|
|
p->mPts.push_back(Point(ftd(to->x), ftd(to->y)));
|
|
|
|
p->mOldP = *to;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FreeType2Impl::lnFn(const FT_Vector *to, void * user)
|
|
|
|
{
|
|
|
|
if (to == NULL) { return 1; }
|
|
|
|
if (user == NULL) { return 1; }
|
|
|
|
|
|
|
|
PathUserData *p = (PathUserData *)user;
|
|
|
|
|
|
|
|
// Store points to draw( in OpenCV coordinates ).
|
|
|
|
p->mPts.push_back(Point(ftd(to->x), ftd(to->y)));
|
|
|
|
p->mOldP = *to;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FreeType2Impl::coFn(const FT_Vector *cnt,
|
|
|
|
const FT_Vector *to,
|
|
|
|
void * user)
|
|
|
|
{
|
|
|
|
if (cnt == NULL) { return 1; }
|
|
|
|
if (to == NULL) { return 1; }
|
|
|
|
if (user == NULL) { return 1; }
|
|
|
|
|
|
|
|
PathUserData *p = (PathUserData *)user;
|
|
|
|
|
|
|
|
// Bezier to Line
|
|
|
|
for (int i = 0; i <= p->mCtoL; i++) {
|
|
|
|
// Split Bezier to lines ( in FreeType coordinates ).
|
|
|
|
double u = (double)i * 1.0 / (p->mCtoL);
|
|
|
|
double nu = 1.0 - u;
|
|
|
|
double p0 = nu * nu;
|
|
|
|
double p1 = 2.0 * u * nu;
|
|
|
|
double p2 = u * u;
|
|
|
|
|
|
|
|
double X = (p->mOldP.x) * p0 + cnt->x * p1 + to->x * p2;
|
|
|
|
double Y = (p->mOldP.y) * p0 + cnt->y * p1 + to->y * p2;
|
|
|
|
|
|
|
|
// Store points to draw( in OpenCV coordinates ).
|
|
|
|
p->mPts.push_back(Point(ftd(X), ftd(Y)));
|
|
|
|
}
|
|
|
|
p->mOldP = *to;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FreeType2Impl::cuFn(const FT_Vector *cnt1,
|
|
|
|
const FT_Vector *cnt2,
|
|
|
|
const FT_Vector *to,
|
|
|
|
void * user)
|
|
|
|
{
|
|
|
|
if (cnt1 == NULL) { return 1; }
|
|
|
|
if (cnt2 == NULL) { return 1; }
|
|
|
|
if (to == NULL) { return 1; }
|
|
|
|
if (user == NULL) { return 1; }
|
|
|
|
|
|
|
|
PathUserData *p = (PathUserData *)user;
|
|
|
|
|
|
|
|
// Bezier to Line
|
|
|
|
for (int i = 0; i <= p->mCtoL; i++) {
|
|
|
|
// Split Bezier to lines ( in FreeType coordinates ).
|
|
|
|
double u = (double)i * 1.0 / (p->mCtoL);
|
|
|
|
double nu = 1.0 - u;
|
|
|
|
double p0 = nu * nu * nu;
|
|
|
|
double p1 = 3.0 * u * nu * nu;
|
|
|
|
double p2 = 3.0 * u * u * nu;
|
|
|
|
double p3 = u * u * u;
|
|
|
|
|
|
|
|
double X = (p->mOldP.x) * p0 + (cnt1->x) * p1 +
|
|
|
|
(cnt2->x) * p2 + (to->x) * p3;
|
|
|
|
double Y = (p->mOldP.y) * p0 + (cnt1->y) * p1 +
|
|
|
|
(cnt2->y) * p2 + (to->y) * p3;
|
|
|
|
|
|
|
|
// Store points to draw( in OpenCV coordinates ).
|
|
|
|
p->mPts.push_back(Point(ftd(X), ftd(Y)));
|
|
|
|
}
|
|
|
|
p->mOldP = *to;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
CV_EXPORTS_W Ptr<FreeType2> createFreeType2()
|
|
|
|
{
|
|
|
|
return Ptr<FreeType2Impl>(new FreeType2Impl());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
} // namespace freetype2
|