// // Created by Matthew on 2024/1/4. // #include #include FT_FREETYPE_H #include FT_OUTLINE_H #include FT_STROKER_H #include FT_IMAGE_H #include FT_BBOX_H #include #include #include "CvText.h" #include 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(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> 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(_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(_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(_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> converter; wstring wstr = converter.from_bytes(_text); #endif _org.y += _fontHeight; if (_bottomLeftOrigin == true) { _org.y -= _fontHeight; } const uint8_t _colorUC8n[4] = { static_cast(_color[0]), static_cast(_color[1]), static_cast(_color[2]), static_cast(_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(_py, _px); int _cb = static_cast(tptr[0]); _cb += ((cb - _cb)*a + 127) >> 8; _cb += ((cb - _cb)*a + 127) >> 8; tptr[0] = static_cast(_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(_py, _px); int _cb = static_cast(tptr[0]); _cb += ((cb - _cb)*a + 127) >> 8; _cb += ((cb - _cb)*a + 127) >> 8; int _cg = static_cast(tptr[1]); _cg += ((cg - _cg)*a + 127) >> 8; _cg += ((cg - _cg)*a + 127) >> 8; int _cr = static_cast(tptr[2]); _cr += ((cr - _cr)*a + 127) >> 8; _cr += ((cr - _cr)*a + 127) >> 8; tptr[0] = static_cast(_cb); tptr[1] = static_cast(_cg); tptr[2] = static_cast(_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(_py, _px); int _cb = static_cast(tptr[0]); _cb += ((cb - _cb)*a + 127) >> 8; _cb += ((cb - _cb)*a + 127) >> 8; int _cg = static_cast(tptr[1]); _cg += ((cg - _cg)*a + 127) >> 8; _cg += ((cg - _cg)*a + 127) >> 8; int _cr = static_cast(tptr[2]); _cr += ((cr - _cr)*a + 127) >> 8; _cr += ((cr - _cr)*a + 127) >> 8; int _ca = static_cast(tptr[3]); _ca += ((ca - _ca)*a + 127) >> 8; _ca += ((ca - _ca)*a + 127) >> 8; tptr[0] = static_cast(_cb); tptr[1] = static_cast(_cg); tptr[2] = static_cast(_cr); tptr[3] = static_cast(_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> converter; wstring wstr = converter.from_bytes(_text); #endif _org.y += _fontHeight; if (_bottomLeftOrigin == true) { _org.y -= _fontHeight; } const uint8_t _colorUC8n[4] = { static_cast(_color[0]), static_cast(_color[1]), static_cast(_color[2]), static_cast(_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> 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 createFreeType2() { return Ptr(new FreeType2Impl()); } } } // namespace freetype2