以freetype2绘制文本

serial
BlueMatthew 1 year ago
parent 1ac504d34d
commit 9e4e73a734

@ -291,6 +291,8 @@ add_library( # Sets the name of the library.
PhoneDevice2.cpp
Camera.cpp
Camera2Reader.cpp
TextPaint.cpp
CvText.cpp
ncnn/yolov5ncnn.cpp

@ -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_

@ -7,12 +7,12 @@
#include <LogThread.h>
#include "ncnn/yolov5ncnn.h"
#include "GPIOControl.h"
#include "CvText.h"
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core/types.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
@ -1055,7 +1055,7 @@ bool CPhoneDevice::OnImageReady(cv::Mat& mat)
#ifdef _DEBUG
cv::Scalar scalar(0, 0, 255); // white
cv::Scalar scalar(0, 0, 255); // red
NdkCamera::CAPTURE_RESULT captureResult = mCamera->getCaptureResult();
char str[128] = { 0 };
@ -1066,7 +1066,22 @@ bool CPhoneDevice::OnImageReady(cv::Mat& mat)
isnan(captureResult.FocusDistance) ? 0 : captureResult.FocusDistance,
(unsigned int)captureResult.afState,
captureResult.hdrMode);
cv::putText(mat, str, cv::Point(0, mat.rows - 20), cv::FONT_HERSHEY_COMPLEX, fontScale, scalar, thickness1, cv::LINE_AA);
// cv::putText(mat, str, cv::Point(0, mat.rows - 20), cv::FONT_HERSHEY_COMPLEX, fontScale, scalar, thickness1, cv::LINE_AA);
std::string fontPath = m_appPath+ "fonts/Noto.ttf";
cv::Ptr<cv::ft::FreeType2> ft2;
ft2 = cv::ft::createFreeType2();
ft2->loadFontData(fontPath.c_str(), 0);
ft2->putText(mat, str, cv::Point(0, mat.rows - 48),
36, scalar, thickness1, cv::LINE_AA, false);
// text.putText(mat, str.c_str(), cv::Point(0, mat.rows - 20), scalar);
// puttext::Pixel32 colorText = {255, 255, 255};
// puttext::Pixel32 colorBorder = {0, 0, 0};
// textPaint.DrawText((puttext::Pixel32*)mat.data, wstr.c_str(), 16, colorText, colorBorder, 3);
#endif

@ -3,3 +3,174 @@
//
#include "TextPaint.h"
namespace puttext
{
void RasterCallback(const int y, const int count, const FT_Span * const spans, void * const user)
{
Spans *sptr = (Spans *)user;
for (int i = 0; i < count; ++i)
sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
}
void RenderSpans(FT_Library &library, FT_Outline * const outline, Spans *spans)
{
FT_Raster_Params params;
memset(&params, 0, sizeof(params));
params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
params.gray_spans = RasterCallback;
params.user = spans;
FT_Outline_Render(library, outline, &params);
}
TextPaint::TextPaint()
{
library = NULL;
face = NULL;
FT_Init_FreeType(&library);
}
TextPaint::~TextPaint()
{
// Clean up the library
FT_Done_FreeType(library);
}
bool TextPaint::LoadFont(const char* fontFilePath)
{
return FT_New_Face(library, fontFilePath, 0, &face) == 0;
}
bool TextPaint::DrawText(Pixel32 *pxl, const wchar_t* str, int size, const Pixel32 &fontCol, const Pixel32 &outlineCol, float outlineWidth)
{
// Set the size to use.
if (FT_Set_Char_Size(face, size << 6, size << 6, 90, 90) != 0) {
return false;
}
const wchar_t* ch = str;
for (; *ch != 0; ch++)
{
// Load the glyph we are looking for.
FT_UInt gindex = FT_Get_Char_Index(face, *ch);
if (FT_Load_Glyph(face, gindex, FT_LOAD_NO_BITMAP) == 0)
{
// Need an outline for this to work.
if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
{
// Render the basic glyph to a span list.
Spans spans;
RenderSpans(library, &face->glyph->outline, &spans);
// Next we need the spans for the outline.
Spans outlineSpans;
// Set up a stroker.
FT_Stroker stroker;
FT_Stroker_New(library, &stroker);
FT_Stroker_Set(stroker,(int)(outlineWidth * 64),FT_STROKER_LINECAP_ROUND,FT_STROKER_LINEJOIN_ROUND,0);
FT_Glyph glyph;
if (FT_Get_Glyph(face->glyph, &glyph) == 0)
{
FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
// Again, this needs to be an outline to work.
if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
{
// Render the outline spans to the span list
FT_Outline *o = &reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
RenderSpans(library, o, &outlineSpans);
}
// Clean up afterwards.
FT_Stroker_Done(stroker);
FT_Done_Glyph(glyph);
// Now we need to put it all together.
if (!spans.empty())
{
// Figure out what the bounding rect is for both the span lists.
Rect rect(spans.front().x, spans.front().y, spans.front().x, spans.front().y);
for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
{
rect.Include(Vec2(s->x, s->y));
rect.Include(Vec2(s->x + s->width - 1, s->y));
}
for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
{
rect.Include(Vec2(s->x, s->y));
rect.Include(Vec2(s->x + s->width - 1, s->y));
}
#if 0
// This is unused in this test but you would need this to draw
// more than one glyph.
float bearingX = face->glyph->metrics.horiBearingX >> 6;
float bearingY = face->glyph->metrics.horiBearingY >> 6;
float advance = face->glyph->advance.x >> 6;
#endif
// Get some metrics of our image.
int imgWidth = rect.Width(),
imgHeight = rect.Height(),
imgSize = imgWidth * imgHeight;
// Allocate data for our image and clear it out to transparent.
// Pixel32 *pxl = new Pixel32[imgSize];
// memset(pxl, 0, sizeof(Pixel32) * imgSize);
// Loop over the outline spans and just draw them into the
// image.
for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
for (int w = 0; w < s->width; ++w)
{
pxl[(int)((imgHeight - 1 - (s->y - rect.ymin)) * imgWidth
+ s->x - rect.xmin + w)] =
Pixel32(outlineCol.r, outlineCol.g, outlineCol.b,
s->coverage);
}
// Then loop over the regular glyph spans and blend them into
// the image.
for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
{
for (int w = 0; w < s->width; ++w) {
Pixel32 &dst =
pxl[(int) (
(imgHeight - 1 - (s->y - rect.ymin)) * imgWidth
+ s->x - rect.xmin + w)];
Pixel32 src = Pixel32(fontCol.r, fontCol.g, fontCol.b,
s->coverage);
dst.r = (int) (dst.r + ((src.r - dst.r) * src.a) / 255.0f);
dst.g = (int) (dst.g + ((src.g - dst.g) * src.a) / 255.0f);
dst.b = (int) (dst.b + ((src.b - dst.b) * src.a) / 255.0f);
dst.a = MIN(255, dst.a + src.a);
}
}
// Dump the image to disk.
// WriteTGA(fileName, pxl, imgWidth, imgHeight);
// delete [] pxl;
}
}
}
}
}
}
#if 0
int TextPaint::DrawText(Pixel32 *pxl, const char * fontFilePath)
{
// Dump out a single glyph to a tga.
WriteGlyphAsTGA(pxl, 100,
Pixel32(255, 90, 30),
Pixel32(255, 255, 255),
3.0f);
// Now that we are done it is safe to delete the memory.
}
}
#endif
} // namespace puttext

@ -5,10 +5,144 @@
#ifndef MICROPHOTO_TEXTPAINT_H
#define MICROPHOTO_TEXTPAINT_H
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H
class TextPaint {
#include <vector>
#include <fstream>
#include <iostream>
};
#ifdef _MSC_VER
#define MIN __min
#define MAX __max
#else
#define MIN std::min
#define MAX std::max
#endif
namespace puttext
{
// Define some fixed size types.
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
// Try to figure out what endian this machine is using. Note that the test
// below might fail for cross compilation; additionally, multi-byte
// characters are implementation-defined in C preprocessors.
#if (('1234' >> 24) == '1')
#elif (('4321' >> 24) == '1')
#define BIG_ENDIAN
#else
#error "Couldn't determine the endianness!"
#endif
union Pixel32
{
Pixel32()
: integer(0) { }
Pixel32(uint8 bi, uint8 gi, uint8 ri, uint8 ai = 255)
{
b = bi;
g = gi;
r = ri;
a = ai;
}
uint32 integer;
struct
{
#ifdef BIG_ENDIAN
uint8 a, r, g, b;
#else // BIG_ENDIAN
uint8 b, g, r, a;
#endif // BIG_ENDIAN
};
};
struct Vec2
{
Vec2() { }
Vec2(float a, float b)
: x(a), y(b) { }
float x, y;
};
struct Rect
{
Rect() { }
Rect(float left, float top, float right, float bottom)
: xmin(left), xmax(right), ymin(top), ymax(bottom) { }
void Include(const Vec2 &r)
{
xmin = MIN(xmin, r.x);
ymin = MIN(ymin, r.y);
xmax = MAX(xmax, r.x);
ymax = MAX(ymax, r.y);
}
float Width() const { return xmax - xmin + 1; }
float Height() const { return ymax - ymin + 1; }
float xmin, xmax, ymin, ymax;
};
// A horizontal pixel span generated by the FreeType renderer.
struct Span
{
Span() { }
Span(int _x, int _y, int _width, int _coverage)
: x(_x), y(_y), width(_width), coverage(_coverage) { }
int x, y, width, coverage;
};
typedef std::vector<Span> Spans;
// Each time the renderer calls us back we just push another span entry on
// our list.
void RasterCallback(const int y, const int count, const FT_Span * const spans, void * const user);
// Set up the raster parameters and render the outline.
void RenderSpans(FT_Library &library, FT_Outline * const outline, Spans *spans);
// Render the specified character as a colored glyph with a colored outline
// and dump it to a TGA.
class TextPaint
{
public:
TextPaint();
~TextPaint();
bool LoadFont(const char* fontFilePath);
bool DrawText(Pixel32 *pxl, const wchar_t* str, int size, const Pixel32 &fontCol, const Pixel32& outlineCol, float outlineWidth);
private:
FT_Library library;
FT_Face face;
};
} // namespace puttext
#endif //MICROPHOTO_TEXTPAINT_H

Loading…
Cancel
Save