Last active
May 14, 2022 07:08
-
-
Save nomissbowling/5367fc4c6bccf33325a6017436fb2f4d to your computer and use it in GitHub Desktop.
cvPutTextFontFT.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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