この記事はC++ Advent Calendar 2022の10日目の記事です。
TL;DR & 前書き
- この記事は、stb_easy_font.h についての使い方の説明になります。
簡単な自己紹介
画像処理系の組み込みエンジニアです。OpenCV communityにもちょくちょくコメントしています。よろしくお願いします!
本当は C++20の「モジュール」について記事を書こうとしたのですが、時間が無く、ゆるふわネタです。ごめんなさい。
煽られてしまいましたか…
(ふぉんとえんじんのきじがよみたいです)
— dandelion (@dandelion1124) November 29, 2022
フォントエンジン・・・フォントエンジンの記事が読みたいとな!!
ということで、STBのフォントエンジン記事を書きますかね?
ただし、STBはSTBでも、stb_truetype.h ではなく、stb_easy_font.h です!
そもそもSTBとは何ぞや?
see https://github.com/nothings/stb ...
single-file public domain (or MIT licensed) libraries for C/C++
「ヘッダファイル1つで機能を実現するライブラリ」ですかね。
例えば、画像データを読み込みたいならば stb_image.h
、書き込みたいならstb_image_write.h
をincludeすれば、この中に実行部分も入っているのですぐに使えます。
OpenCV 5.0以後では、stb_truetype.h を使って、UTF-8で日本語なんかも書けるようになっちゃいます!さて、それはそれとして……
stb_easy_font.h とは?
ヘッダファイルによると「3D Rendering向けのbitmap font」とのこと。
// stb_easy_font.h - v1.1 - bitmap font for 3D rendering - public domain
// Sean Barrett, Feb 2015
//
// Easy-to-deploy,
// reasonably compact,
// extremely inefficient performance-wise,
// crappy-looking,
// ASCII-only,
// bitmap font for use in 3D APIs.
//
解説
本体部分はたった3行である。
char buffer[99999];
char text[]="C++ Advent Calendar 2022\nHappy New Year !!!\nWith stb_easy_font";
int num_quads = stb_easy_font_print(0, 0, text, NULL, buffer, sizeof(buffer));
この3行を実行すると、buffer
の中に、指定した文字列を描画するための座標情報が保持される。
Offset | 内容 |
---|---|
+0x0 | X1座標(Float) |
+0x4 | Y1座標(Float) |
+0x8 | Z1座標(Float, 0固定) |
+0xC | 色情報 |
+0x10 | X2座標(Float) |
+0x14 | Y2座標(Float) |
+0x18 | Z2座標(Float, 0固定) |
+0x1C | 色情報 |
+0x20 | X3座標(Float) |
+0x24 | Y3座標(Float) |
+0x28 | Z3座標(Float, 0固定) |
+0x2C | 色情報 |
+0x30 | X4座標(Float) |
+0x34 | Y4座標(Float) |
+0x38 | Z4座標(Float, 0固定) |
+0x3C | 色情報 |
本来は...
当該データをOpenGLで処理して・・・ということができていたらしい。
今回は...
これに対して、テスト結果を確認するために、画像を描画しなければならない。
- pixel()関数 - フレームバッファに画素を書き出す
- line()関数 - フレームバッファに直線を書き出す
- PBMでの画像出力。
出力結果
こんな感じで、無事に文字出力ができました、と。
以上となります。
おまけ:サンプルコードを以下に示す。
// g++ main.cpp -o a.out
// ./a.out > test.pbm
#include <iostream>
#include "stb_easy_font.h"
const int screen_height = 60;
const int screen_width = 200;
uint32_t fb[screen_height][screen_width] = { 0 };
void pset(int x, int y)
{
if ( ( x < 0 ) || ( screen_width < x ) ) { return ; }
if ( ( y < 0 ) || ( screen_height < y ) ) { return ; }
fb[y][x] = 1;
}
void line(float x1, float y1, float x2, float y2 )
{
int x,y;
float sx, sy, ex, ey;
if ( x1 == x2 )
{
// Vertial line
if( y1 < y2 )
{
x = x1; sy = y1; ey = y2;
}
else
{
x = x1; sy = y2; ey = y1;
}
for( y = sy; y <= ey; y++ )
{
pset(x,y);
}
}else{
// Non vertial line
if( x1 < x2 )
{
sx = x1; ex = x2; sy = y1; ey = y2;
}
else
{
sx = x2; ex = x1; sy = y2; ey = y1;
}
for( x = sx; x <= ex; x++ )
{
y = sy + ( x - sx ) * ( ey - sy ) / ( ex - sx );
pset(x,y);
}
}
}
int main()
{
// Drawing
char buffer[99999];
char text[]="C++ Advent Calendar 2022\nHappy New Year !!!\nWith stb_easy_font";
int num_quads = stb_easy_font_print(0, 0, text, NULL, buffer, sizeof(buffer));
// Rendering
for(int i = 0 ; i < num_quads; i++ )
{
const float x1 = *(float*)&( buffer[i * 64 + 16 * 0 + 0 ] );
const float y1 = *(float*)&( buffer[i * 64 + 16 * 0 + 4 ] );
const float x2 = *(float*)&( buffer[i * 64 + 16 * 1 + 0 ] );
const float y2 = *(float*)&( buffer[i * 64 + 16 * 1 + 4 ] );
const float x3 = *(float*)&( buffer[i * 64 + 16 * 2 + 0 ] );
const float y3 = *(float*)&( buffer[i * 64 + 16 * 2 + 4 ] );
const float x4 = *(float*)&( buffer[i * 64 + 16 * 3 + 0 ] );
const float y4 = *(float*)&( buffer[i * 64 + 16 * 3 + 4 ] );
line(x1, y1, x2, y2 );
line(x2, y2, x3, y3 );
line(x3, y3, x4, y4 );
line(x4, y4, x1, y1 );
}
// Export to PBM
std::cout << "P1 " << screen_width << " " << screen_height << std::endl; ;
for(int iy=0; iy < screen_height; iy++ )
{
for(int ix=0; ix < screen_width; ix++ )
{
std::cout << ( fb[iy][ix] == 0 ? 0 : 1) << " ";
}
std::cout << std::endl;
}
return 0;
}