Merge pull request #15972 from TolyaTalamanov:at/ftext-primitive

This commit is contained in:
Alexander Alekhin
2019-12-03 20:09:21 +00:00
18 changed files with 648 additions and 39 deletions
+214
View File
@@ -0,0 +1,214 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2019 Intel Corporation
#include "precomp.hpp"
#ifdef HAVE_FREETYPE
#include "api/ft_render.hpp"
#include "api/ft_render_priv.hpp"
#include <opencv2/gapi/util/throw.hpp>
#include <opencv2/gapi/own/assert.hpp>
cv::gapi::wip::draw::FTTextRender::Priv::Priv(const std::string& path)
{
if (FT_Init_FreeType(&m_library) != 0)
{
cv::util::throw_error(std::runtime_error("Failed to initialize FT"));
}
if (FT_New_Face(m_library, path.c_str(), 0, &m_face))
{
FT_Done_FreeType(m_library);
cv::util::throw_error(std::runtime_error("Failed to create a font face"));
}
}
cv::Size cv::gapi::wip::draw::FTTextRender::Priv::getTextSize(const std::wstring& text, int fh, int* baseline)
{
//
//
//
// ^ diff between size and advance(2)
// | ______________ width width |<->|
// | | ** | |<------>| <------------|--->
// | | * * | |________| |____________|___|________
// | left | * * | left |* * * * | | * * * * *| | ^ ^
// |<---->| ** ** ** | <----->|* *| | * | | t | |
// | | * * | | |* *| | * | | o | h |
// | | * * | | |* * * * | | * (1) | p | e | baseline
// O------|*------------*|-----O----- |*-------|-|----O--*----O---|-----*-i-|------------>
// | |______________| | |* | |* | * | | ^ g |
// | | | | |* | |* | * | | b | h |
// | | width | | |* | |* | * | | o | t |
// | |<------------>| | |* | | * *|* | | t | |
// | | |________| |____|_______|___|_____|___*
// | advance | advance | |advance| (advance maybe less than width)
// <---------------------------><----------------|----><------>
// |left| (left maybe is negative)
// |<-->|
//
//
// O - The pen position for any time
//
// left (m_face->glyph->bitmap_left) - The horizontal distance from the current pen position to the glyph's left bbox edge.
//
// advance (m_face->glyph->advance.x >> 6) - The horizontal distance to increment (for left-to-right writing)
// or decrement (for right-to-left writing) the pen position after a
// glyph has been rendered when processing text
//
// widht (bitmap->width) - The width of glyph
//
//
// Algorihm to compute size of the text bounding box:
//
// 1) Go through all symbols and shift pen position and save glyph parameters (left, advance, width)
// If left + pen postion < 0 set left to 0. For example it's maybe happened
// if we print first letter 'J' or any other letter with negative 'left'
// We want to render glyph in pen position + left, so we must't allow it to be negative
//
// 2) If width == 0 we must to skip this symbol and don't save parameters for him.
// For example width == 0 for space sometimes
//
// 3) Also we compute max top and max bottom it's required for compute baseline
//
// 3) At the end we'll get the pen position for the symbol next to the last.
// See (1) on picture.
//
// 4) As we can see the last pen position is isn't horizontal size yet.
// We need to check if the glyph goes beyound the last position of the pen
// To do this we can:
// a) Return to the previous position -advance
// b) Shift on left value +left
// c) Shift on width of the last glyph
//
// Compare result position with pen position and choose max
//
// We can compute diff and check if diff > 0 pen.x += diff.
// See (2) on picture.
//
// 5) Return size. Complete!!!
//
// See also about freetype glyph metrics:
// https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html
GAPI_Assert(!FT_Set_Pixel_Sizes(m_face, fh, fh) &&
"Failed to set pixel size");
cv::Point pen(0, 0);
int max_bot = 0;
int max_top = 0;
int last_advance = 0;
int last_width = 0;
int last_left = 0;
for (const auto& wc : text)
{
GAPI_Assert(!FT_Load_Char(m_face, wc, FT_LOAD_RENDER) &&
"Failed to load char");
FT_Bitmap *bitmap = &(m_face->glyph->bitmap);
int left = m_face->glyph->bitmap_left;
int advance = (m_face->glyph->advance.x >> 6);
int width = bitmap->width;
// NB: Read (1) paragraph of algorithm description
if (pen.x + left < 0)
{
left = 0;
}
int bot = (m_face->glyph->metrics.height - m_face->glyph->metrics.horiBearingY) >> 6;
max_bot = std::max(max_bot, bot);
max_top = std::max(max_top, m_face->glyph->bitmap_top);
// NB: Read (2) paragraph of algorithm description
if (width != 0)
{
last_width = width;
last_advance = advance;
last_left = left;
}
pen.x += advance;
}
// NB: Read (4) paragraph of algorithm description
int diff = (last_width + last_left) - last_advance;
pen.x += (diff > 0) ? diff : 0;
if (baseline)
{
*baseline = max_bot;
}
return {pen.x, max_bot + max_top};
}
void cv::gapi::wip::draw::FTTextRender::Priv::putText(cv::Mat& mat,
const std::wstring& text,
const cv::Point& org,
int fh)
{
GAPI_Assert(!FT_Set_Pixel_Sizes(m_face, fh, fh) &&
"Failed to set pixel size");
cv::Point pen = org;
for (const auto& wc : text)
{
GAPI_Assert(!FT_Load_Char(m_face, wc, FT_LOAD_RENDER) &&
"Failed to load char");
FT_Bitmap *bitmap = &(m_face->glyph->bitmap);
cv::Mat glyph(bitmap->rows, bitmap->width, CV_8UC1, bitmap->buffer, bitmap->pitch);
int left = m_face->glyph->bitmap_left;
int top = m_face->glyph->bitmap_top;
int advance = (m_face->glyph->advance.x >> 6);
if (pen.x + left < 0)
{
left = 0;
}
cv::Rect rect(pen.x + left, org.y - top, glyph.cols, glyph.rows);
auto roi = mat(rect);
roi += glyph;
pen.x += advance;
}
}
cv::gapi::wip::draw::FTTextRender::Priv::~Priv()
{
FT_Done_Face(m_face);
FT_Done_FreeType(m_library);
}
cv::gapi::wip::draw::FTTextRender::FTTextRender(const std::string& path)
: m_priv(new Priv(path))
{
}
cv::Size cv::gapi::wip::draw::FTTextRender::getTextSize(const std::wstring& text,
int fh,
int* baseline)
{
return m_priv->getTextSize(text, fh, baseline);
}
void cv::gapi::wip::draw::FTTextRender::putText(cv::Mat& mat,
const std::wstring& text,
const cv::Point& org,
int fh)
{
m_priv->putText(mat, text, org, fh);
}
#endif // HAVE_FREETYPE
+52
View File
@@ -0,0 +1,52 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2019 Intel Corporation
#ifndef OPENCV_FREETYPE_TEXT_RENDER_HPP
#define OPENCV_FREETYPE_TEXT_RENDER_HPP
#include <memory>
#include <string>
#include <opencv2/core.hpp>
#include <opencv2/gapi/own/exports.hpp>
namespace cv
{
namespace gapi
{
namespace wip
{
namespace draw
{
#ifdef HAVE_FREETYPE
class GAPI_EXPORTS FTTextRender
{
public:
class Priv;
explicit FTTextRender(const std::string& path);
cv::Size getTextSize(const std::wstring& text, int fh, int* baseline);
void putText(cv::Mat& mat, const std::wstring& text, const cv::Point& org, int fh);
private:
std::shared_ptr<Priv> m_priv;
};
#else
class GAPI_EXPORTS FTTextRender {};
#endif // HAVE_FREETYPE
} // namespace draw
} // namespace wip
} // namespace gapi
} // namespace cv
#endif // OPENCV_FREETYPE_TEXT_RENDER_HPP
+48
View File
@@ -0,0 +1,48 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2019 Intel Corporation
#ifdef HAVE_FREETYPE
#ifndef OPENCV_FT_RENDER_PRIV_HPP
#define OPENCV_FT_RENDER_PRIV_HPP
#include "api/ft_render.hpp"
#include <ft2build.h>
#include FT_FREETYPE_H
namespace cv
{
namespace gapi
{
namespace wip
{
namespace draw
{
class FTTextRender::Priv
{
public:
explicit Priv(const std::string& path);
cv::Size getTextSize(const std::wstring& text, int fh, int* baseline);
void putText(cv::Mat& mat, const std::wstring& text, const cv::Point& org, int fh);
~Priv();
private:
FT_Library m_library;
FT_Face m_face;
};
} // namespace draw
} // namespace wip
} // namespace gapi
} // namespace cv
#endif // OPENCV_FT_RENDER_PRIV_HPP
#endif // HAVE_FREETYPE
+10 -6
View File
@@ -1,3 +1,7 @@
#include "precomp.hpp"
#include <stdexcept>
#include <opencv2/imgproc.hpp>
#include <opencv2/gapi/render/render.hpp>
#include <opencv2/gapi/own/assert.hpp>
@@ -9,7 +13,7 @@ void cv::gapi::wip::draw::render(cv::Mat& bgr,
cv::GCompileArgs&& args)
{
cv::GMat in;
cv::GArray<Prim> arr;
cv::GArray<cv::gapi::wip::draw::Prim> arr;
cv::GComputation comp(cv::GIn(in, arr),
cv::GOut(cv::gapi::wip::draw::render3ch(in, arr)));
@@ -22,7 +26,7 @@ void cv::gapi::wip::draw::render(cv::Mat& y_plane,
cv::GCompileArgs&& args)
{
cv::GMat y_in, uv_in, y_out, uv_out;
cv::GArray<Prim> arr;
cv::GArray<cv::gapi::wip::draw::Prim> arr;
std::tie(y_out, uv_out) = cv::gapi::wip::draw::renderNV12(y_in, uv_in, arr);
cv::GComputation comp(cv::GIn(y_in, uv_in, arr), cv::GOut(y_out, uv_out));
@@ -40,7 +44,6 @@ void cv::gapi::wip::draw::cvtYUVToNV12(const cv::Mat& yuv,
std::vector<cv::Mat> chs(3);
cv::split(yuv, chs);
y = chs[0];
cv::merge(std::vector<cv::Mat>{chs[1], chs[2]}, uv);
cv::resize(uv, uv, uv.size() / 2, cv::INTER_LINEAR);
}
@@ -58,14 +61,15 @@ namespace cv
{
namespace detail
{
template<> struct CompileArgTag<cv::gapi::wip::draw::use_freetype>
template<> struct CompileArgTag<cv::gapi::wip::draw::freetype_font>
{
static const char* tag() { return "gapi.use_freetype"; }
static const char* tag() { return "gapi.freetype_font"; }
};
} // namespace detail
GMat cv::gapi::wip::draw::render3ch(const GMat& src, const GArray<Prim>& prims)
GMat cv::gapi::wip::draw::render3ch(const GMat& src,
const GArray<cv::gapi::wip::draw::Prim>& prims)
{
return cv::gapi::wip::draw::GRenderBGR::on(src, prims);
}
+95 -16
View File
@@ -2,6 +2,7 @@
#include <opencv2/gapi/render/render.hpp> // Kernel API's
#include "api/render_ocv.hpp"
#include "api/ft_render.hpp"
namespace cv
{
@@ -27,12 +28,15 @@ inline void mosaic(cv::Mat& mat, const cv::Rect &rect, int cellSz)
}
};
inline void image(cv::Mat& mat,
const cv::Point& org,
const cv::Mat& img,
const cv::Mat& alpha)
inline void blendImage(const cv::Mat& img,
const cv::Mat& alpha,
const cv::Point& org,
cv::Mat background)
{
auto roi = mat(cv::Rect(org, img.size()));
GAPI_Assert(alpha.type() == CV_32FC1);
GAPI_Assert(background.channels() == 3u);
cv::Mat roi = background(cv::Rect(org, img.size()));
cv::Mat img32f_w;
cv::merge(std::vector<cv::Mat>(3, alpha), img32f_w);
@@ -40,8 +44,12 @@ inline void image(cv::Mat& mat,
roi32f_w -= img32f_w;
cv::Mat img32f, roi32f;
if (img.type() == CV_32FC3) {
img.copyTo(img32f);
} else {
img.convertTo(img32f, CV_32F, 1.0/255);
}
img.convertTo(img32f, CV_32F, 1.0/255);
roi.convertTo(roi32f, CV_32F, 1.0/255);
cv::multiply(img32f, img32f_w, img32f);
@@ -49,7 +57,22 @@ inline void image(cv::Mat& mat,
roi32f += img32f;
roi32f.convertTo(roi, CV_8U, 255.0);
};
}
inline void blendTextMask(cv::Mat& img,
cv::Mat& mask,
const cv::Point& tl,
const cv::Scalar& color)
{
mask.convertTo(mask, CV_32FC1, 1 / 255.0);
cv::Mat color_mask;
cv::merge(std::vector<cv::Mat>(3, mask), color_mask);
cv::Scalar color32f = color / 255.0;
cv::multiply(color_mask, color32f, color_mask);
blendImage(color_mask, mask, tl, img);
}
inline void poly(cv::Mat& mat,
const cv::gapi::wip::draw::Poly& pp)
@@ -80,8 +103,16 @@ struct EmptyConverter
// FIXME util::visitor ?
template <typename ColorConverter>
void drawPrimitivesOCV(cv::Mat &in, const Prims &prims)
void drawPrimitivesOCV(cv::Mat& in,
const cv::gapi::wip::draw::Prims& prims,
cv::gapi::wip::draw::FTTextRender* ftpr)
{
#ifndef HAVE_FREETYPE
cv::util::suppress_unused_warning(ftpr);
#endif
using namespace cv::gapi::wip::draw;
ColorConverter converter;
for (const auto &p : prims)
{
@@ -95,11 +126,54 @@ void drawPrimitivesOCV(cv::Mat &in, const Prims &prims)
break;
}
// FIXME avoid code duplicate for Text and FText
case Prim::index_of<Text>():
{
const auto& tp = cv::util::get<Text>(p);
const auto color = converter.cvtColor(tp.color);
cv::putText(in, tp.text, tp.org, tp.ff, tp.fs, color, tp.thick, tp.lt, tp.bottom_left_origin);
auto tp = cv::util::get<Text>(p);
tp.color = converter.cvtColor(tp.color);
int baseline = 0;
auto size = cv::getTextSize(tp.text, tp.ff, tp.fs, tp.thick, &baseline);
baseline += tp.thick;
size.height += baseline;
// Allocate mask outside
cv::Mat mask(size, CV_8UC1, cv::Scalar::all(0));
// Org it's bottom left position for baseline
cv::Point org(0, mask.rows - baseline);
cv::putText(mask, tp.text, org, tp.ff, tp.fs, 255, tp.thick);
// Org is bottom left point, trasform it to top left point for blendImage
cv::Point tl(tp.org.x, tp.org.y - mask.size().height + baseline);
blendTextMask(in, mask, tl, tp.color);
break;
}
case Prim::index_of<FText>():
{
#ifdef HAVE_FREETYPE
const auto& ftp = cv::util::get<FText>(p);
const auto color = converter.cvtColor(ftp.color);
GAPI_Assert(ftpr && "I must pass cv::gapi::wip::draw::freetype_font"
" to the graph compile arguments");
int baseline = 0;
auto size = ftpr->getTextSize(ftp.text, ftp.fh, &baseline);
// Allocate mask outside
cv::Mat mask(size, CV_8UC1, cv::Scalar::all(0));
// Org it's bottom left position for baseline
cv::Point org(0, mask.rows - baseline);
ftpr->putText(mask, ftp.text, org, ftp.fh);
// Org is bottom left point, trasform it to top left point for blendImage
cv::Point tl(ftp.org.x, ftp.org.y - mask.size().height + baseline);
blendTextMask(in, mask, tl, color);
#else
cv::util::throw_error(std::runtime_error("FreeType not found !"));
#endif
break;
}
@@ -134,7 +208,8 @@ void drawPrimitivesOCV(cv::Mat &in, const Prims &prims)
cv::Mat img;
converter.cvtImg(ip.img, img);
image(in, ip.org, img, ip.alpha);
img.convertTo(img, CV_32FC1, 1.0 / 255);
blendImage(img, ip.alpha, ip.org, in);
break;
}
@@ -151,14 +226,18 @@ void drawPrimitivesOCV(cv::Mat &in, const Prims &prims)
}
}
void drawPrimitivesOCVBGR(cv::Mat &in, const Prims &prims)
void drawPrimitivesOCVBGR(cv::Mat &in,
const cv::gapi::wip::draw::Prims &prims,
cv::gapi::wip::draw::FTTextRender* ftpr)
{
drawPrimitivesOCV<EmptyConverter>(in, prims);
drawPrimitivesOCV<EmptyConverter>(in, prims, ftpr);
}
void drawPrimitivesOCVYUV(cv::Mat &in, const Prims &prims)
void drawPrimitivesOCVYUV(cv::Mat &in,
const cv::gapi::wip::draw::Prims &prims,
cv::gapi::wip::draw::FTTextRender* ftpr)
{
drawPrimitivesOCV<BGR2YUVConverter>(in, prims);
drawPrimitivesOCV<BGR2YUVConverter>(in, prims, ftpr);
}
} // namespace draw
+3 -2
View File
@@ -1,5 +1,6 @@
#include <vector>
#include "render_priv.hpp"
#include "ft_render.hpp"
#ifndef OPENCV_RENDER_OCV_HPP
#define OPENCV_RENDER_OCV_HPP
@@ -14,8 +15,8 @@ namespace draw
{
// FIXME only for tests
void GAPI_EXPORTS drawPrimitivesOCVYUV(cv::Mat &yuv, const Prims &prims);
void GAPI_EXPORTS drawPrimitivesOCVBGR(cv::Mat &bgr, const Prims &prims);
void GAPI_EXPORTS drawPrimitivesOCVYUV(cv::Mat& yuv, const Prims& prims, cv::gapi::wip::draw::FTTextRender* mc);
void GAPI_EXPORTS drawPrimitivesOCVBGR(cv::Mat& bgr, const Prims& prims, cv::gapi::wip::draw::FTTextRender* mc);
} // namespace draw
} // namespace wip