ppm画像に描画するプログラムについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
市松

ppm画像に描画するプログラムについて

#1

投稿記事 by 市松 » 2週間前

[1.1] 「ppm画像に対角線を描くプログラムと円を描くプログラムを作成せよ」という課題です。
[1.2] 例として次のプログラムを授業で扱いました

コード:

#include <stdio.h>

#define WIDTH 320 //画像の横のサイズ
#define HEIGHT 240 //画像の縦のサイズ

int main(){
  unsigned char pixels[WIDTH*HEIGHT*3];
  int i, j;
  FILE *fp;

  //全ピクセルを単一色に塗りつぶす
  for(j=0; j<HEIGHT; j++){
    for(i=0; i<WIDTH; i++){
      pixels[(j * WIDTH + i) * 3] = 100;
      pixels[(j * WIDTH + i) * 3 + 1] = 155;
      pixels[(j * WIDTH + i) * 3 + 2] = 255;
    }
  }

    for(j=0; j<HEIGHT; j++){
      pixels[(j * WIDTH + j) * 3] = 0;
      pixels[(j * WIDTH + j) * 3 + 1] = 0;
      pixels[(j * WIDTH + j) * 3 + 2] = 0;
      pixels[(j * WIDTH + (HEIGHT-j+1)) * 3] = 0;
      pixels[(j * WIDTH + (HEIGHT-j+1)) * 3 + 1] = 0;
      pixels[(j * WIDTH + (HEIGHT-j+1)) * 3 + 2] = 0;
    }

  //ppm形式で画像を書き出す
  fp = fopen("hoge01.ppm", "wb"); //ファイルをバイナリーモードでオープン
  if(fp != NULL){
    fprintf(fp, "P6¥n%d %d¥n255¥n", WIDTH, HEIGHT); //マジックナンバー,幅,高さを書き出す
    fwrite(pixels, 1, WIDTH * HEIGHT * 3, fp); //画像データを書き出す
    fclose(fp);
  }

  return 0;
} 
 [1.4] 課題はもちろんですが、上記のプログラムの最初のfor文のpixels[]の中身と、3つ目のfor文でなぜ線が描けるのかさっぱりわからない状況です。

[2] 環境  
 [2.1] OS : Linux
 [2.2] コンパイラ名 :gcc (ファイル名) -o (ファイル名)

[3] その他
 ・C言語に関してポインタや配列も理解できていない素人です…提出期限が7月9日で、プログラミングに関して頼れる知り合いもいないのでアドバイスを頂けると嬉しいです。

アバター
usao
記事: 1406
登録日時: 5年前

Re: ppm画像に描画するプログラムについて

#2

投稿記事 by usao » 2週間前

(1)ppmのフォーマットがわかっているのでしょうか
 →わからないならググる
(2)pixels[]とppmフォーマットとの間の関係を把握しているのでしょうか
 →画像を書き出すコードとフォーマットとの対比を見れば,pixels[]の各要素が何の情報なのかがわかるハズ
(3)上記まで把握すれば,画像データの画素位置(x,y)の色を(R,G,B)にしたいとき,pixels[]のどこをどう変更すれば良いのかが自明なハズ.
すなわち,forブロック内でのpixels[XXX]のXXXの意味がわかるハズ.
(例えば,(x,y)の緑成分を128にしたい場合,pixels[XXX] = 128; のXXXをいくつにすべきか)

かずま

Re: ppm画像に描画するプログラムについて

#3

投稿記事 by かずま » 2週間前

1次元配列ではなく、3次元配列にすると分かりやすくなると思います。

コード:

#include <stdio.h>

#define WIDTH  320  // 画像の横のサイズ
#define HEIGHT 240  // 画像の縦のサイズ
#define R 0
#define G 1
#define B 2

int main(void)
{
	unsigned char pixels[HEIGHT][WIDTH][3];

	for (int j = 0; j < HEIGHT; j++) {    // 全ピクセルを単一色に塗りつぶす
		for (int i = 0; i < WIDTH; i++) {
			pixels[j][i][R] = 100;
			pixels[j][i][G] = 155;
			pixels[j][i][B] = 255;
		}
	}
	for (int j = 0; j < HEIGHT; j++) {
		pixels[j][j][R] = 0;               // 左上から右下への斜線
		pixels[j][j][G] = 0;               //   色は黒
		pixels[j][j][B] = 0;
		pixels[j][HEIGHT - j + 1][R] = 0;  // 右上から左下への斜線
		pixels[j][HEIGHT - j + 1][G] = 0;  //   色は黒
		pixels[j][HEIGHT - j + 1][B] = 0;  //   2ピクセル右にずれている
	}
	//ppm形式で画像を書き出す
	FILE *fp = fopen("hoge01.ppm", "wb"); // ファイルをバイナリーモードでオープン
	if (fp != NULL) {
		fprintf(fp, "P6\n%d %d\n255\n", WIDTH, HEIGHT);
		                              // マジックナンバー,幅,高さ, 色の最大値
		fwrite(pixels, 1, sizeof pixels, fp);  // 画像データ
		fclose(fp);
	}
	return 0;
}
どこが分からないのかを具体的に質問してもらえれば、
もっと丁寧に説明します。

