トップコンテンツプログラムノベルゲーム>C++ による文字描画機能のクラス化



C++ による文字描画機能のクラス化

このページでは、「6. 文字の表示 その2」のプログラムの内容をクラス化したものを公開しておきます。
あくまで一例ですので、参考までに。


gl_sub1_class.hpp (クラス定義を行なったヘッダファイル) [表示・非表示]
 1.
 2.
 3.
 4.
 5.
 6.
 7.
 8.
 9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <ft2build.h>
#include FT_FREETYPE_H

/* FreeTypeエラーメッセージを、配列へ格納 */
#include <freetype/fterrors.h>
extern "C"{
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s )  { e, s },
#define FT_ERROR_START_LIST     {
#define FT_ERROR_END_LIST       { 0, 0 } };

const struct
    {
      int          err_code;
      const char*  err_msg;
    } ft_errors[] =

#include FT_ERRORS_H
}

#include <GL/glut.h>

// 前方参照用
class Fontdata;
class strRenderer;


/******************************************
 * 名前 : area
 * 説明 : 描画領域についての情報を格納する構造体(OpenGL で使用)
 ******************************************/
typedef struct
{
  GLint x, y;          // 原点からの(OpenGLなら左下)描画開始位置
  GLsizei width, height; // それぞれ描画領域の幅, 高さ
} area;


/**************************************************************
 * 名前 : setViewport
 * 機能 : ビューポート(描画範囲?)の設定
 * 引数 : const area& space     描画領域に関するデータ(構造体)
 * 戻値 : なし
 *************************************************************/
inline void setViewport(const area& space)
{
  glViewport(space.x, space.y, space.width, space.height);
}


/************************
 * 名前 : Fontdata
 * 説明 : フォントのデータを保持するクラス
 ************************/
class Fontdata
{
public:
  // コンストラクタ
  Fontdata();  // freetypeライブラリのロードのみ
  Fontdata(const char* filepath, const int size_px); // 標準的なもの

  // フォントファイルの変更
  void setFont(const char* filepath);

  // 文字サイズの変更
  void setSize(const int font_px);  // pixelで指定

  // 指定した文字のグリフデータを読み込み、テクスチャを作成
  void setCharGlyph(const FT_ULong ch);

  // 文字画像配置時、基準からの x 方向のオフセットを返す
  FT_Int x_offset () const {return slot->bitmap_left;}

  // 文字画像配置時、基準からの y 方向のオフセットを返す
  FT_Int y_offset () const {return ( slot->bitmap_top - letterHeight() );}

  // 文字画像の幅を取得
  int letterWidth () const {return bits.width;}

  // 文字画像の高さを取得
  int letterHeight () const {return bits.rows;}

  // 横に関する1em辺りのピクセル数を返す
  FT_UShort font_x_ppem () const {return face->size->metrics.x_ppem;}

  // 縦に関する1em辺りのピクセル数を返す
  FT_UShort font_y_ppem () const {return face->size->metrics.y_ppem;}

  // 現在のフォントサイズを返す
  FT_UInt fontsize () const {return size;}

  // 文字画像のテクスチャ ID を返す
  GLuint getImageID () const {return texture;}

private:
  void initFTLib();

  FT_Library library; // freetypeライブラリへのポインタ
  FT_Face face;       // フォントファイルデータへのポインタ
  FT_GlyphSlot slot;  // グリフスロットへのポインタ
  FT_Bitmap bits;     // グリフのBMPデータへのポインタ
  FT_UInt size;       // 現在のフォントサイズ(単位:px)
  GLuint texture;     // テクスチャ ID
};

/************************
 * 名前 : strRenderer
 * 説明 : 文字列の描画処理をするためのクラス(都合上、1文字の場合も含む)
 ************************/
class strRenderer
{
public:
  // 最低限の初期化コンストラクタ
  strRenderer () : obj() {}

  // obj に設定を行なうためのコンストラクタ
  strRenderer(const char* filepath, const int size_px) : obj(filepath, size_px) {}

  // 文字列の描画
  template<typename T>
  void render(const T* str, const float scale, const area& space);

  // 指定したウィンドウ座標上に、指定されたサイズでテクスチャを表示
  // (メソッドにする必要ある?)
  void renderTexture(const short x, const short y,
                     const short width, const short height,
                     const GLuint id, const area& space);

  // フォントのサイズを変更
  void setSize(const int font_px) {obj.setSize(font_px);}

  // 使用するフォントファイルを変更
  void setFont(const char* filepath) {obj.setFont(filepath);}

private:
  // 指定したウィンドウ座標上に、1 字を画面出力(render内部で使用)
  area renderLetter(const short x, const short y, const FT_ULong ch,
                    const float scale, const area& space);

  // 改行処理(render内部で使用)
  void newline(area& next, const float scale, const area& space)
    {
      next.x = space.x, next.width = 0;
      next.y -= obj.font_y_ppem() * scale * 1.2;
    }

  // クラス内でのみ使用出来る定数
  static const unsigned char  HALFSP = 0x0020;  // 半角スペース
  static const unsigned short FULLSP = 0x3000;  // 全角スペース
  static const unsigned char  LF     = '\n';    // 改行文字

  // リソース
  Fontdata obj;   // フォントのデータ
};


/**************************************************************
 * 名前 : render(strRenderer クラスのメソッド)
 * 機能 : 文字列のレンダリング
 * 引数 : const T* str   (T = FT_Long, unsigned short, char?) 文字列
 * 引数 : const float scale   現在の文字サイズからの表示倍率(1.0 = そのまま)
 * 引数 : const area& space   描画領域の構造体への参照
 * 戻値 : なし
 * 備考 : template の実装はヘッダファイルに書かなきゃダメらしい……
 *************************************************************/
template<typename T>
void strRenderer::render(const T* str, const float scale, const area& space)
{
  int i;
  int Ymax = 0;    // 高さの最大値
  const int num = strlen(str);  // 文字数
  area tmp = {0};  // 描画開始位置(画像領域の左下を基準)

  setViewport(space);

  tmp.x = space.x;
  tmp.y = (space.y + space.height) - (obj.font_y_ppem() * scale);


  /* 文字列のレンダリング */
  for (i = 0; i < num; ++i)
    {
      if (str[i] == LF)
        {
          // 明示的な改行処理
          newline(tmp, scale, space);
          continue;
        }

      tmp = renderLetter((tmp.x + tmp.width), tmp.y,
                         static_cast<FT_Long>(str[i]), scale, space);

      /* 次の文字を画面出力する際、表示領域からはみ出るかの確認 */
      if ((tmp.x + tmp.width + (obj.font_x_ppem() * scale)) > 760.0)
        {
          // 自動的な改行処理
          newline(tmp, scale, space);
          continue;
        }
    }
}

gl_sub1_class.cpp (定義したメソッドの実装) [表示・非表示]
 1.
 2.
 3.
 4.
 5.
 6.
 7.
 8.
 9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
#include "gl_sub1_class.hpp"
#include <iostream>


/**************************
 * Fontdata クラスのメソッド
 **************************/

/**************************************************************
 * 名前 : initFTLib(Fontdata クラスの公開メソッド)
 * 機能 : FreeType ライブラリの初期化
 * 引数 : なし
 * 戻値 : なし
 *************************************************************/
void Fontdata::initFTLib()
{
  using std::cerr;

  FT_Error err;

  /* freetypeライブラリの初期化 */
  err = FT_Init_FreeType(&library);
  if (err != 0)
    {
      cerr << "FreeType error: " << ft_errors[err].err_msg << '\n';
      assert(0);
    }
}

/**************************************************************
 * 名前 : Fontdata(Fontdata クラスの公開メソッド)
 * 機能 : 最低限の初期化(freetypeライブラリの初期化)だけを行なうコンストラクタ
 * 引数 : なし
 * 戻値 : なし
 *************************************************************/
Fontdata::Fontdata() : face(NULL), slot(NULL), bits(), size(0), texture(0)
{
  initFTLib();
}

/**************************************************************
 * 名前 : Fontdata(Fontdata クラスの公開メソッド)
 * 機能 : 標準的な初期化用コンストラクタ
 * 引数 : const char* filepath  フォントファイルへのパス
 * 引数 : const int size_px     文字のサイズ(単位 : px)
 * 戻値 : なし
 *************************************************************/
Fontdata::Fontdata(const char* filepath, const int size_px) : texture(0)
{
  using std::cerr;

  /* freetypeライブラリの初期化 */
  initFTLib();

  /* フォントフェースをロード(フォントファイルからロードする) */
  setFont(filepath);

  /* 文字サイズの設定(単位:px) */
  setSize(size_px);
}

/**************************************************************
 * 名前 : setFont(Fontdata クラスの公開メソッド)
 * 機能 : フォントファイルの読込と設定をを行なう
 * 引数 : const char* filepath  フォントファイルへのパス
 * 戻値 : なし
 *************************************************************/
void Fontdata::setFont(const char* filepath)
{
  using std::cerr;

  FT_Error err;

  err = FT_New_Face(library, filepath, 0, &face);
  if (err != 0)
    {
      cerr << "FreeType error: " << ft_errors[err].err_msg << '\n';
      assert(0);
    }

  slot = face->glyph; 
}

/**************************************************************
 * 名前 : setFont(Fontdata クラスの公開メソッド)
 * 機能 : フォントファイルの読込と設定をを行なう
 * 引数 : const int font_px  文字サイズ(単位 : px)
 * 戻値 : なし
 *************************************************************/
void Fontdata::setSize(const int font_px)
{
  using std::cerr;

  FT_Error err;

  size = font_px;
  err = FT_Set_Pixel_Sizes(face, size, size);
  if (err != 0 )
    {
      cerr << "FreeType error: " << ft_errors[err].err_msg << '\n';
      assert(0);
    }
}

/**************************************************************
 * 名前 : setCharGlyph(Fontdata クラスの公開メソッド)
 * 機能 : 引数で指定された文字の字形データ(文字のビットマップ画像)を取得
 * 引数 : const FT_ULong ch       文字のUTF-32コード
 * 戻値 : なし
 *************************************************************/
void Fontdata::setCharGlyph(const FT_ULong ch)
{
  using std::cerr;

  FT_Error err;

  /* グリフデータのロード */
  err = FT_Load_Char(face, ch, FT_LOAD_RENDER);
  if (err != 0)
    {
      cerr << "FreeType error: " << ft_errors[err].err_msg << '\n';
      assert(0);
    }

  /* 字形のbmpデータへのポインタ値を登録 */
  bits = slot->bitmap;

  // テクスチャの作成
  glGenTextures(1, &texture);
  glBindTexture(GL_TEXTURE_2D, texture);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY,
               bits.width, bits.rows, 0, GL_LUMINANCE,
               GL_UNSIGNED_BYTE, bits.buffer);
}


/**************************
 * strRenderer クラスのメソッド
 **************************/

/**************************************************************
 * 名前 : renderLetter(strRenderer クラスの非公開メソッド)
 * 機能 : strRenderer からの指令により、文字 1 字の出力に必要な処理を行なう
 * 引数 : const short x, y       ウィンドウ座標位置
 * 引数 : const FT_ULong ch      文字のUTF-32コード
 * 引数 : const float scale      文字の表示倍率(1.0 = sizeに記録されているサイズ)
 * 引数 : const area& space      描画領域(内部で呼び出すrenderTextureで使うため)
 * 戻値 : なし
 *************************************************************/
area strRenderer::renderLetter(const short x, const short y, const FT_ULong ch,
                             const float scale, const area& space)
{
  area tmp = {x, y, 0, 0};        // 戻り値を格納

  switch (ch)
  {
    case HALFSP:
      tmp.width = static_cast<GLint> (obj.font_x_ppem() * 0.4 * scale);
      tmp.height = tmp.width;
      break;
    case FULLSP:
      tmp.width = static_cast<GLint> (obj.font_x_ppem() * 0.9 * scale);
      tmp.height = tmp.width;
      break;
    default:
      /* 文字のビットマップデータ読込 */
      obj.setCharGlyph(ch);

      /* レンダリング */
      glPushMatrix();
      renderTexture(x + (obj.x_offset() * scale),
                    y + (obj.y_offset() * scale),
                    (obj.letterWidth() * scale), (obj.letterHeight() * scale),
                    obj.getImageID(), space);
      glPopMatrix();


      /* 文字の描画範囲を格納して返却する */
      tmp.width = obj.letterWidth() * scale;
      tmp.height = obj.letterHeight() * scale;
  }

  return tmp;
}


/**************************************************************
 * 名前 : renderTexture(strRenderer クラスの公開メソッド)
 * 機能 : 指定されたウィンドウ座標の位置にテクスチャを描画する
 * 引数 : const short x, y            ウィンドウ座標位置
 * 引数 : const short width, height   画像の幅, 高さ
 * 引数 : const GLuint id             表示したいテクスチャ画像の ID
 * 引数 : const area& space           描画領域(内部で呼び出すrenderTextureで使う)
 * 戻値 : なし
 *************************************************************/
void strRenderer::renderTexture(const short x, const short y,
                              const short width, const short height,
                              const GLuint id, const area& space)
{
  float x1, y1, x2, y2;
  x1 = static_cast<float>(x - (space.x)) / static_cast<float>(space.width);           // 左端
  x2 = static_cast<float>(x - (space.x) + width) / static_cast<float>(space.width);   // 右端
  y1 = static_cast<float>(y - (space.y)) / static_cast<float>(space.height);          // 下端
  y2 = static_cast<float>(y - (space.y) + height) / static_cast<float>(space.height); // 上端

  glEnable(GL_TEXTURE_2D);

  glBindTexture(GL_TEXTURE_2D, id);
  glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2s(1, 0);  glVertex2f(x2, y2);
    glTexCoord2s(1, 1);  glVertex2f(x2, y1);
    glTexCoord2s(0, 0);  glVertex2f(x1, y2);
    glTexCoord2s(0, 1);  glVertex2f(x1, y1);
  glEnd();

  glDisable(GL_TEXTURE_2D);
}

