このページではC言語で「線」を描画する方法について解説します。
線を描画するために必要な「ビットマップデータ」や「点の描画方法」については下のページで解説していますので、まだ読んでない方は事前に下のページを読むことをオススメします。
C言語で図形を描画する線の描画は「座標を移動させながら連続的に点を描画する」ことで実現しますので、特に点の描画については上のページで理解しておいていただけると本ページの内容も理解しやすくなると思います。
Contents
横線を描画する
まずは「横線」の描画を行います。点の描画ができれば横線の描画も簡単です。
横線を描画する考え方
横線は「横方向に点を連続して描画したもの」と考えることができます。
なので、横方向に向かって座標を移動しながら各座標に点を描画してやることで、横線を描画することができます。
横方向に座標を移動させるためには、縦方向の座標 y を固定し、横方向の座標 x のみを増加させることになります。例えば y = y1
と固定して引いた横線は下の図のようになります。
この座標 x のみを増加させる処理は、x に対するループ処理で簡単に実現することが可能です。
for(x = 0; x < width; x++){
/* (x, y1) 座標に対する処理 */
}
スポンサーリンク
横線を描画する関数
ここで紹介する横線を描画する関数は下記になります。
void drawHorizontalLine(
unsigned char *data, /* ビットマップデータ */
unsigned int width, /* ビットマップの横幅 */
unsigned int height, /* ビットマップの高さ */
unsigned int y1, /* 横線のy座標 */
unsigned char r, /* 赤の輝度値 */
unsigned char g, /* 青の輝度値 */
unsigned char b /* 緑の輝度値 */
){
unsigned int x;
unsigned char *p;
for(x = 0; x < width; x++){
p = data + y1 * width * 3 + x * 3;
p[0] = r;
p[1] = g;
p[2] = b;
}
}
x
を 0
から width - 1
まで増加させるループの中で点を描画しているだけですね。
縦方向を示す y
座標を固定し、横方向を示す座標である x
を 0
から width - 1
まで変化させながら点の描画を行なっているので、横方向に連続して点が描画され、結果的に線が描画されることになります。
例えば y = 100
の横線を描画するためには、上記の drawHorizontalLine
関数を下記のように呼び出せば良いです。第4引数で横線の y
座標を指定しています。色は赤色に指定しています。
/* y = 100 の横線を描画 */
drawHorizontalLine(
bitmap.data,
bitmap.width,
bitmap.height,
100, /* 横線のy座標 */
0xFF, 0x00, 0x00 /* 赤色 */
);
上記の drawHorizontalLine
関数のように、このページに載せている関数は、下記ページで紹介した main
関数から呼び出すのがオススメです
また PI
の定義と math.h
のインクルードは、後半で紹介する斜め線の描画時に必要になります
libpng をインストールすれば PNG 出力して画像のプレビューも簡単にできます
このページで紹介する描画結果も上記の main
関数で出力した output.png
をプレビューしたものになります。
横線の描画結果
上記のように drawHorizontalLine
関数を呼び出した時に出力される output.png
は下のようになります。
縦線を描画する
続いては「縦線」です。横線の描画とほぼ同じやり方で縦線の描画もできてしまいます。
スポンサーリンク
縦線を描画する考え方
横線は横方向に点を描画していくのに対し、縦線は「縦方向に点を描画する」ことになります。横線との違いはこれだけです。
縦線を描画する関数
縦線を描画する関数は下記の通りになります。
void drawVerticallLine(
unsigned char *data, /* ビットマップデータ */
unsigned int width, /* ビットマップの横幅 */
unsigned int height, /* ビットマップの高さ */
unsigned int x1, /* 縦線のx座標 */
unsigned char r, /* 赤の輝度値 */
unsigned char g, /* 青の輝度値 */
unsigned char b /* 緑の輝度値 */
){
unsigned int y;
unsigned char *p;
for(y = 0; y < height; y++){
p = data + y * width * 3 + x1 * 3;
p[0] = r;
p[1] = g;
p[2] = b;
}
}
例えば x = 100
の縦線を描画するためには、上記の drawVerticalLine
関数を下記のように呼び出せば良いです。第4引数で縦線の x
座標を指定しています。色は赤色に指定しています。
/* x = 100 の縦線を描画 */
drawVerticalLine(
bitmap.data,
bitmap.width,
bitmap.height,
100, /* 縦線のx座標 */
0xFF, 0x00, 0x00 /* 赤色 */
);
縦線の描画結果
上記のように drawVerticalLine
関数を呼び出した時に出力される output.png
は下のようになります。
スポンサーリンク
斜め線を描画する(長さと角度指定)
ここでは「斜め線」の描画の仕方について説明します。斜め線については2つのやり方を紹介します。まずは「長さと角度」を指定して斜め線の描画を行う方法について紹介します。
斜め線を描画する考え方(長さと角度指定)
ここで紹介するのは、長さ length
と角度 angle
、さらには始点の座標 (x1
, y1
) を指定して斜め線を描画する方法です。
角度については反時計方向ではなく、時計方向が正方向になるので気をつけてください
数学等で学んだ時とは逆方向だと思います
これは縦軸方向の正方向が下方向になっているためです。
斜め線は、「斜め線上の座標に点を描画する」ことで描画することができます。
なので、どの座標が「斜め線上に存在するのか」が分かれば斜め線の描画を実現することができます。
そこで、どの座標が「斜め線上に存在するのか」について考えてみましょう。これは三角関数を利用すれば簡単に求めることができます。
例えば、下図のように、始点座標を (x1
, y1
) 、x
軸と斜め線の成す角度を angle
とした時の斜め線上に座標 (x
, y
) が存在する場合について考えてみましょう!
この座標 (x
, y
) と始点座標 (x1
, y1
) との距離は l
とします。
図を見ると分かるように、l を 0 から length – 1 まで増加させながら変化させ、それに対応した座標 (x, y) に点を描画していくことで斜め線を描画することが可能です。
で、この座標 (x
, y
) の x
と y
は、距離 l
と角度 angle
を用いて下記の式で計算することができます(angle
は θ
で表記してます)。
x = x1 + l * cos θ
y = y1 + l * sin θ
なので、l
を 0
から length - 1
まで増加させるループの中で、その l
に対応する (x
, y
) を上式で求め、その座標に点を描画することで斜め線が描画できます。
斜め線を描画する関数(長さと角度指定)
長さと角度を指定して斜め線を描画する関数は下記のようになります。
void drawLineAngleLength(
unsigned char *data, /* ビットマップデータ */
unsigned int width, /* ビットマップの横幅 */
unsigned int height, /* ビットマップの高さ */
unsigned int angle, /* 線の角度 */
unsigned int length, /* 線の長さ */
unsigned int x1, /* 線の始点のx座標 */
unsigned int y1, /* 線の始点のy座標 */
unsigned char r, /* 赤の輝度値 */
unsigned char g, /* 青の輝度値 */
unsigned char b /* 緑の輝度値 */
){
unsigned int l;
unsigned int x, y;
unsigned char *p;
double rad;
rad = (double)angle / 180 * PI;
/* 長さ分の線を描画 */
for(l = 0; l < length; l++){
/* x座標とy座標を計算 */
x = x1 + l * cos(rad);
y = y1 + l * sin(rad);
/* ビットマップ外の点は描画しない */
if((x >=0 && x < width) && (y >= 0 && y < height)){
/* 座標に対するアドレスを取得 */
p = data + y * width * 3 + x * 3;
/* RGB値を格納 */
p[0] = r;
p[1] = g;
p[2] = b;
}
}
}
注意点は cos
関数と sin
関数の引数に指定する値の単位がラジアンであることです。普通の角度(度数法による角度表記)ではないので注意です。
ラジアンでは 0° 〜 360° を 0 〜 2 π で表します(π は円周率)。
例えば 45° は 45 / 180 * π = 1/ 4 π 、180° は 180 / 180 * π = π といった感じで、ラジアンは角度を 180 で割った数字に π を掛けることで計算することが可能です。
上記ソースコードでは、角度からラジアンへの変換は下記で行なっています。
rad = (double)angle / 180 * PI;
例えば始点を座標 (700
, 300
)・長さ 500
・角度 210
° の斜め線を描画をするためには下記のように drawLineAngleLength
を呼び出しすれば良いです。第4引数で角度、第5引数で長さ、第6引数と第7引数で始点の座標を指定しています。指定している色は緑色になります。
/* 始点を (700, 300) とした長さ 500 、角度 210° の線を描画 */
drawLineAngleLength(
bitmap.data,
bitmap.width,
bitmap.height,
210,
500,
700, 300,
0x00, 0xFF, 0x00
);
スポンサーリンク
斜め線の描画結果(長さと角度指定)
上記のように drawLineAngleLength
関数を呼び出した時に出力される output.png
は下のようになります。
斜め線を描画する(2点指定)
次は2つの点を指定して「斜め線」を描画する方法を紹介します。
斜め線を描画する考え方(2点指定)
では、下の図のように始点 (x1
, y1
) と終点 (x2
, y2
) が指定された時に、この2点間に線を描画する方法について考えていきたいと思います。
結論的には、この2点間の線の描画は、斜め線を描画する(長さと角度指定)と同じ考え方で行うことができます。
これは、2つの点の座標から「2点間の距離」と「2つの点を結ぶ線が x
軸と成す角度(ラジアン)」を算出することが可能だからです。
ですので、与えられた2つの点から「2点間の距離」と「2つの点を結ぶ線が x
軸と成す角度(ラジアン)」を求めてやれば、結局は斜め線を描画する(長さと角度指定)と同様にして線を描画することが可能です。
x = x1 + l * cos θ
y = y1 + l * sin θ
具体的には、距離とラジアンの計算は下記により行うことが可能です。
dx = x2 - x1 dy = y2 - y1 距離 = sqrt(dx * dx + dy * dy) ラジアン = atan2(dy, dx)
dx
と dy
は終点から始点の x
と y
の値を引いたものになります(負の値の場合もあり得る)。
この dx
と dy
を用いれば、2点間の距離をピタゴラスの定理によって計算できます(sqrt
関数は指定された数の平方根を求める関数で、ピタゴラスの定理で距離を求めるために使用しています)。
ラジアンに関しては atan2
関数により算出することできます。
atan2
は引数で指定された “y
方向の距離” と “x
方向の距離” からラジアンを計算して返却してくれる関数です。tan
関数の逆関数的な関数です。
atan2
関数ではなく atan
関数でもラジアンを取得することはできるのですが、atan
関数の場合、返却値は 0 から π の間の値のみです。
下の図のように終点よりも始点の座標の方が大きい場合は方向が逆になってしまい(つまり角度が180度分おかしい)、うまく線が描画できません。
要は atan
関数では始点と終点の位置関係を考慮してくれません(atan
関数の引数は1つなので位置関係を考慮することができない)。
一方で、atan2
関数は始点と終点の位置関係を考慮して(つまり引数で指定する2つの距離の正負を考慮して)ラジアンを求めてくれます。そして、0
から 2 * π
の間の値を返却してくれます(つまり360度全方向)。
今回も始点と終点の位置関係を考慮して角度を求める必要があるため、atan2
を用いてラジアンを算出します。
スポンサーリンク
斜め線を描画する関数(2点指定)
始点と終点を指定して線を描画する関数は下記のようになります。
void drawLineTwoPixels(
unsigned char *data, /* ビットマップデータ */
unsigned int width, /* ビットマップの横幅 */
unsigned int height, /* ビットマップの高さ */
unsigned int xs, /* 線の始点のx座標 */
unsigned int ys, /* 線の始点のy座標 */
unsigned int xe, /* 線の終点のx座標 */
unsigned int ye, /* 線の終点のy座標 */
unsigned char r, /* 赤の輝度値 */
unsigned char g, /* 青の輝度値 */
unsigned char b /* 緑の輝度値 */
){
unsigned int x, y;
unsigned char *p;
int dx, dy;
double rad;
unsigned int length;
unsigned int l;
int sigx, sigy;
/* 始点と終点のx座標とy座標の差を計算 */
dx = xe - xs;
dy = ye - ys;
/* 線の長さを計算 */
length = sqrt(dx * dx + dy * dy);
/* 横軸との成す角を計算 */
rad = atan2(dy, dx);
/* 長さ分の線を描画 */
for(l = 0; l < length; l++){
/* x座標とy座標を計算 */
x = xs + l * cos(rad);
y = ys + l * sin(rad);
/* ビットマップ外の点は描画しない */
if((x >=0 && x < width) && (y >= 0 && y < height)){
/* 座標に対するアドレスを取得 */
p = data + y * width * 3 + x * 3;
/* RGB値を格納 */
p[0] = r;
p[1] = g;
p[2] = b;
}
}
}
例えば始点を座標 (700
, 300
) 、終点を座標 (100
, 100
) とした線を描画をするためには下記のように drawLineTwoPixels
を呼び出しすれば良いです。第4引数と第5引数で始点、第6引数と第7引数で終点の座標を指定しています。指定している色は緑色になります。
drawLineTwoPixels(
bitmap.data,
bitmap.width,
bitmap.height,
700, 300,
100, 100,
0x00, 0xFF, 0x00
);
斜め線の描画結果(2点指定)
上記のように drawLineTwoPixels
で呼び出した時に出力される output.png
は下のようになります。
まとめ
このページでは線を描画する方法の解説と線を描画する関数の紹介を行いました。
線が描画できると、これを利用していろいろな図形を描画することができます。
一例として多角形を描画する方法を下のページで解説していますので、興味のある方は是非読んでみてください。
C言語で多角形を描画する
斜め線を描画する関数(2点指定)の章は書きかけでしょうか?
やまさん
コメントありがとうございます!
まさに書きかけの状態で記事を公開してしまっていました…。
解説も不十分でしたし、ソースコードも途中で終わっていましたし、結果画像もアップ忘れていました…。
大変申し訳ございません。
先程記事を完成させて公開し直しました。ご指摘大変ありがとうございました。
で、よく考えたら atan 関数ではなく atan2 関数で簡単にラジアンを算出できることに気づいたので、atan2 を使用した方法での解説に変更しています。この点はご容赦ください。