Skip to content

Instantly share code, notes, and snippets.

@nomissbowling
Last active May 14, 2022 07:08
Show Gist options
  • Save nomissbowling/5367fc4c6bccf33325a6017436fb2f4d to your computer and use it in GitHub Desktop.
Save nomissbowling/5367fc4c6bccf33325a6017436fb2f4d to your computer and use it in GitHub Desktop.
cvPutTextFontFT.cpp
/*
cvPutTextFontFT.cpp
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64\cl.exe"
-source-charset:utf-8 -execution-charset:utf-8
-EHsc -FecvPutTextFontFT.exe cvPutTextFontFT.cpp
-IC:\OpenCV3\include
-I..\FreeType\include
-link
/LIBPATH:C:\OpenCV3\x64\vc15\lib
/LIBPATH:..\FreeType\x64
/LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\lib\x64"
/LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.17763.0\ucrt\x64"
/LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.17763.0\um\x64"
opencv_world3412.lib
freetype.lib
del .\cvPutTextFontFT.obj
cvPutTextFontFT
https://freetype.org/
Unicode Variation Sequences
https://freetype.org/freetype2/docs/reference/ft2-glyph_variants.html
VS
https://hanya-orz.hatenablog.com/entry/20151228/p1
FT_ULong uc = static_cast<FT_ULong>(L'𠮟');
FT_UInt32
FT_Face_GetVariantSelectors
FT_Face_GetVariantsOfChar
FT_Face_GetCharsOfVariant
FT_Face_GetCharVariantIndex
https://freetype.org/freetype2/docs/reference/ft2-glyph_variants.html#ft_face_getcharvariantindex
surrogate pair
https://ja.wikipedia.org/wiki/Unicode
IPAex IPA font https://moji.or.jp/ipafont/
*/
#define UNICODE
#define _UNICODE
#include <wchar.h>
// use CV_PI instead of M_PI
// #define _USE_MATH_DEFINES
#include <opencv2/opencv.hpp>
// #include <opencv2/imgproc.hpp> // cv::FONT *, cv::LINE *, cv::FILLED
#include <iomanip>
#include <iostream>
#include <sstream>
#include <map>
#include <vector>
#include <string>
#include <stdexcept>
#include <exception>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <freetype/freetype.h>
#define BASEDIR "E:\\virtual2\\SlowShutter\\"
#define V_W 640
#define V_H 480
#define OUTDIR BASEDIR
#define IM_W 640
#define IM_H 480
#define BASECOUNT 40000
#define BREAKCOUNT 600 // when NOWAIT (600/24sec 1536KB) (save 10fps:60frms)
// #define NOWAIT // NOWAIT: <30fps, no NOWAIT: <17fps (depends on CPU)
#define FILE_IN BASEDIR"contours_iroha.png"
#if 1
#define FONT_BASE "C:\\Windows\\Fonts\\"
// #define FONT_FN FONT_BASE"ipaexm.ttf" // OK u9dd7 u20b9f u82a6 (u8879)
// #define FONT_FN FONT_BASE"ipaexg.ttf" // OK u9dd7 u20b9f u82a6 (u8879)
#define FONT_FN FONT_BASE"migu-1m-regular.ttf" // OK u9dd7 u20b9f (u82a6 u8879)
// #define FONT_FN FONT_BASE"migmix-1m-regular.ttf" // BAD IVS (u82a6 u8879)
// #define FONT_FN FONT_BASE"msgothic.ttc" // OK u9dd7 u20b9f u82a6 u8879
// #define FONT_FN FONT_BASE"DFLgs9.ttc" // OK but not support some glyph
// #define FONT_FN FONT_BASE"DFLgs9.ttf" // font exists but can not get glyph
// #define FONT_FN FONT_BASE"consola.ttf" // not support wchar_t area
#else
#define FONT_BASE ".\\"
#define FONT_FN FONT_BASE"mikaP.ttf"
#endif
using namespace std;
uint8_t COLS[][3] = { // RGB
{240, 192, 32}, {32, 240, 192}, {192, 32, 240}, {255, 255, 255},
{240, 32, 192}, {192, 240, 32}, {32, 192, 240}, {0, 0, 0}};
vector<cv::Scalar> createColors()
{
vector<cv::Scalar> colors(_countof(COLS));
int i = 0;
for(auto it = colors.begin(); it != colors.end(); ++it, ++i)
*it = cv::Scalar(COLS[i][2], COLS[i][1], COLS[i][0]); // BGR
return colors;
}
template<typename ... Args>
std::string format(const std::string &fmt, Args ... args)
{
size_t len = std::snprintf(nullptr, 0, fmt.c_str(), args ...);
std::vector<char> buf(len + 1);
std::snprintf(&buf[0], len + 1, fmt.c_str(), args ...);
return std::string(&buf[0], &buf[0] + len);
}
template<typename ... Args>
void putFmtTxt(cv::Mat &dst, cv::Point &pos, cv::Scalar &col,
const std::string &fmt, Args ... args)
{
const string &txt = format(fmt, args ...);
const int th = 1;
const double fs = 1.0;
const int ff = cv::FONT_HERSHEY_PLAIN;
// const cv::FontFace ff("timesnewroman.ttf"); // only new version
cv::Scalar &dc = cv::Scalar(255, 255, 255) - col;
cv::putText(dst, txt, pos, ff, fs, dc, th + 1, cv::LINE_AA);
cv::putText(dst, txt, pos, ff, fs, col, th, cv::LINE_AA);
// cv::addText(dst, txt, pos, "Consolas", fs, col, th, cv::LINE_AA); // Qt
}
inline unsigned int calcW(unsigned int bitsPixel, unsigned int w)
{
return ((bitsPixel / 8) * w) & ~3;
}
FT_Int ftDrawFontSlotBitmap(cv::Mat dst, FT_GlyphSlot slot,
const cv::Point &loc, const vector<uchar> &col)
{
FT_Int tw = dst.size().width, th = dst.size().height;
FT_Int mw = calcW(24, tw);
FT_Int x = loc.x, y = loc.y; // check in loop i<0 j<0 or no wrap left<->right
#if 0
fprintf(stdout, "(%d %d) %d (%d %d)", tw, th, mw, x, y);
fprintf(stdout, " %d %d %d %d %d %d\n", slot->bitmap_left, slot->bitmap_top,
slot->bitmap.width, slot->bitmap.rows, slot->advance.x, slot->advance.y);
#endif
FT_Bitmap *bmp = &slot->bitmap;
FT_Int x_max = x + bmp->width + slot->bitmap_left;
FT_Int y_max = y + bmp->rows - slot->bitmap_top;
FT_Int i, j, p, q;
for(j = y - slot->bitmap_top, q = 0; j < y_max; ++j, q++){
uchar *img_line = (uchar *)dst.data + j * mw;
for(i = x + slot->bitmap_left, p = 0; i < x_max; ++i, p++){
uchar b = bmp->buffer[q * bmp->width + p];
#if 0
fprintf(stdout, "%c", ".-+*"[b >> 6]);
#endif
if(i < 0 || j < 0 || i >= tw || j >= th) continue;
uchar *img = img_line + i * 3;
double a = b / 255.0;
for(int c = 0; c < 3; ++c){
uchar s = *img;
*img++ = (uchar)(a * col[c] + (1.0 - a) * s);
}
}
#if 0
fprintf(stdout, "\n");
#endif
}
return slot->bitmap.width + slot->bitmap_left;
}
void ftPutText(cv::Mat &dst, const wstring &wtxt, const cv::Point &pos,
const string &ffont, double fontScale, const cv::Scalar &color)
{
#if 0 // cv::Scalar has no iterator
vector<uchar> col; // not set size
#if 0
col.reserve(color.rows); // for push_back
for(int i = 0; i < color.rows; ++i) col.push_back(color[i]);
#else
cv::Mat(color).copyTo(col); // CV_8UC3 ?
#endif
#else
vector<uchar> col(4);
size_t num = 0;
for(auto it = col.begin(); it != col.end(); ++it, ++num) *it = color[num];
#endif
FT_Library library;
FT_Error error = FT_Init_FreeType(&library);
FT_Face face;
FT_Int p = 0; // face index 0: proportional, 1: fixed (wide) eg DFLgs9.ttc
error = FT_New_Face(library, ffont.c_str(), p, &face); // FT_New_Memory_Face
#if 0
error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
#endif
FT_Int dpi = 72, ffact = 64, fpt = 16; // ffact: 64 (1:1) at 72dpi
FT_Int scale = (FT_Int)(fpt * fontScale);
error = FT_Set_Char_Size(face, scale * ffact, 0, dpi, 0);
#if 0
error = FT_Select_Size(face, fpt); // always 16pt ? returns 35 ?
#else
error = FT_Set_Pixel_Sizes(face, scale, scale); // width height OK
#endif
#if 1 // get Selectors (VS)
vector<FT_UInt32> vsel; // unknown size
#if 1 // vsel for face has e0100 e0101 e0102 Migu 1M
FT_UInt32 *fsel = FT_Face_GetVariantSelectors(face);
if(fsel) while(*fsel) vsel.push_back(*fsel++);
#else // vsel for L'芦' has e0100 e0102 Migu 1M (L'漣' has e0101 e0102)
FT_ULong uc = static_cast<FT_ULong>(L'芦'); // L'叱' L'𠮟' has no gsel
FT_UInt32 *gsel = FT_Face_GetVariantsOfChar(face, uc);
if(gsel) while(*gsel) vsel.push_back(*gsel++);
#endif
// get CodePoints in each Selectors
map<FT_UInt32, vector<FT_UInt32> > vselCodePoints;
for(auto &v : vsel){
FT_UInt32 *codePoints = FT_Face_GetCharsOfVariant(face, v);
if(codePoints)
while(*codePoints) vselCodePoints[v].push_back(*codePoints++);
}
#if 0 // display Selectors and CodePoints
for(auto &kv : vselCodePoints){
fprintf(stderr, " %08x:", kv.first);
for(auto &cp : kv.second) fprintf(stderr, " %08x", cp);
fprintf(stderr, "\n");
/*
for face
000e0100: 000053c9 ... 00009bab
000e0101: 00006f23
000e0102: 00006756 ... 000082a6
for L'芦'
000e0100: 000053c9 ... 00009bab
000e0102: 00006756 ... 000082a6
*/
}
#endif
#endif
#if 0 // debug only
if(true){
FT_ULong wc = 0x20b9f; // not found (0xd842df9f bad wc vs)
FT_Int gid = FT_Face_GetCharVariantIndex(face, wc, 0x000e0100); // vs
fprintf(stderr, "test VS id: %08x: %08x\a\n", wc, gid); // 0x00000000
wc = 0x20b9f; // found 0x00003153 (0xd842df9f bad wc)
if(!gid) gid = FT_Get_Char_Index(face, wc);
fprintf(stderr, "test gid id: %08x: %08x\a\n", wc, gid); // 0x00003153
}
#endif
FT_GlyphSlot slot = face->glyph;
cv::Point loc = pos; // copy
FT_Int wnum = 0, wskip = 0; // check and skip SVS IVS
FT_UInt32 wc = 0, wn = 0, wvs = 0; // must be FT_UInt32
for(auto it = wtxt.begin(); it != wtxt.end(); ++it, ++wnum){
FT_UInt32 wch = *it;
if(wskip){ --wskip; continue; }
if(wch == L' '){ loc.x += scale / 2; continue; } // not erase previous draw
// care surrogate pair
FT_UInt16 uw = (FT_UInt16)wch; // for unsigned compare (wch is const)
if(uw < 0x0d800 || uw >= 0x0e000){ // not surrogate
wc = uw;
}else if(uw >= 0x0d800 && uw < 0x0dc00){ // first upper
wc = uw;
continue;
}else if(uw >= 0x0dc00 && uw < 0x0e000){ // second lower
if(!(wc >= 0x0d800 && wc < 0x0dc00)){
fprintf(stderr, "first surrogate out of bounds %08x %08x\a\n", wc, uw);
}else{ // output wc >= u10000 && wc <= u10ffff (4bit+01 16bit)
wc = 0x10000 + ((wc - 0x0d800) << 10) | (uw - 0x0dc00); // 10bit 10bit
// wc = 0x20b9f; // debug only
}
}
#if 0 // debug only
// wc = L'\u9b31'; // for test any glyph re write it (anti optimize) 0x3760
// wc = static_cast<FT_ULong>(L'\u20b9f'); // set bad value wc==0x20b9
// wc = static_cast<FT_ULong>(L'𠮟'); // set bad value wc==0xd842
wc = 0x20b9f; // OK
#endif
if((wn = wtxt[wnum + 1]) != L'\0'){
if(wn == 0xdb40){ // NOT (wn >= 0x0d800 && wn < 0x0dc00){ // first upper
wvs = wn;
if((wn = wtxt[wnum + 2]) != L'\0'){
if(wn >= 0x0dd00 && wn < 0x0ddf0){ // NOT (wn >= 0x0dc00 && wn < 0x0e000){ // second lower
wskip = 2;
wvs = 0x10000 + ((wvs - 0x0d800) << 10) | (wn - 0x0dc00); // 10 10
}else{
wvs = 0;
}
}else{
wvs = 0;
}
}
}
FT_Int gid = 0;
#if 1 // wc=UInt32,([1(or2)]==db40),(dd00<=[2(or3)]<ddf0) ue0100-ue01ef
if(wvs){
for(auto &v : vsel){
if(v == wvs) gid = FT_Face_GetCharVariantIndex(face, wc, v); // VS
#if 1 // break
if(gid) break;
#else // display all VS
if(gid) fprintf(stderr, "VS %08x: %08x: %08x\n", v, wc, gid);
#endif
}
wvs = 0;
}
#endif
if(!gid) gid = FT_Get_Char_Index(face, wc);
#if 1
if(!gid) fprintf(stderr, "glyph id: %08x: %08x\a\n", wc, gid);
#endif
// | FT_LOAD_NO_SCALE | FT_LOAD_NO_AUTOHINT
// FT_ULong flg = FT_LOAD_DEFAULT | FT_LOAD_NO_BITMAP; // get outline
FT_ULong flg = FT_LOAD_RENDER; // get bitmap direct
error = FT_Load_Glyph(face, gid, flg);
if(error){
fprintf(stderr, "error: %d\a\n", error);
}else{
if(slot->format == FT_GLYPH_FORMAT_OUTLINE){
#if 0
fprintf(stdout, "slot->outline\n");
#endif
FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); // FT_RENDER_MODE_LCD
loc.x += ftDrawFontSlotBitmap(dst, slot, loc, col);
}else if(slot->format == FT_GLYPH_FORMAT_BITMAP){
#if 0
fprintf(stdout, "slot->bitmap\n");
#endif
loc.x += ftDrawFontSlotBitmap(dst, slot, loc, col);
}else{
fprintf(stderr, "slot unknown\a\n");
}
}
}
FT_Done_Face(face);
FT_Done_FreeType(library);
}
int main(int ac, char **av)
{
fprintf(stdout, "sizeof(size_t): %zu\n", sizeof(size_t));
vector<cv::Scalar> colors = createColors();
cv::Mat src = cv::imread(FILE_IN);
cv::resize(src, src, cv::Size(IM_W, IM_H), 0, 0, cv::INTER_LANCZOS4);
cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY); // CV_8UC3 -> CV_8UC1
cv::threshold(dst, dst, 127.0, 255.0, cv::THRESH_BINARY | cv::THRESH_OTSU);
dst = ~dst;
#if 0
cv::cvtColor(dst, dst, cv::COLOR_GRAY2BGR);
#else
vector<cv::Mat> bgr{ // BGR
cv::Mat::ones(dst.size(), dst.type()) * 32, // CV_8UC1
dst * 240 / 255, // CV_8UC1
cv::Mat::ones(dst.size(), dst.type()) * 192}; // CV_8UC1
cv::merge(bgr, dst); // CV_8UC3
#endif
#if 0
// 鷗 u9dd7 𠮟 u20b9f = ud842 udf9f 漣 u6f23 芦 u82a6
// const wchar_t *wcs = L"DF麗雅宋 鷗𠮟 PdCaVAWlqg"; // test surrogate
const wchar_t *wcs = L"DF芦鷗𠮟漣龜 Å î PdCaVAWlqg"; // test VS
// const wchar_t *wcs = L"DF麗雅宋 PdCaVAWlqg"; // test kerning
// const wchar_t *wcs = L"Consolas PdCaVAWlqg"; // test proportional
// const wchar_t *wcs = L"Wha't a Beautiful font ++mikaP++ is !";
#else
const wchar_t *wcs = L"漣芦󠄀芦蘆鴎鷗叱𠮟祇󠄀祇󠄁祇祗衹"; // VS u82a6 ue0100
#endif
const wstring wtxt(wcs);
const string ffont(FONT_FN);
ftPutText(dst, wtxt, cv::Point{80, 120}, ffont, 2.0, colors[2]); // normal
ftPutText(dst, wtxt, cv::Point{320, 496}, ffont, 3.0, colors[1]); // over y
ftPutText(dst, wtxt, cv::Point{608, 240}, ffont, 3.0, colors[0]); // over x
ftPutText(dst, wtxt, cv::Point{608, 496}, ffont, 3.0, colors[2]); // over xy
ftPutText(dst, wtxt, cv::Point{64, 32}, ffont, 3.0, colors[1]); // under y
ftPutText(dst, wtxt, cv::Point{-8, 64}, ffont, 3.0, colors[0]); // under x
ftPutText(dst, wtxt, cv::Point{-6, 16}, ffont, 4.0, colors[2]); // u-wrap xy
vector<string> wn({"Src", "Gray", "Diff", "KpPk", "Mask", "Dst"});
for(vector<string>::iterator i = wn.begin(); i != wn.end(); ++i)
cv::namedWindow(*i, CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO);
int cam_id = 0; // 1; // 0; // may be 'ManyCam Virtual Webcam'
int width = IM_W, height = IM_H, fourcc;
double fps = 27.0; // 30.0;
cv::VideoCapture cap(cv::CAP_DSHOW + cam_id); // cap(FN);
if(!cap.isOpened()) return 1;
fprintf(stdout, "width: %d, height %d\n",
(int)cap.get(cv::CAP_PROP_FRAME_WIDTH),
(int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));
// save to .mp4 and convert .mp4 by ShinkuSuperLite
fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v'); // OK .p4 (mp4v)
// fourcc = cv::VideoWriter::fourcc('X', 'V', 'I', 'D'); // OK must save .avi
// fourcc = cv::VideoWriter::fourcc('X', '2', '6', '4'); // OK .avi need .dll
// fourcc = cv::VideoWriter::fourcc('H', '2', '6', '4'); // OK .avi need .dll
// fourcc = 0x00000020; // fallback tag
bool col = true;
cv::VideoWriter wr(OUTDIR"_ttf.mp4", fourcc, fps, cv::Size(IM_W, IM_H), col);
cv::Mat frm(V_H, V_W, CV_8UC3);
cv::Mat gry(IM_H, IM_W, CV_8UC1); // to CV_8UC3
cv::Mat img(IM_H, IM_W, CV_8UC3);
int cnt = BASECOUNT;
long tick0 = cvGetTickCount();
while(cap.read(frm)){
long tick = cvGetTickCount() - tick0;
double sec = (tick / cvGetTickFrequency()) / 1000000.0; // usec to sec
int fcnt = cnt - BASECOUNT + 1;
double ffps = fcnt / sec; // not same as fps of the cv::VideoWriter
// now putFmtTxt does not erase previous drawing (as fast as possible)
putFmtTxt(dst, cv::Point{20, 460}, colors[0], "FPS:%06.2f", ffps);
putFmtTxt(dst, cv::Point{120, 460}, colors[1], "Time:%07.2f", sec);
putFmtTxt(dst, cv::Point{240, 460}, colors[2], "Frame: %08d", fcnt);
cv::GaussianBlur(frm, frm, cv::Size(3, 3), 0);
cv::Mat tmp;
cv::resize(frm, tmp, cv::Size(IM_W, IM_H), 0, 0, cv::INTER_LANCZOS4);
if(cnt == BASECOUNT) tmp.copyTo(img);
cv::cvtColor(tmp, gry, cv::COLOR_BGR2GRAY); // CV_8UC1
cv::cvtColor(gry, gry, cv::COLOR_GRAY2BGR); // CV_8UC3
cv::imshow("Src", tmp);
cv::imshow("Gray", gry);
cv::imshow("Diff", dst);
int fromto[] = {0, 2, 1, 1, 2, 0}; // 0->2 1->1 2->0
cv::mixChannels(dst, img, fromto, _countof(fromto) / 2);
cv::imshow("Dst", img);
wr << img;
++cnt;
int k = cv::waitKey(1); // 1ms > 15ms ! on Windows
if(k == 'q' || k == '\x1b') break;
}
wr.release();
cap.release();
cv::destroyAllWindows();
fprintf(stdout, "frames: %d\n", cnt);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment