Commit 04ad07c0 authored by Kumataro's avatar Kumataro Committed by Alexander Alekhin

Merge pull request #936 from Kumataro:freetype2.927

* Support getTextSize() to resolve issue #927

This getTextSize() funtion helps to print more text,
either on the same line or on another line.
To add getTextSize() function, some member variables are removed
which are corresponding at putText() function's arguments.
And a negative fontHeight must be asserted.

* Fix to remove whitespace
parent 5127e7f2
...@@ -115,7 +115,67 @@ The function putText renders the specified text string in the image. Symbols tha ...@@ -115,7 +115,67 @@ The function putText renders the specified text string in the image. Symbols tha
int thickness, int line_type, bool bottomLeftOrigin int thickness, int line_type, bool bottomLeftOrigin
) = 0; ) = 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 = 8;
Mat img(600, 800, CV_8UC3, Scalar::all(0));
int baseline=0;
cv::Ptr<cv::freetype::FreeType2> ft2;
ft2 = cv::freetype::createFreeType2();
ft2->loadFontData( "./mplus-1p-regular.ttf", 0 );
Size textSize = ft2->getTextSize(text,
fontHeight,
thickness,
&baseline);
if(thickness > 0){
baseline += thickness;
}
// center the text
Point textOrg((img.cols - textSize.width) / 2,
(img.rows + textSize.height) / 2);
// draw the box
rectangle(img, textOrg + Point(0, baseline),
textOrg + Point(textSize.width, -textSize.height),
Scalar(0,255,0),1,8);
// ... and the baseline first
line(img, textOrg + Point(0, thickness),
textOrg + Point(textSize.width, thickness),
Scalar(0, 0, 255),1,8);
// then put the text itself
ft2->putText(img, text, textOrg, fontHeight,
Scalar::all(255), thickness, linestyle, true );
@endcode
@param text Input text string.
@param fontHeight Drawing font size by pixel unit.
@param thickness Thickness of lines used to render the text. See putText for details.
@param[out] baseLine y-coordinate of the baseline relative to the bottom-most text
point.
@return The size of a box that contains the specified text.
@see cv::putText
*/
CV_WRAP virtual Size getTextSize(const String& text,
int fontHeight, int thickness,
CV_OUT int* baseLine) = 0;
}; };
/** @brief Create FreeType2 Instance /** @brief Create FreeType2 Instance
The function createFreeType2 create instance to draw UTF-8 strings. The function createFreeType2 create instance to draw UTF-8 strings.
......
...@@ -50,6 +50,8 @@ ...@@ -50,6 +50,8 @@
#include <ft2build.h> #include <ft2build.h>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include FT_OUTLINE_H #include FT_OUTLINE_H
#include FT_IMAGE_H
#include FT_BBOX_H
#include <hb.h> #include <hb.h>
#include <hb-ft.h> #include <hb-ft.h>
...@@ -71,25 +73,36 @@ public: ...@@ -71,25 +73,36 @@ public:
int fontHeight, Scalar color, int fontHeight, Scalar color,
int thickness, int line_type, bool bottomLeftOrigin int thickness, int line_type, bool bottomLeftOrigin
); );
Size getTextSize(
const String& text, int fontHeight, int thickness,
CV_OUT int* baseLine
);
private: private:
FT_Library mLibrary; FT_Library mLibrary;
FT_Face mFace; FT_Face mFace;
FT_Outline_Funcs mFn; FT_Outline_Funcs mFn;
Point mOrg;
int mLine_type;
int mThickness;
int mHeight;
Scalar mColor;
bool mIsFaceAvailable; bool mIsFaceAvailable;
String mText;
int mCtoL; int mCtoL;
hb_font_t *mHb_font; hb_font_t *mHb_font;
void putTextBitmapMono ( InputOutputArray _img); void putTextBitmapMono(
void putTextBitmapBlend( InputOutputArray _img); InputOutputArray img, const String& text, Point org,
void putTextOutline ( InputOutputArray _img); 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
);
static int mvFn( const FT_Vector *to, void * user); static int mvFn( const FT_Vector *to, void * user);
static int lnFn( const FT_Vector *to, void * user); static int lnFn( const FT_Vector *to, void * user);
...@@ -172,7 +185,7 @@ void FreeType2Impl::setSplitNumber(int num ){ ...@@ -172,7 +185,7 @@ void FreeType2Impl::setSplitNumber(int num ){
void FreeType2Impl::putText( void FreeType2Impl::putText(
InputOutputArray _img, const String& _text, Point _org, InputOutputArray _img, const String& _text, Point _org,
int _fontHeight, Scalar _color, int _fontHeight, Scalar _color,
int _thickness, int _line_type, bool bottomLeftOrigin int _thickness, int _line_type, bool _bottomLeftOrigin
) )
{ {
CV_Assert( mIsFaceAvailable == true ); CV_Assert( mIsFaceAvailable == true );
...@@ -184,11 +197,16 @@ void FreeType2Impl::putText( ...@@ -184,11 +197,16 @@ void FreeType2Impl::putText(
CV_Assert( ( _line_type == CV_AA) || CV_Assert( ( _line_type == CV_AA) ||
( _line_type == 4 ) || ( _line_type == 4 ) ||
( _line_type == 8 ) ); ( _line_type == 8 ) );
CV_Assert( _fontHeight >= 0 );
if ( _text.empty() ) if ( _text.empty() )
{ {
return; return;
} }
if ( _fontHeight == 0 )
{
return;
}
if( _line_type == CV_AA && _img.depth() != CV_8U ){ if( _line_type == CV_AA && _img.depth() != CV_8U ){
_line_type = 8; _line_type = 8;
...@@ -196,48 +214,48 @@ void FreeType2Impl::putText( ...@@ -196,48 +214,48 @@ void FreeType2Impl::putText(
CV_Assert(!FT_Set_Pixel_Sizes( mFace, _fontHeight, _fontHeight )); CV_Assert(!FT_Set_Pixel_Sizes( mFace, _fontHeight, _fontHeight ));
mThickness = _thickness; if( _thickness < 0 ) // CV_FILLED
mLine_type = _line_type;
mColor = _color;
mHeight = _fontHeight;
mText = _text;
mOrg = _org;
if( !bottomLeftOrigin ) {
mOrg.y += mHeight;
}
if( mThickness < 0 ) // CV_FILLED
{ {
if ( mLine_type == CV_AA ) { if ( _line_type == CV_AA ) {
putTextBitmapBlend(_img); putTextBitmapBlend( _img, _text, _org, _fontHeight, _color,
_thickness, _line_type, _bottomLeftOrigin );
}else{ }else{
putTextBitmapMono (_img); putTextBitmapMono( _img, _text, _org, _fontHeight, _color,
_thickness, _line_type, _bottomLeftOrigin );
} }
}else{ }else{
putTextOutline(_img); putTextOutline( _img, _text, _org, _fontHeight, _color,
_thickness, _line_type, _bottomLeftOrigin );
} }
} }
void FreeType2Impl::putTextOutline(InputOutputArray _img) void FreeType2Impl::putTextOutline(
InputOutputArray _img, const String& _text, Point _org,
int _fontHeight, Scalar _color,
int _thickness, int _line_type, bool _bottomLeftOrigin )
{ {
hb_buffer_t *hb_buffer = hb_buffer_create (); hb_buffer_t *hb_buffer = hb_buffer_create ();
CV_Assert( hb_buffer != NULL ); CV_Assert( hb_buffer != NULL );
unsigned int textLen; unsigned int textLen;
hb_buffer_guess_segment_properties (hb_buffer); hb_buffer_guess_segment_properties (hb_buffer);
hb_buffer_add_utf8 (hb_buffer, mText.c_str(), -1, 0, -1); hb_buffer_add_utf8 (hb_buffer, _text.c_str(), -1, 0, -1);
hb_glyph_info_t *info = hb_glyph_info_t *info =
hb_buffer_get_glyph_infos(hb_buffer,&textLen ); hb_buffer_get_glyph_infos(hb_buffer,&textLen );
CV_Assert( info != NULL ); CV_Assert( info != NULL );
hb_shape (mHb_font, hb_buffer, NULL, 0); hb_shape (mHb_font, hb_buffer, NULL, 0);
mOrg.y -= mHeight; if( _bottomLeftOrigin == true ){
_org.y -= _fontHeight;
}
PathUserData *userData = new PathUserData( _img ); PathUserData *userData = new PathUserData( _img );
userData->mColor = mColor; userData->mColor = _color;
userData->mCtoL = mCtoL; userData->mCtoL = mCtoL;
userData->mThickness = mThickness; userData->mThickness = _thickness;
userData->mLine_type = mLine_type; userData->mLine_type = _line_type;
for( unsigned int i = 0 ; i < textLen ; i ++ ){ for( unsigned int i = 0 ; i < textLen ; i ++ ){
CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0 )); CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0 ));
...@@ -255,8 +273,8 @@ void FreeType2Impl::putTextOutline(InputOutputArray _img) ...@@ -255,8 +273,8 @@ void FreeType2Impl::putTextOutline(InputOutputArray _img)
cOutlineOffset ); cOutlineOffset );
// Move // Move
FT_Outline_Translate(&outline, FT_Outline_Translate(&outline,
(FT_Pos)(mOrg.x << 6), (FT_Pos)(_org.x << 6),
(FT_Pos)((mOrg.y + mHeight) << 6) ); (FT_Pos)( (_org.y + _fontHeight) << 6) );
// Draw // Draw
CV_Assert( !FT_Outline_Decompose(&outline, &mFn, (void*)userData) ); CV_Assert( !FT_Outline_Decompose(&outline, &mFn, (void*)userData) );
...@@ -264,33 +282,45 @@ void FreeType2Impl::putTextOutline(InputOutputArray _img) ...@@ -264,33 +282,45 @@ void FreeType2Impl::putTextOutline(InputOutputArray _img)
// Draw (Last Path) // Draw (Last Path)
mvFn( NULL, (void*)userData ); mvFn( NULL, (void*)userData );
mOrg.x += ( mFace->glyph->advance.x ) >> 6; _org.x += ( mFace->glyph->advance.x ) >> 6;
mOrg.y += ( mFace->glyph->advance.y ) >> 6; _org.y += ( mFace->glyph->advance.y ) >> 6;
} }
delete userData; delete userData;
hb_buffer_destroy (hb_buffer); hb_buffer_destroy (hb_buffer);
} }
void FreeType2Impl::putTextBitmapMono(InputOutputArray _img) 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 == 4 || _line_type == 8);
Mat dst = _img.getMat(); Mat dst = _img.getMat();
hb_buffer_t *hb_buffer = hb_buffer_create (); hb_buffer_t *hb_buffer = hb_buffer_create ();
CV_Assert( hb_buffer != NULL ); CV_Assert( hb_buffer != NULL );
unsigned int textLen; unsigned int textLen;
hb_buffer_guess_segment_properties (hb_buffer); hb_buffer_guess_segment_properties (hb_buffer);
hb_buffer_add_utf8 (hb_buffer, mText.c_str(), -1, 0, -1); hb_buffer_add_utf8 (hb_buffer, _text.c_str(), -1, 0, -1);
hb_glyph_info_t *info = hb_glyph_info_t *info =
hb_buffer_get_glyph_infos(hb_buffer,&textLen ); hb_buffer_get_glyph_infos(hb_buffer,&textLen );
CV_Assert( info != NULL ); CV_Assert( info != NULL );
hb_shape (mHb_font, hb_buffer, NULL, 0); hb_shape (mHb_font, hb_buffer, NULL, 0);
_org.y += _fontHeight;
if( _bottomLeftOrigin == true ){
_org.y -= _fontHeight;
}
for( unsigned int i = 0 ; i < textLen ; i ++ ){ for( unsigned int i = 0 ; i < textLen ; i ++ ){
CV_Assert( !FT_Load_Glyph(mFace, info[i].codepoint, 0 ) ); CV_Assert( !FT_Load_Glyph(mFace, info[i].codepoint, 0 ) );
CV_Assert( !FT_Render_Glyph( mFace->glyph, FT_RENDER_MODE_MONO ) ); CV_Assert( !FT_Render_Glyph( mFace->glyph, FT_RENDER_MODE_MONO ) );
FT_Bitmap *bmp = &(mFace->glyph->bitmap); FT_Bitmap *bmp = &(mFace->glyph->bitmap);
Point gPos = mOrg; Point gPos = _org;
gPos.y -= ( mFace->glyph->metrics.horiBearingY >> 6) ; gPos.y -= ( mFace->glyph->metrics.horiBearingY >> 6) ;
gPos.x += ( mFace->glyph->metrics.horiBearingX >> 6) ; gPos.x += ( mFace->glyph->metrics.horiBearingX >> 6) ;
...@@ -319,41 +349,52 @@ void FreeType2Impl::putTextBitmapMono(InputOutputArray _img) ...@@ -319,41 +349,52 @@ void FreeType2Impl::putTextBitmapMono(InputOutputArray _img)
if ( ( (cl >> bit) & 0x01 ) == 1 ) { if ( ( (cl >> bit) & 0x01 ) == 1 ) {
cv::Vec3b* ptr = dst.ptr<cv::Vec3b>( gPos.y + row, gPos.x + col * 8 + (7 - bit) ); cv::Vec3b* ptr = dst.ptr<cv::Vec3b>( gPos.y + row, gPos.x + col * 8 + (7 - bit) );
(*ptr)[0] = mColor[0]; (*ptr)[0] = _color[0];
(*ptr)[1] = mColor[1]; (*ptr)[1] = _color[1];
(*ptr)[2] = mColor[2]; (*ptr)[2] = _color[2];
} }
} }
} }
} }
mOrg.x += ( mFace->glyph->advance.x ) >> 6; _org.x += ( mFace->glyph->advance.x ) >> 6;
mOrg.y += ( mFace->glyph->advance.y ) >> 6; _org.y += ( mFace->glyph->advance.y ) >> 6;
} }
hb_buffer_destroy (hb_buffer); hb_buffer_destroy (hb_buffer);
} }
void FreeType2Impl::putTextBitmapBlend(InputOutputArray _img) 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 == 16 );
Mat dst = _img.getMat(); Mat dst = _img.getMat();
hb_buffer_t *hb_buffer = hb_buffer_create (); hb_buffer_t *hb_buffer = hb_buffer_create ();
CV_Assert( hb_buffer != NULL ); CV_Assert( hb_buffer != NULL );
unsigned int textLen; unsigned int textLen;
hb_buffer_guess_segment_properties (hb_buffer); hb_buffer_guess_segment_properties (hb_buffer);
hb_buffer_add_utf8 (hb_buffer, mText.c_str(), -1, 0, -1); hb_buffer_add_utf8 (hb_buffer, _text.c_str(), -1, 0, -1);
hb_glyph_info_t *info = hb_glyph_info_t *info =
hb_buffer_get_glyph_infos(hb_buffer,&textLen ); hb_buffer_get_glyph_infos(hb_buffer,&textLen );
CV_Assert( info != NULL ); CV_Assert( info != NULL );
hb_shape (mHb_font, hb_buffer, NULL, 0); hb_shape (mHb_font, hb_buffer, NULL, 0);
_org.y += _fontHeight;
if( _bottomLeftOrigin == true ){
_org.y -= _fontHeight;
}
for( unsigned int i = 0 ; i < textLen ; i ++ ){ for( unsigned int i = 0 ; i < textLen ; i ++ ){
CV_Assert( !FT_Load_Glyph(mFace, info[i].codepoint, 0 ) ); CV_Assert( !FT_Load_Glyph(mFace, info[i].codepoint, 0 ) );
CV_Assert( !FT_Render_Glyph( mFace->glyph, FT_RENDER_MODE_NORMAL ) ); CV_Assert( !FT_Render_Glyph( mFace->glyph, FT_RENDER_MODE_NORMAL ) );
FT_Bitmap *bmp = &(mFace->glyph->bitmap); FT_Bitmap *bmp = &(mFace->glyph->bitmap);
Point gPos = mOrg; Point gPos = _org;
gPos.y -= ( mFace->glyph->metrics.horiBearingY >> 6) ; gPos.y -= ( mFace->glyph->metrics.horiBearingY >> 6) ;
gPos.x += ( mFace->glyph->metrics.horiBearingX >> 6) ; gPos.x += ( mFace->glyph->metrics.horiBearingX >> 6) ;
...@@ -382,17 +423,121 @@ void FreeType2Impl::putTextBitmapBlend(InputOutputArray _img) ...@@ -382,17 +423,121 @@ void FreeType2Impl::putTextBitmapBlend(InputOutputArray _img)
cv::Vec3b* ptr = dst.ptr<cv::Vec3b>( gPos.y + row , gPos.x + col); cv::Vec3b* ptr = dst.ptr<cv::Vec3b>( gPos.y + row , gPos.x + col);
double blendAlpha = (double ) cl / 255.0; double blendAlpha = (double ) cl / 255.0;
(*ptr)[0] = (double) mColor[0] * blendAlpha + (*ptr)[0] * (1.0 - blendAlpha ); (*ptr)[0] = (double) _color[0] * blendAlpha + (*ptr)[0] * (1.0 - blendAlpha );
(*ptr)[1] = (double) mColor[1] * blendAlpha + (*ptr)[1] * (1.0 - blendAlpha ); (*ptr)[1] = (double) _color[1] * blendAlpha + (*ptr)[1] * (1.0 - blendAlpha );
(*ptr)[2] = (double) mColor[2] * blendAlpha + (*ptr)[2] * (1.0 - blendAlpha ); (*ptr)[2] = (double) _color[2] * blendAlpha + (*ptr)[2] * (1.0 - blendAlpha );
} }
} }
mOrg.x += ( mFace->glyph->advance.x ) >> 6; _org.x += ( mFace->glyph->advance.x ) >> 6;
mOrg.y += ( mFace->glyph->advance.y ) >> 6; _org.y += ( mFace->glyph->advance.y ) >> 6;
} }
hb_buffer_destroy (hb_buffer); hb_buffer_destroy (hb_buffer);
} }
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 ));
hb_buffer_t *hb_buffer = hb_buffer_create ();
CV_Assert( hb_buffer != NULL );
Point _org(0,0);
unsigned int textLen;
hb_buffer_guess_segment_properties (hb_buffer);
hb_buffer_add_utf8 (hb_buffer, _text.c_str(), -1, 0, -1);
hb_glyph_info_t *info =
hb_buffer_get_glyph_infos(hb_buffer,&textLen );
CV_Assert( info != NULL );
hb_shape (mHb_font, hb_buffer, NULL, 0);
_org.y -= _fontHeight;
int xMin = INT_MAX, xMax = INT_MIN;
int yMin = INT_MAX, yMax = INT_MIN;
for( unsigned int i = 0 ; i < textLen ; i ++ ){
CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0 ));
FT_GlyphSlot slot = mFace->glyph;
FT_Outline outline = slot->outline;
FT_BBox bbox ;
// Flip
FT_Matrix mtx = { 1 << 16 , 0 , 0 , -(1 << 16) };
FT_Outline_Transform(&outline, &mtx);
// Move
FT_Outline_Translate(&outline,
cOutlineOffset,
cOutlineOffset );
// Move
FT_Outline_Translate(&outline,
(FT_Pos)(_org.x << 6 ),
(FT_Pos)((_org.y + _fontHeight) << 6 ) );
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 = (_org.x << 6);
bbox.xMax = (_org.x << 6 ) + ( mFace->glyph->advance.x );
bbox.yMin = yMin;
bbox.yMax = yMax;
bbox.xMin += cOutlineOffset;
bbox.xMax += cOutlineOffset;
bbox.yMin += cOutlineOffset;
bbox.yMax += cOutlineOffset;
}
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) );
_org.x += ( mFace->glyph->advance.x ) >> 6;
_org.y += ( mFace->glyph->advance.y ) >> 6;
}
hb_buffer_destroy (hb_buffer);
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) int FreeType2Impl::mvFn( const FT_Vector *to, void * user)
{ {
if(user == NULL ) { return 1; } if(user == NULL ) { return 1; }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment