diff --git a/app/src/main/cpp/CvText.cpp b/app/src/main/cpp/CvText.cpp index 06ebe018..e0d2f56e 100644 --- a/app/src/main/cpp/CvText.cpp +++ b/app/src/main/cpp/CvText.cpp @@ -18,6 +18,77 @@ namespace cv { 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: @@ -30,10 +101,7 @@ namespace cv { int fontHeight, Scalar color, int thickness, int line_type, bool bottomLeftOrigin ); - Size getTextSize( - const String& text, int fontHeight, int thickness, - CV_OUT int* baseLine - ); + Size getTextSize(const String& text, int fontHeight, int thickness, CV_OUT int* baseLine); private: FT_Library mLibrary; @@ -61,6 +129,12 @@ namespace cv { 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; @@ -73,13 +147,8 @@ namespace cv { 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); + 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) @@ -160,6 +229,7 @@ namespace cv { } CV_Assert(mHb_font != NULL); #endif + mIsFaceAvailable = true; } @@ -209,8 +279,8 @@ namespace cv { } } else { - putTextOutline(_img, _text, _org, _fontHeight, _color, - _thickness, _line_type, _bottomLeftOrigin); + // putTextOutline(_img, _text, _org, _fontHeight, _color, _thickness, _line_type, _bottomLeftOrigin); + putTextStroker(_img, _text, _org, _fontHeight, _color, _thickness, _line_type, _bottomLeftOrigin); } } @@ -243,7 +313,7 @@ namespace cv { userData->mLine_type = _line_type; // Initilize currentPosition ( in FreeType coordinates) - FT_Vector currentPos = { 0,0 }; + FT_Vector currentPos = { 0, 0 }; currentPos.x = _org.x * 64; currentPos.y = _org.y * 64; @@ -267,9 +337,7 @@ namespace cv { FT_Outline_Transform(&outline, &mtx); // Move to current position ( in FreeType coordinates ) - FT_Outline_Translate(&outline, - currentPos.x, - currentPos.y); + FT_Outline_Translate(&outline, currentPos.x, currentPos.y); // Draw ( in FreeType coordinates ) CV_Assert(!FT_Outline_Decompose(&outline, &mFn, (void*)userData)); @@ -282,6 +350,262 @@ namespace cv { currentPos.y += mFace->glyph->advance.y; } delete userData; +#if defined(USING_HB) + hb_buffer_destroy(hb_buffer); +#endif 0 + } + + 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; + + 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; + +#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++) + { + // if (_bottomLeftOrigin != true) + { + 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; + + // Allocate data for our image and clear it out to transparent. + // Pixel32 *pxl = new Pixel32[imgSize]; + // memset(pxl, 0, sizeof(Pixel32) * imgSize); + + offsetY = ((bbox.yMax - bbox.yMin) - ((glyph_bbox.yMax - glyph_bbox.yMin))) / 2; + // offsetY = ((bbox.yMax) - (glyph_bbox.yMax >> 6)) / 2; + // 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) + { + // int row = (imgHeight - 1 - (s->y - rect.ymin)) - imgHeight + (rect.ymax - rect.ymin); + // int row = (imgHeight - 1 - (s->y - rect.ymin)); + int row = (bbox.yMax - bbox.yMin) - (s->y - rect.ymin) + _org.y - offsetY; + // int row = ((bbox.yMax - bbox.yMin) - (glyph_bbox.yMax - glyph_bbox.yMin)) / 2 - (s->y - rect.ymin) + _org.y; + // mat.at((imgHeight - 1 - (s->y - rect.ymin)), s->x - rect.xmin + w) = outlineColor; + mat.at(row, s->x - rect.xmin + w + _org.x + bearingX) = outlineColor; + // mat.at(-(s->y - rect.ymin), s->x - rect.xmin + w) = outlineColor; + // vec3b. + /* + 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) + { + // int row = (imgHeight - 1 - (s->y - rect.ymin)) - imgHeight + (rect.ymax - rect.ymin); + int row = (bbox.yMax - bbox.yMin) - (s->y - rect.ymin) + _org.y - offsetY; + // int row = ((bbox.yMax - bbox.yMin) - (glyph_bbox.yMax - glyph_bbox.yMin)) / 2 - (s->y - rect.ymin) + _org.y; + mat.at(row, s->x - rect.xmin + w + _org.x + bearingX) = fontColor; + // mat.at(-(s->y - rect.ymin), s->x - rect.xmin + w) = fontColor; +#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; + currentPos.x += mFace->glyph->advance.x >> 6; + currentPos.y += mFace->glyph->advance.y >> 6; + // currentPos.x += mFace->glyph->metrics.horiBearingX >> 6; + // currentPos.y += mFace->glyph->metrics.horiBearingY >> 6; + + // currentPos.x += mFace->glyph->metrics.horiBearingX; + // currentPos.y += mFace->glyph->metrics.horiBearingY; + // break; + _org.x += mFace->glyph->advance.x >> 6; + _org.y += mFace->glyph->advance.y >> 6; + + } + #if defined(USING_HB) hb_buffer_destroy(hb_buffer); #endif 0 @@ -614,6 +938,14 @@ namespace cv { CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 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; + } CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0)); #endif FT_GlyphSlot slot = mFace->glyph; @@ -625,9 +957,7 @@ namespace cv { FT_Outline_Transform(&outline, &mtx); // Move to current position ( in FreeType coordinates ) - FT_Outline_Translate(&outline, - currentPos.x, - currentPos.y); + FT_Outline_Translate(&outline, currentPos.x, currentPos.y); // Get BoundaryBox ( in FreeType coordinatrs ) CV_Assert(!FT_Outline_Get_BBox(&outline, &bbox)); @@ -637,7 +967,8 @@ namespace cv { 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; diff --git a/app/src/main/cpp/CvText.h b/app/src/main/cpp/CvText.h index 979ecc8f..452300e2 100644 --- a/app/src/main/cpp/CvText.h +++ b/app/src/main/cpp/CvText.h @@ -13,8 +13,7 @@ namespace cv { namespace ft { - using cv::String; - // using cv::CV_Assert; + class FreeType2 : public Algorithm { @@ -54,11 +53,8 @@ namespace cv { @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; + 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. @@ -115,9 +111,8 @@ namespace cv { @see cv::putText */ - virtual Size getTextSize(const String& text, - int fontHeight, int thickness, - CV_OUT int* baseLine) = 0; + virtual Size getTextSize(const String& text, int fontHeight, int thickness, CV_OUT int* baseLine) = 0; + };