// // 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; #if (('1234' >> 24) == '1') #elif (('4321' >> 24) == '1') #define BIG_ENDIAN #else #error "Couldn't determine the endianness!" #endif 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 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) { 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)); } // Set up the raster parameters and render the outline. void RenderSpans(FT_Library &library, FT_Outline * const outline, Spans *spans) { FT_Raster_Params params; memset(¶ms, 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, ¶ms); } 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, bool usingStroker ); 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 ); void putTextStroker( 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, bool usingStroker ) { 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 { if (usingStroker) { putTextStroker(_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++) { wchar_t ch = wstr[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 ) #if defined(USING_HB) currentPos.x += mFace->glyph->advance.x; #else if (wstr[i] == ' ') { currentPos.x += mFace->glyph->advance.x << 1; } else { currentPos.x += mFace->glyph->advance.x; } #endif currentPos.y += mFace->glyph->advance.y; } delete userData; #if defined(USING_HB) hb_buffer_destroy(hb_buffer); #endif // 0 } // https://freetype.org/freetype2/docs/tutorial/example2.cpp void FreeType2Impl::putTextStroker( 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 Mat& mat = _img.getMatRef(); /* PathUserData *userData = new PathUserData(_img); userData->mColor = _color; userData->mCtoL = mCtoL; userData->mThickness = _thickness; userData->mLine_type = _line_type; */ int offsetY = 0; int imgHeight = mat.rows; // 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; currentPos.y = imgHeight * 64 - currentPos.y; } // To Freetype coordinates FT_BBox bbox, glyph_bbox; FT_Vector pen{ 0, 0 }; bbox.xMin = bbox.yMin = 32000; bbox.xMax = bbox.yMax = -32000; // Get some metrics of our image. int imgWidth = mat.cols; // _color. cv::Vec3b outlineColor = cv::Vec3b(255 - (uchar)_color[0], 255 - (uchar)_color[1], 255 - (uchar)_color[2]); cv::Vec3b fontColor = cv::Vec3b((uchar)_color[0], (uchar)_color[1], (uchar)_color[2]); #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++) { FT_Set_Transform(mFace, 0, &pen); CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), FT_LOAD_RENDER)); #endif FT_GlyphSlot slot = mFace->glyph; FT_Glyph glyph = NULL; int error = FT_Get_Glyph(mFace->glyph, &glyph); if (error) { printf("FT_Get_Glyph error!\n"); break; } FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox); if (glyph_bbox.xMin < bbox.xMin) bbox.xMin = glyph_bbox.xMin; if (glyph_bbox.yMin < bbox.yMin) bbox.yMin = glyph_bbox.yMin; if (glyph_bbox.xMax > bbox.xMax) bbox.xMax = glyph_bbox.xMax; if (glyph_bbox.yMax > bbox.yMax) bbox.yMax = glyph_bbox.yMax; pen.x += slot->advance.x; pen.y += slot->advance.y; } currentPos.x -= bbox.xMin * 64; currentPos.y -= bbox.yMax * 64; int row, col; #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++) { FT_Set_Transform(mFace, 0, ¤tPos); CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), FT_LOAD_NO_BITMAP)); #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); Spans spans; RenderSpans(mLibrary, &outline, &spans); // Next we need the spans for the outline. Spans outlineSpans; // Set up a stroker. FT_Stroker stroker = NULL; FT_Stroker_New(mLibrary, &stroker); FT_Stroker_Set(stroker, (int)(_thickness * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); FT_Glyph glyph = NULL; if (FT_Get_Glyph(slot, &glyph) == 0) { FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox); 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(glyph)->outline; RenderSpans(mLibrary, 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)); } float bearingX = slot->metrics.horiBearingX >> 6; float bearingY = slot->metrics.horiBearingY >> 6; // float advance = slot->advance.x >> 6; offsetY = bbox.yMax - (glyph_bbox.yMax >> 6); // Loop over the outline spans and just draw them into the // image. for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s) { row = imgHeight - (s->y - (currentPos.y >> 6)) - bbox.yMax; col = s->x - (currentPos.x >> 6); if (row < 0 || row >= imgHeight) { continue; } for (int w = 0; w < s->width; ++w) { col++; if (col < 0 || col >= imgWidth) { continue; } // mat.at(row, col) = outlineColor; putPixel_8UC3_blend(mat, row, col, &(outlineColor[0]), 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) { row = imgHeight - (s->y - (currentPos.y >> 6)) - bbox.yMax; col = s->x - (currentPos.x >> 6); if (row < 0 || row >= imgHeight) { continue; } for (int w = 0; w < s->width; ++w) { col++; if (col < 0 || col >= imgWidth) { continue; } // mat.at(row, col) = fontColor; putPixel_8UC3_blend(mat, row, col, &(fontColor[0]), s->coverage); #if 0 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); #endif } } } } #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; #endif // Update current position ( in FreeType coordinates ) // float advance = mFace->glyph->advance.x >> 6; if (wstr[i] == ' ') { currentPos.x += mFace->glyph->advance.x << 1; } else { currentPos.x += mFace->glyph->advance.x; } currentPos.y += mFace->glyph->advance.y; } #if defined(USING_HB) hb_buffer_destroy(hb_buffer); #endif // 0 } 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_UInt glyph_index; FT_Bool use_kerning; FT_UInt previous; use_kerning = FT_HAS_KERNING(mFace); previous = 0; FT_Vector currentPos = { 0, 0 }; FT_Set_Transform(mFace, 0, ¤tPos); #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++) { glyph_index = info[i].codepoint; CV_Assert(!FT_Load_Glyph(mFace, glyph_index, 0)); #else for (unsigned int i = 0; i < wstr.size(); i++) { if (wstr[i] == '\r' || wstr[i] == '\n') { // 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, currentPos.y + (mFace->glyph->advance.y)); continue; } // FT_Set_Transform(mFace, 0, ¤tPos); glyph_index = FT_Get_Char_Index(mFace, wstr[i]); CV_Assert(!FT_Load_Glyph(mFace, glyph_index, 0)); #endif /* retrieve kerning distance and move pen position */ if (use_kerning && previous) { FT_Vector delta; FT_Get_Kerning(mFace, previous, glyph_index, FT_KERNING_DEFAULT, &delta); currentPos.x += delta.x >> 6; } 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. bool isSpace = false; if ( (bbox.xMin == 0) && (bbox.xMax == 0) && (bbox.yMin == 0) && (bbox.yMax == 0) ) { isSpace = true; bbox.xMin = currentPos.x; bbox.xMax = currentPos.x + ((mFace->glyph->advance.x << 1) + mFace->glyph->metrics.horiBearingX); bbox.yMin = yMin; bbox.yMax = yMax; } // Update current position ( in FreeType coordinates ) currentPos.x += isSpace ? (mFace->glyph->advance.x << 1) : 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)); previous = glyph_index; } #if defined(USING_HB) hb_buffer_destroy(hb_buffer); #endif // Calcurate width/height/baseline ( in OpenCV coordinates ) int width = xMax - xMin; int height = yMax; 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 = -yMin; } return Size(width, height); } 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); } } #if defined(USING_HB) _org.x += (mFace->glyph->advance.x) >> 6; #else if (wstr[i] == ' ') { _org.x += ((mFace->glyph->advance.x) >> 6) << 1; } else { _org.x += (mFace->glyph->advance.x) >> 6; } #endif _org.y += (mFace->glyph->advance.y) >> 6; } #if defined(USING_HB) hb_buffer_destroy(hb_buffer); #endif } 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