C言語で図形を描画する

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

このページではC言語で図形を描画する考え方とC言語でのビットマップデータの扱い方についてまず解説し、その後実際に点を描画する方法とそのプログラムについて解説します。また、最後に色んな図形を描画する方法の解説ページへのリンクの紹介を行なっています。

ビットマップデータについては下のページでも解説しています。

画像データの構造・画素・ビットマップデータについて解説画像データの構造・画素・ビットマップデータについて解説

ビットマップデータについてはこのページでも復習の意味で簡単に解説しますが、ビットマップデータが何か分からない方は先に上のページ を見ていただけると、このページも理解しやすくなります。

図形を描くプログラミングなんてやったことないなぁ

ペイントで簡単にできるしね!

確かにそうだね

あまり使う機会は無いかも…

でも図形を描くプログラムを作成すると画像データやポインタの知識も深まるよ

一番のメリットは結果が絵として確認できるから楽しい点だね

後、昔習った簡単な数学の公式なんかも出てきて懐かしさを感じながらプログラミングを学べるよ

なるほど!

でも数学は苦手なんだよなぁ

簡単な数学しか出てこないので大丈夫!

最初はC言語での図形の描画の考え方について解説していくよー

C言語での図形の描画

C言語での図形の描画といっても、基本的にやることは「メモリ上のデータの値の変更」です。

もっと具体的に言うと、メモリ上に図形を描画するキャンバスとなるビットマップデータを用意し、そのビットマップデータの色を変更することで、その色の点を描画することができます。

さらにその点を色んな座標に描画することで図形を描画を行います。例えばビットマップデータの中心からの距離がある値以下となる座標全てに点を描画すれば、下のような円を描画することができます。

縁を描画した結果

つまり、点さえ描画できるようになれば、あとは点を描画する座標をうまく制御さえしてやれば色んな図形を描画できるようになります(この座標を制御するためにちょっとした数学の知識が必要になります)。なので、C言語で図形を描画するためにはまずビットマップデータについてと、ビットマップデータへの点の描画の方法を理解する必要があります。

このページでは以下でC言語での図形描画に必要になるビットマップデータの知識と点の描画方法について解説します。点の描画を利用してさまざまな図形を描画する方法については別ページで解説しています(他の図形の描画で解説ページへのリンクを紹介しています)。

それでは、まずはそのビットマップデータについて解説していきたいと思います。

ビットマップデータ

まずはビットマップデータについて解説します。

ビットマップデータの座標

本ページで扱うビットマップデータは下の図の通り、左上の座標を (0, 0) とし、横軸の座標は右方向が縦軸の座標は下方向が正方向になります。数学等で習った座標軸とは縦軸の正方向が逆なので注意してください。また、横軸を x 軸、縦軸を y 軸としています。

扱うビットマップデータの座標の説明

基本的にコンピュータで扱う画像データは上図のような座標軸のものが多いです。

ビットマップデータの画素

画像を拡大すると、下の図のように画像が小さな点がたくさん集まって構成されていることが確認できます。このような点は画素と呼ばれます。ビットマップデータもこの画素の集まりとして表現されます。画素数の単位には px が用いられます。

画像が画素の集まりであることを示す図

ここで扱うビットマップデータでは一つの画素を3バイト(24ビット)のデータとして扱います。この3バイトにはそれぞれ下記のように赤緑青(RGB)の情報が格納されます。

画素が赤緑青の情報から構成される様子

この赤・緑・青の情報としては、それぞれ256の色の度合いを表す数字として扱われます。この度合いを輝度値と呼びます。それぞれが256の輝度値を表現できますので、一つの画素は256 x 256 x 256色のパターンのものを表現可能です。

例えば上の図だと、赤緑青の輝度値が下記のように設定されることで、画素のオレンジ色が表現されています。

  • 赤:248
  • 緑:186
  • 青:50

ビットマップデータの作成

ビットマップデータというのは前述の通り、3バイトで表される画素データの集まりです。C言語上では、ビットマップデータの幅 x 高さ x 3バイト分のサイズのメモリを確保し、そのメモリをビットマップデータとして扱って、その上に図形を描画することになります。

ビットマップデータのメモリの確保は下記のように malloc 関数を用いて行うことができます。

data = (unsigned char*)malloc(sizeof(unsigned char) * 幅 * 高さ * 3);

特定の座標の画素へのアクセス

このページではビットマップデータは一次元のデータとして扱います。

ビットマップデータは二次元データのように見えますが、実際のメモリ上には下の図のように行ごとのデータが横並びになることになります。1行目のデータの直後に2行目のデータ、その直後に3行目のデータ…と言った感じでデータが並びます。

ビットマップデータが1次元のデータであることの説明図

また各座標の画素は3バイトで構成されているのでビットマップデータは下の図のように表せます(x = 0 の場合のみ記載)。一つの四角が1バイトを表しています。

1行分の画素データを示す図

したがってビットマップデータの一行分のデータサイズは下記となります。

ビットマップデータの幅 * 3

またこのページでは unsigned char * 型の data という変数名のポインタにビットマップデータの先頭画素 (0, 0) を指させるように各関数を作成しています。もっと正確に言うと data は (0, 0) 座標の赤色のデータを指しています。

ポインタが画素データを指す様子

data は unsigned char * 型なので、data を +1 すると、指すアドレスは unsigned char 型のサイズである1バイト分進むことになります。

ポインタの型に自信の無い方は是非下記ページも読んでみてください。

ポインタの型の解説ページのアイキャッチC言語のポインタの型の意味は?

つまり、data に加算する値に応じて指す先が下記のように変化します。

  • data +1 : (0, 0) 座標の緑色のデータ
  • data + 2: (0, 0) 座標の青色のデータ
  • data + 3: (1, 0) 座標の赤色のデータ
  • data + 4: (1, 0) 座標の緑色のデータ
  • 以下略

ここまでを踏まえ、一般的に書くと、ある座標 (x, y) の画素は下記の式で求まるポインタ p にアクセスすることで、取得や変更することが可能です。

p = data + y * ビットマップデータの幅 * 3 + x * 3

さらに座標 (x, y) の画素の赤緑青のデータには、上で求めた p を用いて次のようにアクセス可能です。

  • 赤色:*p
  • 緑色:*(p + 1)
  • 青色:*(p + 2)

配列として扱うのであれば下記でアクセスすることが可能です。

  • 赤色:p[0]
  • 緑色:p[1]
  • 青色:p[2]

つまり、上記でアクセスするデータの値を変更することで、その座標の色を自由に変更することができます。

さらに、それを図形の一部となる座標全てに対して行うことで好きな色の図形を描画することができます。

ビットマップデータのプレビュー

せっかく図形を描くのだから画像で結果を確認したいですよね?でも、ビットマップデータはそのままの形式では使用するプレビューアプリによっては表示することができないことがあります(PhotoShop などのソフトだと表示可能)。

なので、もしプレビューアプリがビットマップデータが表示できないのであれば、JPEG や PNG などの形式に変換してから表示してやる必要があります。

このページで紹介するプログラムでは libpng を使った PNG 形式の変換を行ってからファイル書き出しを行うようにしています。同様に libpng を使用して PNG 形式への変換を行いたい方は是非下記ページを参考にしてください。libpng と下記ページのソースコードを使えば、関数一つ呼ぶだけで簡単に PNG 形式に変換できます。

libpngのインストールとC言語での使用方法・使用例libpngのインストールとC言語での使用方法・使用例

それでは実際に図形を描画していきましょう!

スポンサーリンク

点を描画する

まずはここまでのおさらいの意味も含めて「点」を描画したいと思います。

点を描画する考え方

考え方としては単純で、ある座標 (x1, y1) の画素に赤緑青の輝度値を格納すれば良いだけです。

点を描画する時のイメージ

さらに特定の座標の画素へのアクセスで解説したように、座標 (x1, y1) の赤緑青の輝度値の格納は下記のように行うことが可能です(ビットマップデータの幅を width, 赤緑青の輝度値をそれぞれ r, g, b としています)。

p = data + y1 * width * 3 + x1 * 3;
p[0] = r; /* 赤の輝度値格納 */
p[1] = g; /* 緑の輝度値格納 */
p[2] = b; /* 青の輝度値格納 */

点を描画するために必要なのはこれだけです。

点を描画する関数

ソースコード全体としては下記のようになります。

main.c
#include "myPng.h"
#include <math.h>

#define PI 3.14

void drawDot(
  unsigned char *data, /* ビットマップデータ */
  unsigned int width, /* ビットマップの横幅 */
  unsigned int height, /* ビットマップの高さ */
  unsigned int x1, /* 始点のx座標 */
  unsigned int y1, /* 始点のy座標 */
  unsigned char r, /* 赤の輝度値 */
  unsigned char g, /* 青の輝度値 */
  unsigned char b /* 緑の輝度値 */
){
  unsigned char *p;
  
  p = data + y1 * width * 3 + x1 * 3;
  p[0] = r;
  p[1] = g;
  p[2] = b;
}

