diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 8d610752..cb1ed8f5 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -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 diff --git a/app/src/main/cpp/CvText.cpp b/app/src/main/cpp/CvText.cpp new file mode 100644 index 00000000..ef63ec56 --- /dev/null +++ b/app/src/main/cpp/CvText.cpp @@ -0,0 +1,793 @@ +// +// 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 diff --git a/app/src/main/cpp/CvText.h b/app/src/main/cpp/CvText.h new file mode 100644 index 00000000..66b205dd --- /dev/null +++ b/app/src/main/cpp/CvText.h @@ -0,0 +1,136 @@ +// +// Created by Matthew on 2024/1/4. +// +//==================================================================== +//==================================================================== +// CvText.h + +#ifndef _OPENCV_CVText_ +#define _OPENCV_CVText_ + +#include + +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 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 createFreeType2(); + +//! @} +} } // namespace freetype + + +#endif // _OPENCV_CVText_ + diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index f7855cc3..3eaf8efd 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -7,12 +7,12 @@ #include #include "ncnn/yolov5ncnn.h" #include "GPIOControl.h" +#include "CvText.h" #include #include #include - #include #include #include @@ -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 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 diff --git a/app/src/main/cpp/TextPaint.cpp b/app/src/main/cpp/TextPaint.cpp index c3541dd9..e0df8038 100644 --- a/app/src/main/cpp/TextPaint.cpp +++ b/app/src/main/cpp/TextPaint.cpp @@ -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(¶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); + } + + 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(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 \ No newline at end of file diff --git a/app/src/main/cpp/TextPaint.h b/app/src/main/cpp/TextPaint.h index ba921442..5a2469c4 100644 --- a/app/src/main/cpp/TextPaint.h +++ b/app/src/main/cpp/TextPaint.h @@ -5,10 +5,144 @@ #ifndef MICROPHOTO_TEXTPAINT_H #define MICROPHOTO_TEXTPAINT_H +#include +#include FT_FREETYPE_H +#include FT_STROKER_H -class TextPaint { +#include +#include +#include -}; +#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 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