gl_sub1.cpp (プログラムを実行する) [表示・非表示]
 1.
 2.
 3.
 4.
 5.
 6.
 7.
 8.
 9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
#include <iostream>
#include <cstdlib>
#include <cstring>

#include <GL/GLUT.h>

/* freetypeライブラリ使用時に必要なおまじない */
#include <ft2build.h>
#include FT_FREETYPE_H
#include "Fontdata1.hpp"

#pragma comment(lib, "freetype244MT.lib")
#define  FONTFILE  "C:\\Windows\\Fonts\\msgothic.ttc"
const area msgbox = {40, 30, 720, 180};  // メッセージボックスの領域
area window = {0, 0, 800, 600};

/* グローバル変数 */
strRenderer* obj;


/* 関数のプロトタイプ宣言 */
void myInit(void);
void display(void);


int main(int argc, char* argv[])
{
  /* 初期化 */
  glutInit(&argc, argv);
  myInit();

  /* イベント待機状態に入る */
  glutMainLoop();

  return 0;
}

/* 個々のプログラム独自に行なうべき初期化 */
void myInit(void)
{
  using std::cerr;
  /* ウィンドウ生成前に行なうべき設定 */
  glutInitWindowSize(800, 600);                  // ウィンドウサイズの設定
  glutInitWindowPosition(200, 200);              // 表示位置の設定
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);  // ディスプレイモードの設定
  glutCreateWindow("OpenGL");                    // ウィンドウを生成
  glClearColor(0.6, 0.0, 1.0, 1.0);

  glEnable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 2.0);        // ビューボリュームの設定(平行投影)

  /* freetype */
  /* ライブラリ,フォントファイルの初期化 */
  obj = new strRenderer (FONTFILE, 200);


  /* イベント発生時に呼び出される関数の登録 */
  glutDisplayFunc(display);                      // 描画関数(自分自身で定義)を登録
}

void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT);

  glPushMatrix();
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    obj->render("abdjiis", 0.3, msgbox);
    glBlendFunc(GL_ONE, GL_ZERO);
  glPopMatrix();

  glutSwapBuffers();
}

ここからはクラス設計に関するただの垂れ流し文章なので、見る必要はないです。


クラス定義は長いのですが、それを利用するコードはかなりすっきりしています。
クラスの設計というものをやってみたのはこれで 2 度目なのですが、今回は「Effective C++」を部分的にですが読んだおかげで、「カプセル化」とか「is-inmplemented-in-terms-of 関係」(実装関係)というものを意識できた気がします。

最初、文字列を描画する render メソッドは Fontdata オブジェクト(フォントのデータを記憶)を引数に渡し、メソッド内部で Fontdata クラスの private メンバにアクセスして処理を行なう、というものでした。
イメージ的に工場での流れ作業みたいなものを考えていたので、データを保持するだけのクラス, データを処理するだけのクラス、という分け方をしていました。
でも、そうすると、引数に渡したオブジェクトの private メンバにはアクセス出来ないので、もちろん、friend 登録はしてあります。


しかし、普通は friend を使わずにカプセル化をきちんとやるのが当然な気がする。
ということで、メソッドの処理を行なうのに必要な情報(リソース)として、内部に Fontdata オブジェクトを持つ形に変更したのが現在の形です。
正直それだけ。でも、friend を使わずに、やりたいことが綺麗に処理できている。
カプセル化もきちんとできている。

ただ、strRenderer クラスのメソッドに、setSize, setFont のメソッドを用意しなければいけないのが、個人的に違和感を覚える。
用意しないと文字サイズと使うフォントファイルの入れ替えは出来ない。
でも、Renderer という機能的に、描画機能ではない処理がメソッドとしてあるのは、変な気がする。
逆に、内部に Fontdata がない場合だと、一々引数に Fontdata を渡さなければならないから、正直面倒な気もする。


friend を使うけど、フォントに対する処理に関してはアクセス方法が自然な方か。
もしくは、完全にカプセル化されており、文字列描画を行なうメソッドの引数指定もすっきり!な方か。
普通は後者なんだろうな……。“自分”もですが。