以freetype2绘制文本
parent
1ac504d34d
commit
9e4e73a734
@ -0,0 +1,793 @@
|
||||
//
|
||||
// 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
|
@ -0,0 +1,136 @@
|
||||
//
|
||||
// Created by Matthew on 2024/1/4.
|
||||
//
|
||||
//====================================================================
|
||||
//====================================================================
|
||||
// CvText.h
|
||||
|
||||
#ifndef _OPENCV_CVText_
|
||||
#define _OPENCV_CVText_
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace ft {
|
||||
|
||||
using cv::String;
|
||||
// using cv::CV_Assert;
|
||||
|
||||
class FreeType2 : public Algorithm
|
||||
{
|
||||
public:
|
||||
/** @brief Load font data.
|
||||
|
||||
The function loadFontData loads font data.
|
||||
|
||||
@param fontFileName FontFile Name
|
||||
@param idx face_index to select a font faces in a single file.
|
||||
*/
|
||||
|
||||
virtual void loadFontData(String fontFileName, int idx) = 0;
|
||||
|
||||
/** @brief Set Split Number from Bezier-curve to line
|
||||
|
||||
The function setSplitNumber set the number of split points from bezier-curve to line.
|
||||
If you want to draw large glyph, large is better.
|
||||
If you want to draw small glyph, small is better.
|
||||
|
||||
@param num number of split points from bezier-curve to line
|
||||
*/
|
||||
|
||||
virtual void setSplitNumber( int num ) = 0;
|
||||
|
||||
/** @brief Draws a text string.
|
||||
|
||||
The function putText renders the specified text string in the image. Symbols that cannot be rendered using the specified font are replaced by "Tofu" or non-drawn.
|
||||
|
||||
@param img Image. (Only 8UC1/8UC3/8UC4 2D mat is supported.)
|
||||
@param text Text string to be drawn.
|
||||
@param org Bottom-left/Top-left corner of the text string in the image.
|
||||
@param fontHeight Drawing font size by pixel unit.
|
||||
@param color Text color.
|
||||
@param thickness Thickness of the lines used to draw a text when negative, the glyph is filled. Otherwise, the glyph is drawn with this thickness.
|
||||
@param line_type Line type. See the line for details.
|
||||
@param bottomLeftOrigin When true, the image data origin is at the bottom-left corner. Otherwise, it is at the top-left corner.
|
||||
*/
|
||||
|
||||
virtual void putText(
|
||||
InputOutputArray img, const String& text, Point org,
|
||||
int fontHeight, Scalar color,
|
||||
int thickness, int line_type, bool bottomLeftOrigin
|
||||
) = 0;
|
||||
|
||||
/** @brief Calculates the width and height of a text string.
|
||||
|
||||
The function getTextSize calculates and returns the approximate size of a box that contains the specified text.
|
||||
That is, the following code renders some text, the tight box surrounding it, and the baseline: :
|
||||
@code
|
||||
String text = "Funny text inside the box";
|
||||
int fontHeight = 60;
|
||||
int thickness = -1;
|
||||
int linestyle = LINE_8;
|
||||
|
||||
Mat img(600, 800, CV_8UC3, Scalar::all(0));
|
||||
|
||||
int baseline=0;
|
||||
|
||||
cv::Ptr<cv::freetype::FreeType2> ft2;
|
||||
ft2 = cv::freetype::createFreeType2();
|
||||
ft2->loadFontData( "./mplus-1p-regular.ttf", 0 );
|
||||
|
||||
Size textSize = ft2->getTextSize(text,
|
||||
fontHeight,
|
||||
thickness,
|
||||
&baseline);
|
||||
|
||||
if(thickness > 0){
|
||||
baseline += thickness;
|
||||
}
|
||||
|
||||
// center the text
|
||||
Point textOrg((img.cols - textSize.width) / 2,
|
||||
(img.rows + textSize.height) / 2);
|
||||
|
||||
// draw the box
|
||||
rectangle(img, textOrg + Point(0, baseline),
|
||||
textOrg + Point(textSize.width, -textSize.height),
|
||||
Scalar(0,255,0),1,8);
|
||||
|
||||
// ... and the baseline first
|
||||
line(img, textOrg + Point(0, thickness),
|
||||
textOrg + Point(textSize.width, thickness),
|
||||
Scalar(0, 0, 255),1,8);
|
||||
|
||||
// then put the text itself
|
||||
ft2->putText(img, text, textOrg, fontHeight,
|
||||
Scalar::all(255), thickness, linestyle, true );
|
||||
@endcode
|
||||
|
||||
@param text Input text string.
|
||||
@param fontHeight Drawing font size by pixel unit.
|
||||
@param thickness Thickness of lines used to render the text. See putText for details.
|
||||
@param[out] baseLine y-coordinate of the baseline relative to the bottom-most text
|
||||
point.
|
||||
@return The size of a box that contains the specified text.
|
||||
|
||||
@see cv::putText
|
||||
*/
|
||||
virtual Size getTextSize(const String& text,
|
||||
int fontHeight, int thickness,
|
||||
CV_OUT int* baseLine) = 0;
|
||||
|
||||
};
|
||||
|
||||
/** @brief Create FreeType2 Instance
|
||||
|
||||
The function createFreeType2 create instance to draw UTF-8 strings.
|
||||
|
||||
*/
|
||||
Ptr<FreeType2> createFreeType2();
|
||||
|
||||
//! @}
|
||||
} } // namespace freetype
|
||||
|
||||
|
||||
#endif // _OPENCV_CVText_
|
||||
|
Loading…
Reference in New Issue