Skip to content

module freetype: adding advance parameter to getTextSize for positioning bounding box #1874

Open
@abar52

Description

@abar52

freetype: adding advance parameter to getTextSize for positioning bounding box

System information (version)

  • OpenCV => recent 4.0 branch
  • Operating System / Platform => Ubuntu 18.10 64bit
  • Compiler => GCC 8.2 + CUDA 10

running example code from freetype documentation

the font come from
{here}

the complete sentence in the example means:

// ▄▄▄ ▄ ▄▄▄▄▄ ▄ ▄ ▄▄▄ ▄ ▄▄▄▄▄▄▄ ▄ ▄ ▄▄▄ ▄ ▄ ▄ ▄▄▄ ▄ ▄ ▄ ▄
// █▄█▄█▄▄▄█ ▄ █ █ █ ▄ █▄▄▄▄▄█ ▄ █ █ ▄▄█ █ █ █ █▄█▄█▄█▄█▄█
// █▄▄ ▄ ▄ ▄▄█▄█ █ █▄▄▄█ █▄█ ▄▄█▄█ █ █▄█▄█▄█ █ █▄▄▄▄▄▄▄▄ ▄
//   In the name of Allah, Most Gracious, Most Merciful
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/freetype.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

int main()
{
    //cv::String text = "\ufc45\ufb50\ufc44\ufb50\ufc43\ufb50\ufc42\ufb50\ufc41";
    //cv::String text = "\ufc44\ufb50\ufc43\ufb50\ufc42\ufb50\ufc41";
    cv::String text = "\ufc43";
    int fontHeight = 150;
    int thickness = 1;
    int linestyle = 8;
    cv::Mat img( 400, 1200, CV_8UC3, cv::Scalar::all( 0 ) );
    int baseline = 0;

    cv::Ptr<cv::freetype::FreeType2> ft2;
    ft2 = cv::freetype::createFreeType2();

    ft2->setSplitNumber( 8 );

    ft2->loadFontData( "QCF2001.ttf", 0 );
    cv::Size textSize = ft2->getTextSize( text,
        fontHeight,
        thickness,
        &baseline );
    if ( thickness > 0 ) {
        baseline += thickness;
    }

    // show baseline and advance
    std::cout << "baseline " << baseline << std::endl;

    // center the text
    cv::Point textOrg( ( img.cols - textSize.width ) / 2,
        ( img.rows + textSize.height ) / 2 );

    // draw the box
    std::cout << "rectanle " << cv::Point( 0, baseline )
              << ", " << cv::Point( textSize.width, -textSize.height ) << std::endl;
    cv::rectangle( img, textOrg + cv::Point( 0, baseline ),
        textOrg + cv::Point( textSize.width, -textSize.height ),
        cv::Scalar( 0, 255, 0 ), 1, 8 );

    // ... and the baseline first
    std::cout << "baseline " << cv::Point( 0, thickness )
              << ", " << cv::Point( textSize.width, thickness ) << std::endl;
    cv::line( img, textOrg + cv::Point( 0, thickness ),
        textOrg + cv::Point( textSize.width, thickness ),
        cv::Scalar( 0, 0, 255 ), 1, 8 );

    // then put the text itself
    ft2->putText( img, text, textOrg, fontHeight,
        cv::Scalar::all( 255 ), thickness, linestyle, true );

    cv::imshow( "img_path", img );

    cv::waitKey( 0 );
}

without surprise, the result is

qcf-fc43-no-advance

and now, adding advance to getTextSize

#include "freetype.hpp"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

int main()
{
    //cv::String text = "\ufc45\ufb50\ufc44\ufb50\ufc43\ufb50\ufc42\ufb50\ufc41";
    //cv::String text = "\ufc44\ufb50\ufc43\ufb50\ufc42\ufb50\ufc41";
    cv::String text = "\ufc43";
    int fontHeight = 150;
    int thickness = 1;
    int linestyle = 8;
    cv::Mat img( 400, 1200, CV_8UC3, cv::Scalar::all( 0 ) );
    int baseline = 0;
    int advance = 0;

    cv::Ptr<cv::freetype::FreeType2> ft2;
    ft2 = cv::freetype::createFreeType2();

    ft2->setSplitNumber( 8 );

    ft2->loadFontData( "QCF2001.ttf", 0 );
    cv::Size textSize = ft2->getTextSize( text,
        fontHeight,
        thickness,
        &baseline,
        &advance );
    if ( thickness > 0 ) {
        baseline += thickness;
    }

    // show baseline and advance
    std::cout << "baseline " << baseline << ", advance "
              << advance << ", Size " << textSize << std::endl;

    // center the text
    cv::Point textOrg( ( img.cols - advance ) / 2, // ( img.cols - textSize.width ) / 2,
        ( img.rows + textSize.height ) / 2 );

    // draw the box
    std::cout << "rectanle " << cv::Point( advance - textSize.width, baseline )
              << ", " << cv::Point( advance, -textSize.height ) << std::endl;
    cv::rectangle( img, textOrg + cv::Point( advance - textSize.width, baseline ),
        textOrg + cv::Point( advance, -textSize.height ),
        cv::Scalar( 0, 255, 0 ), 1, 8 );

    // ... and the vertical origin
    std::cout << "origin " << cv::Point( 0, baseline )
              << ", " << cv::Point( 0, thickness ) << std::endl;
    cv::line( img, textOrg + cv::Point( 0, baseline ),
        textOrg + cv::Point( 0, -textSize.height ),
        cv::Scalar( 0, 0, 255 ), 1, 8 );

    // ... and the baseline first
    std::cout << "baseline " << cv::Point( advance - textSize.width, thickness )
              << ", " << cv::Point( advance, thickness ) << std::endl;
    cv::line( img, textOrg + cv::Point( advance - textSize.width, thickness ),
        textOrg + cv::Point( advance, thickness ),
        cv::Scalar( 0, 0, 255 ), 1, 8 );

    // then put the text itself
    ft2->putText( img, text, textOrg, fontHeight,
        cv::Scalar::all( 255 ), thickness, linestyle, true );

    cv::imshow( "img_path", img );

    cv::waitKey( 0 );
}

and the result is a matching bounding box

qcf-fc43

the submited proposition is

add this function to freetype.hpp

CV_WRAP virtual Size getTextSize(const String& text,
                    int fontHeight, int thickness,
                    CV_OUT int* baseLine,
                    CV_OUT int* advanceX) = 0;

add this implementation to freetype.cpp

...
Size getTextSize(
    const String& text, int fontHeight, int thickness,
    CV_OUT int* baseLine, CV_OUT int* advanceX ) CV_OVERRIDE;
...
...
Size FreeType2Impl::getTextSize(
    const String& _text,
    int _fontHeight,
    int _thickness,
    CV_OUT int* _baseLine,
    CV_OUT int* _advanceX )
{
    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 );

    if (advanceX)
      *_advanceX = 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;

        if (advanceX)
          *_advanceX += slot->advance.x >> 6;

        // 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 );
}
...

and the whole story become

the-whole-picture

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions