プログラミング入門(7)


Java では Java2D という API (Application Program Interafce、 =特定の機能をプログラムの中から利用する方法)が用意されています。 Java2Dはグラフィックスを扱うためのAPIで、Java2D を使うとJavaで 記述したプログラムから簡単にグラフィックスを操作することができます。

さて今回は、簡単なグラフィックスを扱うプログラムを用いて 2重ループを学習することにしましょう。


グラフィックス

ウィンドウと座標系


コンピュータの画面上に、『ドット』または『ピクセル』と 呼ばれる点が縦横方向にずらっと並んでいます。 このドットがいろいろな色に発光することで、画面上に 絵や文字が表示されるわけです。 画面の座標系は、左上を原点にして、水平右方向にx軸、垂直下方向に y軸を設定するのが一般的です。

ウィンドウは特定のアプリケーションで使うために用意した、 画面上の矩形(くけい←「四角形」という意味です)領域です。 やはり、左上を原点にして、水平右方向にx軸、垂直下方向に y軸を設定した座標系で表現します。

ドットの色は、3原色(赤、緑、青)の組合せで表現できます。 各原色は(8bitで表される0〜255の整数値で)256段階の明るさを 表現できるとすると、色はその組合せで

    256 × 256 × 256 = 16777216 (通り)
    (28 × 28 × 28 = 224)
を表現できる(区別できる)ことになります。

「明るさ」を数値で表現して、0を「最も暗い状態」、255を「最も明るい状態」 とすると、たとえば

	(R,G,B)=(255,0,0) は赤、
	(R,G,B)=(128,0,0) は暗い赤、
	(R,G,B)=(0,255,0) は緑、
	(R,G,B)=(255,255,0) は黄色
を表します。代表的な色の名前とRGB値をいくつか次の表に挙げておきます。

RGB名称
25500 赤 (red)
02550 緑 (green)
00255 青 (blue)
255255255 白 (white)
000 黒 (black)
RGB名称
2552550 黄色 (yellow)
0255255 シアン (cyan)
2550255 マゼンタ (magenta)
190190190 gray (灰色)
255192203 ピンク
RGB名称
1654242 茶色 (brown)
1281280 暗い黄色 (dark yellow)
2551650 オレンジ (orange)
16032240 パープル (purple)
15420550 黄緑 (yellow green)

自由に3原色を組み合わせて、色を作り出すには http://ynitta.com/ColorEditor/ などを参照して下さい。


ToyGraphicsクラス

今回の授業では ToyGraphics というクラスを使って、Java 言語で書いたプログラムからグラフィックを扱ってみます。

ただし、今回の授業で使う ToyGraphics というクラス は、 Javaに標準で用意されているクラスではないことに注意して下さい。 ToyGraphics クラスは java言語でグラフィックを扱う標準のAPIである Java2Dを使って作られたクラスではありますが、ToyGraphics.java というファイルがなければ、その定義も存在しないので ToyGraphics クラスは 使えません。

また、今回 ToyGraphics クラスを使うのは「繰り返し」を行う プログラムの動きを見るための道具としてであって、 本格的に Java2Dを学ぶためではないことに注意して下さい。

Java2Dは本来、線を描いたり、ある領域を塗りつぶしたりといった 複雑な操作ができます。が、今回はわざと 「指定した座標のドットを指定した色にする」 という非常に基本的な機能だけしか使いません。 「グラフィックスを描くのに必要な複雑な操作を自分で少しずつ 作成してみる」ことが、Javaのプログラミングを学習するのに 効果的なので単に利用するだけなのです。

ToyGraphicsクラスを使ってみるには

  1. まず http://ynitta.com/lec/2dcg/c7/ToyGraphics.java から ToyGraphics.java を ダウンロードして、「マイコンピュータの中の pro というフォルダ」 の中に保存して下さい。

    ToyGraphics クラスを使っているプログラムをコンパイルすると、 同時に同じフォルダ内にある ToyGraphics.java もコンパイルされます。

  2. ToyGraphicsクラスを利用するプログラムを書きましょう。
    GSample01.java (横線を描く)
    public class GSample01 {
        public static void main(String args[]) {
    	ToyGraphics tg = new ToyGraphics();
    	int x;
    	x=0;
    	while (x < 256) {
    	    tg.drawRGB(x,50,255,255,255);
    	    x = x+ 1;
    	}
        }
    }
    

    ToyGraphics.java を利用するプログラムは次のように記述します。

    1. main()メソッドの先頭で、一度だけ
          ToyGraphics tg = new ToyGraphics();
      
      と記述します。 詳しい話は今回は省略しますが、 これで「ToyGraphicsクラスのインスタンスを作る」ことになります (Inputクラスを使うときと似てますね)。 これで、幅640ドット、高さ480ドットの黒いウィンドウが表示されるようになります。
    2. プログラムの中で、座標(x, y)の位置の点を (red,green,blue) で表される色にするには 以下のように書きます。
          tg.drawRGB(int x,int y,int red,int green, int blue);
      
      この場合、0≦x<640, 0≦y<480, 0≦red≦255, 0≦green≦255, 0≦blue≦255 であることに注意して下さい。
  3. プログラムをコンパイルして実行します。
    GSample01.javaの実行例
    sp204: ~/pro 11> javac GSample01.java 
    sp204: ~/pro 12> java GSample01 
    

  4. プログラムが実行を終了した後は、ウィンドウの最終状態が 表示されたままになります。 ウィンドウの上のタイトルバー の右の 上でマウスの左ボタンをクリックすると、ウィンドウを閉じてプログラムが終了します。
GSample02.java (赤い横線と緑の縦線を描く)
public class GSample02 {
    public static void main(String args[]) {
	ToyGraphics tg = new ToyGraphics();
	int x,y;
	x=50;
	y=50;
	while (x < 600) {
	    tg.drawRGB(x,y,255,0,0);
	    x = x+ 1;
	}
	x=50;
	y=50;
	while (y < 450) {
	    tg.drawRGB(x,y,0,255,0);
	    y=y+1;
	}
    }
}
GSample02.javaの実行例
sp204: ~/pro 13> javac GSample02.java 
sp204: ~/pro 14> java GSample02 

直線を描きましょう

画面上のドットをうまく光らせて、直線を表示してみましょう。

直線の始点の座標を (sx,sy)、終点の座標を(ex,ey)とします。 今回は以下の方針で直線を描くことにします。

GSample03.java (sx,sy)から(ex,ey)へ直線を描く
import java.util.*;
public class GSample03 {
    public static void main(String args[]) {
	Scanner sc = new Scanner(System.in);
	ToyGraphics tg = new ToyGraphics();
	int sx, sy, ex, ey, x, y, r, g, b;
	System.out.print("赤の強さを0〜255の整数として入力して下さい   ");
	r = sc.nextInt();
	System.out.print("緑の強さを0〜255の整数として入力して下さい   ");
	g = sc.nextInt();
	System.out.print("青の強さを0〜255の整数として入力して下さい   ");
	b = sc.nextInt();
	System.out.print("始点のx座標を0〜639の整数として入力して下さい   ");
	sx = sc.nextInt();
	System.out.print("始点のy座標を0〜479の整数として入力して下さい   ");
	sy = sc.nextInt();
	System.out.print("終点のx座標を0〜639の整数として入力して下さい   ");
	ex = sc.nextInt();
	System.out.print("終点のy座標を0〜479の整数として入力して下さい   ");
	ey = sc.nextInt();
	if (sx == ex) {
	    x = sx;
	    if (sy == ey) {
		tg.drawRGB(sx,sy,r,g,b);
	    } else if (sy < ey) {
		// 提出課題1-1
	    } else {
		y = sy;
		while (y >= ey) {
		    tg.drawRGB(x,y,r,g,b);
		    y = y - 1;
		}
	    }
	} else if (sx < ex) {
	    x = sx;
	    while (x <= ex) {
		y = (ey - sy) * (x - sx) / (ex - sx) + sy;
		tg.drawRGB(x,y,r,g,b);
		x = x + 1;
	    }
	} else {
	    x = sx;
	    // 提出課題1-2
	}
    }
}
GSample03.java の実行例
sp204: ~/pro 15> javac GSample03.java 
sp204: ~/pro 16> java GSample03 
赤の強さを0〜255の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊床亀鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌緑の強さを0〜255の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊床亀鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌青の強さを0〜255の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊尚鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌始点のx座標を0〜639の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊庄旭鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌始点のy座標を0〜479の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊庄旭鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌終点のx座標を0〜639の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊承屋鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌終点のy座標を0〜479の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊廠屋鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌
始点:(100,100),終点(520,320)の時

2重ループ

「繰り返し(iteration)」のことを「ループ (loop)」とも言います。 「繰り返し」の中に「繰り返し」があると「2重ループ」になります。 一般に「繰り返し」がn層になっていると 「n重ループ」と呼びます。

長方形を描きましょう

長方形は2重ループで描くことができます。 座標 (x,y)の点を塗って行きますが、 外側のループでyを小さい値から大きい値へと変化させていき、 内側のループでxを小さい値から大きい値へと変化させていけば よいのです。
Rect1.java(長方形を描く)
import java.util.*;
public class Rect1 {
    public static void main(String args[]) {
	Scanner sc = new Scanner(System.in);
	ToyGraphics tg = new ToyGraphics();
	int sx, sy, r, g, b, x, y, w, h;
	System.out.print("赤の強さを0〜255の整数として入力して下さい   ");
	r = sc.nextInt();
	System.out.print("緑の強さを0〜255の整数として入力して下さい   ");
	g = sc.nextInt();
	System.out.print("青の強さを0〜255の整数として入力して下さい   ");
	b = sc.nextInt();
	System.out.print("左上点のx座標を0〜639の整数として入力して下さい   ");
	sx = sc.nextInt();
	System.out.print("左上点のy座標を0〜479の整数として入力して下さい   ");
	sy = sc.nextInt();
	System.out.print("幅をドット数で入力して下さい   ");
	w = sc.nextInt();
	System.out.print("高さをドット数で入力して下さい   ");
	h = sc.nextInt();
	y = sy;
	while (y < sy + h) {
	    x = sx;
	    while (x < sx + w) {
		tg.drawRGB(x,y,r,g,b);
		x = x + 1;
	    }
	    y = y + 1;
	}
    }
}
Rect1.javaの実行例
sp204: ~/pro 17> javac Rect1.java 
sp204: ~/pro 18> java Rect1 
赤の強さを0〜255の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊尚鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌緑の強さを0〜255の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊床亀鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌青の強さを0〜255の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊庄恩鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌左綸世ぢ座標を0〜639の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊庄旭鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌左綸世ぢ座標を0〜479の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊掌絢ゔ昭藁埔 蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌幅をドット数で入力して王踉擦気   柴藁地模匳鱚箴蕊承旭鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌高さをドット数で入力して王踉擦気   柴藁地模匳鱚箴蕊廠旭鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌

三角形を描きましょう

三角形も2重ループで描くことができます。 四角形の場合とは異なり、yの値に応じてxの 変化する幅を変えます。 プログラム中では ww という変数で表されていますが、 以下のようにして計算できます。

y=syにおいて幅は1      --- (1)
y=sy+h-1において幅はw  --- (2)
したがって、あるyにおける幅wwは (1)より
    ww = k * (y - sy) + 1 --- (3)
とおくことができます。(3)に(2)を代入して
    w = k * (sy + h - 1 - sy) + 1
これを解くと、 k = (w - 1) / (h - 1) が求まります。
(3)にkの値を代入して
    ww = (w - 1)  * (y - sy)/ (h - 1) + 1
で計算できることがわかります。

Triangle1.java (3角形を描く)
import java.util.*;
public class Triangle1 {
    public static void main(String args[]) {
	Scanner sc = new Scanner(System.in);
	ToyGraphics tg = new ToyGraphics();
	int sx, sy, r, g, b, x, y, w, h;
	r = 255;
	g = 0;
	b = 255;
	System.out.print("頂点のx座標を0〜639の整数として入力して下さい   ");
	sx = sc.nextInt();
	System.out.print("頂点のy座標を0〜479の整数として入力して下さい   ");
	sy = sc.nextInt();
	System.out.print("底辺の幅をドット数で入力して下さい   ");
	w = sc.nextInt();
	System.out.print("高さをドット数で入力して下さい   ");
	h = sc.nextInt();
	y = sy;
	while (y < sy + h) {
	    int ww = (w - 1)*(y - sy) / (h - 1) + 1;
	    x = sx;
	    while (x < sx + ww) {
		tg.drawRGB(x,y,r,g,b);
		x = x + 1;
	    }
	    y = y + 1;
	}
    }
}
Triangle1.javaの実行例
sp204: ~/pro 19> javac Triangle1.java 
sp204: ~/pro 20> java Triangle1 
頂点のx座標を0〜639の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊庄旭鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌頂点のy座標を0〜479の整数として入力して王踉擦気   柴藁地模匳鱚箴蕊承絢ゔ昭藁埔 蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌底辺の幅をドット数で入力して王踉擦気   柴藁地模匳鱚箴蕊彰旭鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌高さをドット数で入力して王踉擦気   柴藁地模匳鱚箴蕊床軌鹿評鹿届淋蕊庸 嘔箪跫竅讚蜒闔鶩緕鬯芍罌



演習

提出課題1

GSsample03.java の省略されている部分を自分で作成し、 正しく動作するようにして下さい。 ファイル名は GSample04.java としましょう。

  1. sx == ex の場合でも正しく処理できるようにプログラムを書いて下さい。
    以下の入力を与えた場合でも正しく動作することを確認しておいて下さい。
    1. 始点(100,100), 終点(100,300)
    2. 始点(100,100), 終点(100,50)
    3. 始点(100,100), 終点(100,100)
  2. sx > ex の場合でも正しく処理できるようにプログラムを書いて下さい。
    以下の入力を与えた場合でも正しく動作することを確認しておいて下さい。
    1. 始点(300,100), 終点(100,300)
    2. 始点(300,300), 終点(100,100)
    3. 始点(300,100), 終点(100,100)

提出課題2

オリジナリティ溢れる美しい画像を出力するJavaのプログラムを作成して 下さい。 単に幾何学的な模様だけでなく、動物、乗物など、意味のある構成要素を 登場させることを条件にします。 単なる「矩形領域の塗りつぶし」だけでなく、三角形の 塗りつぶしなど、いろいろアルゴリズムを考えて実験して下さい。 ファイル名は GReport01.java としましょう。

プログラムをいろいろ変更しているうちにバグを含んでしまう場合が あります。提出するプログラムは、きちんとコンパイルできて、かつ、 正しく動作することを確認しておいて下さい。

「繰り返しを学習した時点で、プログラムで絵を描いてみる」 という課題は過去にも出題されています。 先輩達の作品は以下のURLから参照できます。 (ただし、過去の課題は cos や sin を計算する方法を学習した後で 作成されたプログラムです)。

提出方法

宿題提出WEBの 「1年セミナー課題1」 http://ynitta.com/tsuda/handin/up.php?id=2006/1semi/kadai1 および 「1年セミナー課題2」 http://ynitta.com/tsuda/handin/up.php?id=2023/1semi/kadai2 から提出して下さい。

課題の提出〆切は7月19日 13:00です。 〆切に遅れないように注意して下さい。

正しく提出されたかどうかを http://ynitta.com/tsuda/handin/list.php?id=2006/1semi/kadai1 および http://ynitta.com/tsuda/handin/list.php?id=2006/1semi/kadai2 で必ず確認しておいて下さい。