SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

C++で線を描画する際のさまざまなテクニック

C++による高品質な線の描画手法

描画先の画素配列

 「glbuffer2d.h」で実装しています。

 今回解説するプログラムが線分を描く先はメモリです。メモリを2次元配列的な構造として扱い、碁盤の目のように並んだ格子にドット(画素、ピクセル)を配置していくやり方で擬似的に線分を表現します。

 何はともあれ、まずキャンバスとなる画像クラスを作成することにします。2次元配列なのでまず必要となる要素は、

  • 横幅
  • 縦幅

 が考えられます。実際にプログラム上で扱うには、これ以外にさらに2つの変数を管理する必要があります。

  • 確保したメモリを指すポインタ変数
  • 1行分移動するのに必要なバイト数

 場合によってはクラスの外で確保したメモリも扱えるようにする必要があるので、ポインタ変数を外部から設定できるようにもしておきます。また処理速度を追求すると、安全性を犠牲にしてメモリを直接操作したいこともあるので、メソッドを用意して取り出せるようにします。

 1行分移動するのに必要なバイト数をメンバに持つ理由は、4バイト境界などに各行の開始アドレスを合わせて、画素配列用のメモリが確保される場合があるからです。この理由はCPUが高速にメモリアクセスを行えるようにするためのようです。WindowsのBITMAPはそのようなフォーマットです。また、SSEなどのSIMDで処理する場合は、16バイト境界に合わせてメモリを確保する必要があるようです。

 TopDown座標系の場合は、確保したアドレスの先頭が(0, 0)の座標になり、1行分移動するのに必要なバイト数は正数となります。BottomUp座標系の場合は(0, 0)の座標の位置のアドレスは、確保したアドレスの先頭+1行のバイト数×(縦幅-1)になり、1行分移動するのに必要なバイト数は負数となります。

 また画素の型を決め打ちではなく、自由に指定できるようにすると良いでしょう。一般的に良く扱う画素の種類は、RGBA(赤・緑・青・不透明度)各8bit精度で、ちょうど32bitCPUが扱いやすいint型の大きさに収まりますが、高精度を求めたり、色の並びがRGBAではなく、ARGBやBGRA、ABGRとなる可能性も考えられます。また、色のモデルがRGBではなくCMYKだったりするかもしれませんし、単純にAlphaだけ計算したいのかもしれません。そこで画素の型をtemplateのparameterとします。

色の型

 「glcolor.h」で実装しています。

 画素を表す色を型でカプセル化します。その目的は上で解説している内容と、あとは利便性のためです。各色の強度の範囲は、0.0以上~1.0未満とします。

 まず光の3原色のRGBと不透明度のAlphaをメンバーに持つColorクラスを用意します。メモリ表現として、各メンバーの並び順を自由に調整できるようにしたいですが、それは基底型を必要な分だけ作る方法で実現します。各色の強度を示す数値型(精度およびサイズが型に依存)を自由にするために、それを基底型のtemplate parameterとします。コンストラクタやオペレーター演算子などの実装は派生先の型に記述することにします。少し面倒ですが、派生先の型のtemplate parameterに基底型を指定することによって上記の事を実現します。

 また不透明度だけを要素とするモノクロームカラーを表現する型も用意しておきましょう。こちらは要素が1つだけなので数値型をtemplateのparameterで指定するようにします。

固定小数点型

 「glfixedpoint.h」で実装しています。

 今回、固定小数点数を要素に持った型にオペレーターオーバーロード演算子などを定義することによって、基本型のように扱えるようにしました。

 固定小数点とは、浮動小数点とは違い、小数部のビット幅が固定な数値表現の事を指します。浮動小数点型はfloatは32bit、doubleは64bitのサイズがありますが、これでは大きすぎるという場合用に固定小数点型を作成しました。環境によっては固定小数点のが速度が出る場合もあれば、浮動小数点のほうが高速な場合もあります。時と場合によって使い分けが必要なようです。

線分計算の基礎

 線と言っても無限大の長さを持つ線ではなく、有限の長さを持つ「線分」とします。点1と点2の2点が存在したとして、その2点を結ぶ線分を描くには、まず座標を明らかにしなければいけません。

  • 点1のX座標
  • 点1のY座標
  • 点2のX座標
  • 点2のY座標

 この4つの数値から以下の事が分かります。

  • 線の横幅 : 点1のX座標と点2のX座標の差の絶対値。
  • 線の縦幅 : 点1のY座標と点2のY座標の差の絶対値。
  • 線の傾き : (Y2-Y1)÷(X2-X1)。

 そして以下のことも自明です。

  • 線の横幅が0の時、その線の傾きは無限大。完全に縦の直線となる。
  • 線の縦幅が0の時、その線の傾きは0。完全に横の直線となる。
  • 傾きが1の時、その線は完全に斜め右上45度の方向に傾いている。
  • 傾きが-1の時、その線は完全に斜め右下45度の方向に傾いている。
  • 傾きの絶対値が1より低い場合、横幅の方が縦幅より大きい。
  • 傾きの絶対値が1より大きい場合、縦幅の方が横幅より大きい。

 直線の方程式は、ax+by+c=0 のようですが、よく聞くのは、y=ax+bという形かと思います。y=ax+bの形であればプログラムでも扱いやすいように思えるかもしれません。xの座標が変化した時のyの値を求めてその位置に点を置けば良いからです。しかし、傾きが1より大きい場合、そのやり方で点を求めていくと線がきちんと繋がりません。傾きが1より小さい場合は、1行に2点以上点が置かれる場合が出てきますし、同様に傾きが1より大きい場合は1列に2点以上点を置く場合が出てくるからです。そのため、傾きが1より大きい場合は、xをインクリメントしていくループ処理ではなく、yをインクリメントしていくループ処理で線を記述すると良いでしょう。

次のページ
クリッピング処理

修正履歴

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

べるぽん(ベルポン)

職業:プログラマ日記:http://d.hatena.ne.jp/berupon/物置:http://www.geocities.jp/beruponu/index.html 

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/335 2006/03/27 14:12

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー