C言語で線を描画する

線描画の解説ページのアイキャッチ

このページではC言語で「線」を描画する方法について解説します。

線を描画するために必要な「ビットマップデータ」や「点の描画方法」については下のページで解説していますので、まだ読んでない方は事前に下のページを読むことをオススメします。

図形描画の解説ページのアイキャッチC言語で図形を描画する

線の描画は「座標を移動させながら連続的に点を描画する」ことで実現しますので、特に点の描画については上のページで理解しておいていただけると本ページの内容も理解しやすくなると思います。

横線を描画する

まずは「横線」の描画を行います。点の描画ができれば横線の描画も簡単です。

横線を描画する考え方

横線は「横方向に点を連続して描画したもの」と考えることができます。なので、横方向に向かって座標を移動しながら、各座標の画素に色の情報を格納してやることで横線が引けます。

横方向に座標を移動させるためには、縦方向の座標 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 /* 赤色 */
);
main 関数について

上記の drawHorizontalLine 関数のように、このページに載せている関数は、下記ページで紹介した main 関数から呼び出すのがオススメです。

C言語で図形を描画する

また PI の定義と math.h のインクルードも必要になりますので注意してください。

libpng をインストールすれば PNG 出力して画像のプレビューも簡単にできます。

このページで紹介する描画結果も上記の main 関数で出力した outpnt.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) の距離を l として考えてみましょう。

斜め線を描画するときの各変数の関係

図を見ると分かるように、l を 0 から length – 1 まで増加させながら変化させ、それに対応した座標 (x, y) に点を描画していくことで斜め線を描画することが可能です。

この座標 (x, y) の x と y は距離 l を用いて下記の式で計算することができます(角度は θ で表記)。

  • 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 関数の引数に指定する値の単位がラジアンであることです。普通の角度(度数法による角度表記)ではないので注意です。角度からラジアンへの変換は下記で行なっています。

  rad = (double)angle / 180 * PI;

ラジアンでは 0° 〜 360° を 0 〜 2 π で表します(π は円周率)。例えば 45° は 45 / 180  * π = 1/ 4 π 、180° は 180 / 180 * π = π といった感じで、ラジアンは角度を 180 で割った数字に π を掛けることで計算することが可能です。

例えば始点を座標 (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つの点の座標を結ぶ線が x 軸と成す角度(ラジアン)を算出することが可能です。

2つの点から角度と長さを求めるイメージ

ですので、結局は斜め線を描画する(長さと角度指定)と同様にして線を描画することが可能です。

  • x = x1 + l cos θ
  • y = y1 + l sin θ

具体的には長さとラジアンの計算は下記により行うことが可能です。

dx = x2 - x1
dy = y2 - y1
長さ = sqrt(dx * dx + dy * dy)
ラジアン = atan(dy / dy)

dx と dy は終点から始点の x と y の値を引いたものになります。またこの dx と dy を用いれば、長さをピタゴラスの定理によって計算できます。sqrt 関数は指定された数の平方根を求める関数です。

ラジアンに関しては atan 関数を使用することで計算できます。atan は指定された値の逆正接をラジアン返却する関数です。tan 関数の逆関数的な関数です。

ただし、atan 関数が返却するのは 0 から π の間の値のみです。ですので、下記のように終点よりも始点の座標の方が大きい場合は方向が逆になってしまい、うまく線が描画できません。

角度産出がおかしく線がうまく描けない様子

これは線を描画する際に下記のように処理することで解決できます。

  1. dx と dy 算出時に大きい方から小さい方の値を引くようにする(値が必ず正になる)
  2. atan(dx / dy) でラジアン算出(1. により必ず 0 から π / 2 の値になる)
  3. 始点と終点の x と y の大小関係に応じて、座標 x と y それぞれを始点から正 or 負どちらの方向に移動させながら線を描画するかを決定
  4. 描画を行うときの座標計算時に上記方向を考慮して x と y を算出

まず、1. と 2. で atan 関数から取得するラジアンを 0 から π / 2 に制限します。イメージとしては終点を始点に対して線対象の位置に移動させてからラジアンを求めることでこの制限を行う感じです。

x2>x1になるように点を移動させた様子

その後、3. で自分で線を描画するために、(もともとの)始点と終点の位置関係より、点を描画する座標を移動させる方向を考えます。

 

 

 

図を変える、下の図で x とyの方向も表現

点を移動させる方向の説明図

具体的には、始点と終点の x と y の大小関係から点の座標の x と y をそれぞれどの方向(正 or 負)に移動させるかを決めます。

  • 赤色の方向:x は正方向、y も正方向
  • 緑色の方向:x は正方向、y は負方向
  • 青色の方向:x は負方向、y も負方向
  • 黄色の方向:x は負方向、y は正方向

さらに実際に点を描画する座標を、3. で決めた方向を、例えば下記のように考慮して計算します。

  if(x2 >= x1){
    sigx = 1;
  } else {
    sigx = -1;
  }
  if(y2 >= y1){
    sigy = 1;
  } else {
    sigy = -1;
  }
  x = x1 + l * cos(rad) * sigx;
  y = y1 + l * sin(rad) * sigy;

sigx と sigy は x と y をそれぞれどの方向(正 or 負)に移動させながら点を描画するかを決めるための符号を格納する変数になります。rad は 2. で求めたラジアンになります。

斜め線を描画する関数(2点指定)

始点と終点を指定して線を描画する関数は下記のようになります。

void drawLineTwoPixels(
  unsigned char *data, /* ビットマップデータ */
  unsigned int width, /* ビットマップの横幅 */
  unsigned int height, /* ビットマップの高さ */
  unsigned int x1, /* 線の始点のx座標 */
  unsigned int y1, /* 線の始点のy座標 */
  unsigned int x2, /* 線の終点のx座標 */
  unsigned int y2, /* 線の終点のy座標 */
  unsigned char r, /* 赤の輝度値 */
  unsigned char g, /* 青の輝度値 */
  unsigned char b /* 緑の輝度値 */
){
  unsigned int x, y;
  unsigned int dx, dy;
  unsigned char *p;
  unsigned int length;
  unsigned int l;
  int sigx, sigy;
  double rad;

  /* 始点と終点のx座標とy座標それぞれの差を計算 */
  if(x2 >= x1){
    dx = x2 - x1;
  } else {
    dx = x1 - x2;
  }
  if(y2 >= y1){
    dy = y2 - y1;
  } else {
    dy = y1 - y2;
  }
  
  /* 線の長さを計算 */
  length = sqrt(dx * dx + dy * dy);

  /* 横軸との成す角を計算 */
  if(dx != 0){
    rad = atan((double)dy / (double)dx);
  } else {
    rad = PI / 2;
  }

  if(x2 >= x1){
    sigx = 1;
  } else {
    sigx = -1;
  }
  if(y2 >= y1){
    sigy = 1;
  } else {
    sigy = -1;
  }

例えば始点を座標 (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言語で多角形を描画する

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です