int main(void){
  BITMAPDATA_t bitmap;
  
  /* 作成するビットマップデータの情報格納 */
  bitmap.width = 800;
  bitmap.height = 400;
  bitmap.ch = 3;

  /* ビットマップデータのメモリ確保 */
  bitmap.data = 
    (unsigned char*)malloc(sizeof(unsigned char) * bitmap.width * bitmap.height * bitmap.ch);
  if(bitmap.data == NULL){
    printf("malloc error\n");
    return -1;
  }

  /* ビットマップデータの背景を白にする */
  memset(bitmap.data, 0xFF, bitmap.width * bitmap.height * bitmap.ch);

  /* (10, 20)に点を描画 */
  drawDot(
    bitmap.data,
    bitmap.width,
    bitmap.height,
    20, 10, /* 点を描画する座標 */
    0xFF, 0x00, 0x00 /* 赤色 */
  );

  /* PNGに変換してファイル保存 */
  if(pngFileEncodeWrite(&bitmap, "output.png") == -1){
    freeBitmapData(&bitmap);
    return -1;
  }

  freeBitmapData(&bitmap);

  return 0;
}
MEMO

math.h のインクルードと PI の定義は、点を描画する場合は不要ですが、他のページで紹介する関数では必要になりますので、このソースコードにも同様のインクルードと定義を行なっています。

点の描画に関しては drawDot 関数で実行しています。引数として座標 (x1, y2) を受け取り、その座標のアドレスに対して、こちらも同様に引数として指定された r, g, b を格納しています。

一方 main 関数では drawDot 関数に対して、下記のように引数を指定して呼び出しを行なっています。

drawDot(
  bitmap.data,
  bitmap.width,
  bitmap.height,
  20, 10, /* 点を描画する座標 */
  0xFF, 0x00, 0x00 /* 赤色 */
);

各引数は下記を表すデータになります。

  • 第1引数:ビットマップデータの先頭アドレス
  • 第2引数:ビットマップデータの幅
  • 第3引数:ビットマップデータの高さ
  • 第4引数:点を描画する座標 x
  • 第5引数:点を描画する関数 y
  • 第6引数〜第8引数:各色の輝度値

なので、main 関数から drawDot 関数に指定したビットマップデータの (20, 10) 座標の色を赤色に塗るように依頼していることになります。

最初なので main 関数の drawDot 呼び出し部分以外についても説明しておきます。main 関数としては、下記を行なっています。

  1. ビットマップデータの情報を BITMAPDATA_t 構造体に格納
    (幅 800 px、高さ 400 px に設定)
  2. ビットマップデータ分のメモリを確保
  3. ビットマップデータのすべての画素の色を白に設定
    (赤:255,、緑:255、青:255に設定すると白になる)
  4. 描画を行う関数を呼び出し
  5. ビットマップデータを PNG に変換してファイル保存
  6. ビットマップデータを解放

1. に記載の BITMAPDATA_T と 5. の PNG 変換については下記ページで紹介しているものを使用していますので詳細を知りたい方はこちらを参考にしてください。

libpngのインストールとC言語での使用方法・使用例libpngのインストールとC言語での使用方法・使用例

続いてコンパイル方法についても紹介しておきます。上記のソースコードを main.c とした場合、コマンドライン(例えばターミナルアプリ)から下記コマンドでコンパイルと実行可能ファイル生成を行うことができます。

gcc main.c myPng.c -lpng -o main.exe

myPng.c をコンパイルしたり、-lpng オプションをつけたりしているのは pngFileEncodeWrite を使用するためです。

上記コマンドで main.exe が実行されますので、main.exe を単に実行すれば画像 output.png が出力されます。例えばコマンドラインで実行するためには下記を実行すれば良いです。

./main.exe

点の描画結果

output.png をプレビューソフトで開いてみましょう。点を描画した (20, 10) 座標の色が赤くなっているはずです。点なので、かなり小さいので拡大して表示する必要があります。

点を描画した結果

かなり地味な結果になりますが、この点の描画ができれば、あとはこの応用で(座標の指定の仕方を工夫するだけで)いろんな図形を描画できるようになります。

他の図形の描画

図形の描画に興味を持ってくださった方は、点の描画を利用した線の描画について下記で解説していますので、是非こちらも読んでみていただければと思います。

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

線を描画できれば三角形や四角形等の多角形も描画可能になります。

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

また点の描画ができれば円や楕円の描画も簡単に行えます。こちらについては下のページで解説しています。

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

まとめ

このページでは、C言語で図形を描画するために必要な知識、特にビットマップデータについて解説し、その後「点」の描画を行う方法とその関数の紹介を行いました。

図形描画はC言語の基本的な力がつくだけでなく、数学を使って楽しくプログラミングが学べるのでオススメです。是非興味を持たれた方は他の図形の描画も試してみてください!

コメントを残す

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