おまけ
円を描くならこんなもんでしょうか?

コード:

#include <math.h>  // sqrt

#define PLOT(p, x, y, r, g, b) \
	(p[y][x][R] = r,  p[y][x][G] = g,  p[y][x][B] = b)

	const int cx = WIDTH / 2, cy = HEIGHT / 2; // 中心の座標
	const int r = cy - 1;                      // 円の半径(少し小さめ)
	const int R = r * sqrt(2) / 2;  // 八分の一円を書くのに必要な x の最大値
	for (int x = 0; x <= R; x++) {
		int y = sqrt(r * r - x * x) + 0.5;
		PLOT(pixels, cx + x, cy + y, 0, 0, 0);
		PLOT(pixels, cx + x, cy - y, 0, 0, 0);
		PLOT(pixels, cx - x, cy + y, 0, 0, 0);
		PLOT(pixels, cx - x, cy - y, 0, 0, 0);
		PLOT(pixels, cx + y, cy + x, 0, 0, 0);
		PLOT(pixels, cx - y, cy + x, 0, 0, 0);
		PLOT(pixels, cx + y, cy - x, 0, 0, 0);
		PLOT(pixels, cx - y, cy - x, 0, 0, 0);
	}

かずま

Re: ppm画像に描画するプログラムについて

#4

投稿記事 by かずま » 2週間前

かずま さんが書きました:
2週間前
円を描くならこんなもんでしょうか?

コード:

	const int R = r * sqrt(2) / 2;  // 八分の一円を書くのに必要な x の最大値
	for (int x = 0; x <= R; x++) {
#define R 0 なので、R は変数として使用できません。
次のように修正します。

コード:

	const int M = r * sqrt(2) / 2;  // 八分の一円を書くのに必要な x の最大値
	for (int x = 0; x <= M; x++) {

市松
記事: 3
登録日時: 2週間前

Re: ppm画像に描画するプログラムについて

#5

投稿記事 by 市松 » 2週間前

>>usaoさん
アドバイスありがとうございます!色々検索したり試すことができました。

>>かずまさん
ものすごく分かりやすくなりました…ありがとうございます。
ただ今回の「320×240のサイズの画像の対角線を描くプログラム」を作ろうとする場合どのように変えたらいいかよく分からないです…

かずま

Re: ppm画像に描画するプログラムについて

#6

投稿記事 by かずま » 2週間前

1. #1 のプログラムは理解しましたか?
2. #3 の 3次元配列版プログラムは理解しましたか?
3. #3 の円を描くプログラムは理解しましたか?
4. 自宅に Linux の実行環境がありますか?
5. PPM画像のビューアは何を使っていますか? GIMPですか?

以上の質問に全部答えてもらうと、対角線を描くプログラムについて説明します。
分かりませんではなく、どことどこが分からないかを具体的に回答してください。

おまけ

コード:

char v[240 * 320 * 3];  // 1次元配列
char a[240][320][3];    // 3次元配列
v[(y * 320 + x) * 3 + c] は、a[y][x][c] と同じ位置。

市松
記事: 3
登録日時: 2週間前

Re: ppm画像に描画するプログラムについて

#7

投稿記事 by 市松 » 2週間前

1.2.#1と#3は理解できました。
3.PLOTについて検索しましたが、円のプログラムのPLOT(pixels, cx + x, cy + y, 0, 0, 0);の()の中身がそれぞれどういう意味なのか分からないです
4.自分のパソコンにインストールしたVirtualBoxの仮想PCでLinuxを実行しています。
5.PPM画像は、プログラム実行後にディレクトリに保存されたファイルを開く形で確認しています。

対角線を描くプログラムについては、画像のサイズからy=(240/320)xを踏まえて考えればいいというところまでは分かりますが、for文をどう変えればいいのかが分からないです。

かずま

Re: ppm画像に描画するプログラムについて

#8

投稿記事 by かずま » 2週間前

市松 さんが書きました:
2週間前
3.PLOTについて検索しましたが、円のプログラムのPLOT(pixels, cx + x, cy + y, 0, 0, 0);の()の中身がそれぞれどういう意味なのか分からないです
#define を調べないと解決しません。

コード:

    PLOT(pixels, cx + x, cy + y, 0, 0, 0);
が展開されて、
    (pixels[cy + y][cx + x][R] = 0, pixels[cy + y][cx + x][G] = 0, pixels[cy + y][cx + x][B] = 0);
になります。
市松 さんが書きました:
2週間前
5.PPM画像は、プログラム実行後にディレクトリに保存されたファイルを開く形で確認しています。
どんなツールで開いているんですか?
P6 の PPMファイルはバイナリファイルなので、テキストエディタでは
開けませんし、バイナリエディタで開いても、画像は見えませんよ。
市松 さんが書きました:
2週間前
対角線を描くプログラムについては、画像のサイズからy=(240/320)xを踏まえて考えればいいというところまでは分かりますが、for文をどう変えればいいのかが分からないです。

コード:

    → x
 ↓  ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □
 y  □ ■ ■ □ □ □ □ □ □ □ □ □ □ □ □ □
    □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □
    □ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □
    □ □ □ □ □ ■ ■ □ □ □ □ □ □ □ □ □
    □ □ □ □ □ □ □ ■ □ □ □ □ □ □ □ □ 
    □ □ □ □ □ □ □ □ ■ □ □ □ □ □ □ □ 
    □ □ □ □ □ □ □ □ □ ■ ■ □ □ □ □ □ 
    □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ □ 
    □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ 
    □ □ □ □ □ □ □ □ □ □ □ □ □ ■ ■ □ 
    □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ 
幅320、高さ240 で各点の座標(x,y)は、
左上(0,0)、右下(319,239) です。

x が 0 から 319 まで変化すると
y が 0 から 239 まで変化する比例関係なので、
y = x * 239 / 319 となります。

整数の割り算は切り捨てとなるので、浮動小数点の計算にして、
0.5 を足してから整数に変換すると、四捨五入になります。

コード:

	for (int i = 0; i < WIDTH; i++) {
		int j = i * (HEIGHT - 1.0) / (WIDTH - 1.0) + 0.5;
		pixels[j][i][R] = 0;  // 左上から右下への対角線
		pixels[j][i][G] = 0;  //   色は黒
		pixels[j][i][B] = 0;
		j = HEIGHT - 1 - j;
		pixels[j][i][R] = 0;  // 左下から右上への対角線
		pixels[j][i][G] = 0;  //   色は黒
		pixels[j][i][B] = 0;
	}

かずま

Re: ppm画像に描画するプログラムについて

#9

投稿記事 by かずま » 2週間前

a = 239.0 / 319.0、y = a * x とすると、
対角線の座標は、
 (0, 0.000) (1, 0.749) (2, 1.498) ... (318, 238.250) (319, 239.000)
これを四捨五入で
 (0, 0) (1, 1), (2, 1) (318, 238) (319, 239)
にすると、カクカクした折れ線になってしまいます。

(1, 0.749) を黒くしたいんだったら、
(1, 0) の現在の輝度を 74.9% にし、
(1, 1) の現在の輝度を 25.1% にしたら、
2つの点を合わせて、(1, 0749) を黒にしたことになるでしょう。
画像を見ると、きれいな線に見えます。

コード:

	double a = (HEIGHT - 1.0) / (WIDTH - 1.0);
	for (int i = 0; i < WIDTH; i++) {
		double x = i, y = a * x;
		int j = y;
		double f = y - j;
		for (int k = 0; k < 3; k++) {  // RGB 3成分
			double c = pixels[j][i][k];
			pixels[j][i][k] = c * f;
			if (j < HEIGHT - 1) pixels[j+1][i][k] = c * (1 - f);
		}
	}
(i, j) と (i, j+1) が同じ背景色と仮定しています。
異なる場合は、もう少し細工が必要でしょう。

アバター
usao
記事: 1406
登録日時: 5年前

Re: ppm画像に描画するプログラムについて

#10

投稿記事 by usao » 2週間前

期限が今日ですね.

(ブレゼンハム的なアルゴリズムをどうこうせねばならない系の課題趣旨でないならば,ですが)
とりあえず(非効率だが,わかりやすい方法として)
「320x240個の画素を全走査し,各画素位置(x,y)について,そこに着色するか否かを判定する」とかじゃダメなんですかね.
描画したいのが対角線だろうが円だろうが,その図形と座標(x,y)との間の「距離(のようなもの)」の計算さえできるならば,着色するか否かの判定は書けますよね.
(例えば,図形の式を f(x,y)=0 としたとき,f(x,y)の絶対値とか二乗値だとかを評価関数にすりゃいい.線の太さもアンチエイリアスも思いのままですね.)

市松
記事: 3
登録日時: 2週間前

Re: ppm画像に描画するプログラムについて

#11

投稿記事 by 市松 » 1週間前

#define見落としてました…すみません…

調べてみましたが、どのツールか分かりませんでした。ppm画像はスクリーンショットのような感じで確認しています。ディレクトリのppmファイルをクリックすると右のように表示されます。

対角線について非常に丁寧な説明ありがとうございます。
とても分かりやすくて有難いです…

「320x240個の画素を全走査し,…」という方法も試してみます、本当にありがとうございます!
添付ファイル
新規キャンバス1.jpg
新規キャンバス1.jpg (55.7 KiB) 閲覧数: 80 回

返信

“C言語何でも質問掲示板” へ戻る