C言語で図形を描画する

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

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

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

画像データの解説ページアイキャッチ画像データの構造・画素・ビットマップデータについて解説

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

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

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

確かにそうだね

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

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

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

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

なるほど!

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

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

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

C言語での図形の描画

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

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

さらにその点を色んな座標に描画することで図形を描画を行います。

例えば、「中心からの距離が “閾値以下” となる座標」全てに赤色の点を描画すれば、結果的に下記のような赤色の縁を描画するようなことができます。

縁を描画した結果

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

なので、C言語で図形を描画するためには、まずビットマップデータについてとビットマップデータへの点の描画の方法を理解する必要があります。

これらを理解していただくために、このページではC言語での図形描画に必要になるビットマップデータの知識と点の描画方法について解説ていきたいと思います。

実際に点の描画を利用してさまざまな図形を描画する方法については別ページで解説しています(他の図形の描画で解説ページへのリンクを紹介しています)。

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

ビットマップデータ

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

スポンサーリンク

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

本ページで扱うビットマップデータは下の図の通り、左上の座標を原点0,0) とし、右方向が横軸の正方向下方向が縦軸の正方向になります。

数学等で習った座標軸とは縦軸の正方向が逆なので注意してください。また、横軸を x 軸、縦軸を y 軸として説明していきます。

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

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

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

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

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

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

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

この赤・緑・青の情報としては、それぞれ256の色の度合いを表す数字として扱われます(1バイトで表現できる数値は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次元データのように見えますが、実際のメモリ上には下の図のように行ごとのデータが横並びになって配置されています。1行目のデータの直後に2行目のデータ、その直後に3行目のデータ…と言った感じでデータが並びます。

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

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

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

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

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

また、このページでは unsigned char * 型の data という変数名のポインタにビットマップデータの先頭画素 、つまり原点 (0, 0) の画素を指させるように各関数を作成しています。

もっと正確に言うと data は (0, 0) 座標の赤色のデータを指しています。

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

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

MEMO

ポインタへの加減算で具体的にアドレスがいくつ進むかはポインタの型によって決まります

この辺りは下記ページで解説していますので、詳しく知りたい方は読んでみてください

ポインタの型の解説ページアイキャッチ【C言語】ポインタの「型」について解説

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

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

要は、「原点の赤色のデータ」を指している data に対して加算を行うことで、任意の座標の画素の任意の色を指すことができます。

具体的には、座標 (x, y) のアドレスは下記の式で data に対して加算を行えば求めることができます。このアドレスをポインタ p に格納しているので、このポインタ p が座標 (x, y) の赤色のデータを指すことになります。

座標(x,y)の画素のアドレス計算
p = data + y * ビットマップデータの幅 * 3 + x * 3

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

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

配列のように添字を指定してアクセスするのであれば、下記のようにアクセスします。

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

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

例えば、座標 (x, y) の画素を赤色にする場合(赤の輝度値:255、青の輝度値:0、緑の輝度値:0)、下記のように処理を行います。

画素を赤色に変更
p = data + y * ビットマップデータの幅 * 3 + x * 3;
p[0] = 255; /* 赤の輝度値格納 */
p[1] = 0; /* 緑の輝度値格納 */
p[2] = 0; /* 青の輝度値格納 */

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

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

せっかく図形を描くのだから画像で結果を確認したいですよね?

でも、ビットマップデータはそのままの形式では使用するプレビューアプリによっては表示することができないことがあります(PhotoShop などのソフトだと表示可能)。

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

このページで紹介するプログラムでは libpng を使った PNG 形式の変換を行ってからファイル書き出しを行うようにしています。

同様に libpng を使用して PNG 形式への変換を行いたい方は是非下記ページを参考にしてください。libpng と下記ページのソースコードを使えば、関数一つ呼ぶだけで簡単に PNG 形式に変換できます。

libpngの使い方解説ページアイキャッチ【C言語】libpngのインストールと使用方法・使用例

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

点を描画する

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

スポンサーリンク

点を描画する考え方

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

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

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

座標(x1,y1)の画素変更
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関数の呼び出し
drawDot(
  bitmap.data,
  bitmap.width,
  bitmap.height,
  20, 10, /* 点を描画する座標 */
  0xFF, 0x00, 0x00 /* 赤色 */
);

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

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

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

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

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

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

libpngの使い方解説ページアイキャッチ【C言語】libpngのインストールと使用方法・使用例

続いてコンパイル方法についても紹介しておきます。上記のソースコードを 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言語の基本的な力がつくだけでなく、数学を使って楽しくプログラミングが学べるのでオススメです。是非興味を持たれた方は他の図形の描画も試してみてください!

コメントを残す

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