#author("2021-02-18T02:11:59+00:00","ocha","ocha")
#author("2021-02-18T02:13:37+00:00","ocha","ocha")
* Java swing を使ったお絵描きプログラム [#h604d72c]

**javaのマニュアル [#pf1a0c93]

//http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/api/index.html
//http://java.sun.com/javase/ja/6/docs/ja/api/index.html
//http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/index.html
http://docs.oracle.com/javase/jp/6/api/



** 線を引く簡単なプログラム [#pac2b7d5]

JPanelのサブクラスを作りました。

 import javax.swing.JPanel;
 import java.awt.Graphics;
 
 public class DrawPanel extends JPanel {
 
 	public void drawLine(int x1, int y1, int x2, int y2){
 		Graphics g = this.getGraphics();
 		g.drawLine(x1, y1, x2, y2);
 	}
  }
 

こちらはメインのプログラム。JFrameのサブクラスで、これに上記のJPanelのサブクラスを貼付けます。
リスナーになっているので、こちらでマウスなどのイベントを受け取ります。

 
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionListener;
 
 import javax.swing.JFrame;
 
 public class SimpleDraw extends JFrame implements MouseMotionListener {
 
 	int lastx=0, lasty=0, newx, newy;
 	DrawPanel panel;
  
 
 	public void mouseMoved(MouseEvent arg0) {
        }
 
 	public void mouseDragged(MouseEvent arg0) {
 		newx=arg0.getX();
 		newy=arg0.getY();
 		panel.drawLine(lastx,lasty,newx,newy);
 		lastx=newx;
 		lasty=newy;
 	}
 
 
 	private void init() {
 		this.setTitle("Simple Draw");
 		this.setSize(300, 200);
 		this.addMouseMotionListener(this);
 		panel=new DrawPanel();
 		this.getContentPane().add(panel);
 		this.setVisible(true);
 		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 	}
 	
 	public static void main(String[] args) {
 		SimpleDraw frame=new SimpleDraw();
 		frame.init();
 	}
 
 }
 

**(演習課題1)上のお絵描きプログラムを改良してください [#m4005657]

上のプログラムでは,描画するとゴミが出ます.
これを出ないように改良してください.

+上のプログラムでは、マウスポインターの位置と、描かれる線の位置がずれています。直してください。
+上のプログラムでは,描画するとゴミが出ます. (前回描き終わった場所から線が引けてしまって、一筆書き状態になっています) これを出ないように改良してください.


一筆書きを解消するヒント

-マウスのクリックで線を引く最初の座標を指定します.
--マウスのクリックを受け取れるようにMouse Listenerもimplementします
http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/awt/event/MouseListener.html
//http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/api/java/awt/event/MouseListener.html


解答編はこちら SimpleDraw_Ans1


**(演習課題2)お絵描きプログラムに以下の機能を追加してください [#scfb12cc]

-メニューから以下の機能を利用できるようにする
--色が変えられる

http://siio.jp/gyazo/java_simple_draw.png

このプログラムのjavaとclassファイルをフォルダに入れて,学籍番号+ローマ字名前を付けて,zipで圧縮して提出してください.

-----------------------


//**(演習課題3)お絵描きプログラムに以下の機能を追加してください [#scfb12cc]

**さらに改良をすすめましょう [#d0370b3e]

-メニューから以下の機能を利用できるようにする
--色が変えられる
--線の太さが変えられる
--消しゴム機能を利用する

以下にヒントとなる情報を書いておきます.

**色の変え方 [#v681acc2]

GraphicsのsetColorメソッドで色を変えられます。

 public class DrawPanel extends JPanel {
 // (略)
	public void drawLine(int x1, int y1, int x2, int y2){
		Graphics g = this.getGraphics();
		g.setColor(Color.yellow);
		g.drawLine(x1, y1, x2, y2);
	}
 // (略)
 }	

**SimpleDrawのインスタンスの中から、DrawPanelの色を変える [#j784df64]

メインのSimpleDrawが呼び出すメソッドとして、例えばsetPenColorというようなメソッドを用意して、これでsetColorで使う色(以下の例ではcurrentColorという変数)の値を変えるようにします。

 public class DrawPanel extends JPanel {
 // (略)
	Color currentColor=Color.black;
 // (略)
 // 以下の関数は、引数で指定された色に設定します。
 	public void setPenColor(Color newColor) {
 		currentColor = newColor;
 	}
 // (略)
	public void drawLine(int x1, int y1, int x2, int y2){
		Graphics g = this.getGraphics();
		g.setColor(currentColor);
		g.drawLine(x1, y1, x2, y2);
	}
 // (略)
 }	

そして、SimpleDrawのインスタンスから、これを呼び出せば色が変わります。

 public class SimpleDraw extends JFrame implements ActionListener,
 		MouseListener, MouseMotionListener {
 // (略)
 // メソッド間でpanelのインスタンスを共同利用するために変数をここに書いておきます。
 	DrawPanel panel;
 // (略)
 // どこかのメソッドでDrawPanelを作って変数panelに入れておきます。
 		panel=new DrawPanel();
 // (略)
 // どこか別のメソッドでこの変数panelを利用します。色を変えるメソッドを呼び出して変更します。
 		panel.setPenColor(Color.black);
 // (略)
 }
 
 
**太い線を引くには [#h3f95aa7]

Graphicsクラスを拡張した(継承した)Graphics2Dクラスを使うと太い線が簡単に描けるようです. Graphics2DクラスはGraphicsクラスのサブクラスなので, Graphicsクラスのインスタンスをキャストしてしまえば、Graphics2Dクラスにできます.


 public class DrawPanel extends JPanel {
 // (略)
 	Float currentWidth=20.0f;
 // (略)
	public void drawLine(int x1, int y1, int x2, int y2){
 		Graphics2D g = (Graphics2D)this.getGraphics();
 
 		//太さがcurrentWidth の線を描く.線の両端は丸くする.
 		g.setStroke(new BasicStroke(currentWidth ,BasicStroke.CAP_ROUND,BasicStroke.JOIN_MITER));
 		g.drawLine(x1, y1, x2, y2);
	}
 // (略)
 }	


**SimpleDrawのインスタンスの中から、DrawPanelの線の太さを変える [#k053ce60]

これも、色の場合と同じく、太さの変数を外部から変更するメソッドを用意しておきます。

 public class DrawPanel extends JPanel {
 // (略)
 	Float currentWidth=20.0f;
 // (略)
 	public void setPenWidth(float newWidth) {
 		currentWidth = newWidth;
 	}
 // (略)
 }	


**メニューを簡単に書くためのヒント [#k44ebcec]

教科書の方法ではメニューアイテムを一つ定義するのに、何行も書く必要があります。メニューを用意する部分は大きくなりそうですので、一つのメソッドにしてわかり易くしておきましょう。例えばinitMenu()というメソッドにしておいて、プログラムの初期段階のところでこれを呼び出すようにします。そうすればたとえば

#ref(menu1.png);

というようなメニューは、

 public class SimpleDraw extends JFrame implements ActionListener,
 		MouseListener, MouseMotionListener {
 // (略)
 	private void initMenu() {
 		JMenuBar menubar=new JMenuBar();
 		JMenu menuFile = new JMenu("File");
 		JMenuItem itemNew = new JMenuItem("New");
 		itemNew.setActionCommand("New");
 		itemNew.addActionListener(this);
 		menuFile.add(itemNew);
 		JMenuItem itemOpen = new JMenuItem("Open...");
 		itemOpen.setActionCommand("Open");
 		itemOpen.addActionListener(this);
 		menuFile.add(itemOpen);
 		JMenuItem itemSave = new JMenuItem("Save...");
 		itemSave.setActionCommand("Save");
 		itemSave.addActionListener(this);
 		menuFile.add(itemSave);
 		menubar.add(menuFile);
 		this.setJMenuBar(menubar);
 	}
 // (略)
 }

として表示できます。

でもまだ簡単にできそうです。
というのは、よく見ると同じことを繰り返していますし、JMenuItemの変数はあとで利用するわけでもありません。そこで、こんなメソッドを作れば楽になるかと思います。

 public class SimpleDraw extends JFrame implements ActionListener,
 		MouseListener, MouseMotionListener {
 // (略)
 	private void addMenuItem
 	(JMenu targetMenu, String itemName, String actionName, ActionListener listener) {
 		JMenuItem menuItem = new JMenuItem(itemName);
 		menuItem.setActionCommand(actionName);
 		menuItem.addActionListener(listener);
 		targetMenu.add(menuItem);
 	}
 	
 	private void initMenu() {
 		JMenuBar menubar=new JMenuBar();
 		JMenu menuFile = new JMenu("File");
 		this.addMenuItem(menuFile,"New","New",this);
 		this.addMenuItem(menuFile,"Open...","Open",this);
 		this.addMenuItem(menuFile,"Save...","Save",this);
 		menubar.add(menuFile);
 		this.setJMenuBar(menubar);
 	}
 // (略)
 }

これでしたらこんな

#ref(menu2.png);

メニューでも、以下のように作れます。


 public class SimpleDraw extends JFrame implements ActionListener,
 		MouseListener, MouseMotionListener {
 // (略)
 	private void addMenuItem
 	(JMenu targetMenu, String itemName, String actionName, ActionListener listener) {
 		JMenuItem menuItem = new JMenuItem(itemName);
 		menuItem.setActionCommand(actionName);
 		menuItem.addActionListener(listener);
 		targetMenu.add(menuItem);
 	}
 	
 	private void initMenu() {
 		JMenuBar menubar=new JMenuBar();
 		JMenu menuFile = new JMenu("File");
 		this.addMenuItem(menuFile,"New","New",this);
 		this.addMenuItem(menuFile,"Open...","Open",this);
 		this.addMenuItem(menuFile,"Save...","Save",this);
 		menubar.add(menuFile);
 		JMenu menuPen = new JMenu("Pen");
 		this.addMenuItem(menuPen, "Color...", "Color", this);
 		JMenu menuWidth = new JMenu("Width");
 		this.addMenuItem(menuWidth, "width1", "width1", this);
 		this.addMenuItem(menuWidth, "width5", "width5", this);
 		this.addMenuItem(menuWidth, "width10", "width10", this);
 		this.addMenuItem(menuWidth, "width20", "width20", this);
 		menuPen.add(menuWidth);
 		menubar.add(menuPen);
 		this.setJMenuBar(menubar);
 	}
 // (略)
 }

**メニューによって線の太さを変更する [#c9a2a92a]

上記のようにメニューを作った場合、メニューが選択されると、自分自信のactionPerformedが呼び出されますので、この中でメニューを判定して太さを変えます。

 public class SimpleDraw extends JFrame implements ActionListener,
 		MouseListener, MouseMotionListener {
 //(略)
 	DrawPanel panel;
 //(略)
 	public void actionPerformed(ActionEvent arg0) {
 		if(arg0.getActionCommand().equals("width1")) 
 			panel.setPenWidth(1);
 		else if(arg0.getActionCommand().equals("width5")) 
 			panel.setPenWidth(5);
 		else if(arg0.getActionCommand().equals("width10")) 
 			panel.setPenWidth(10);
 		else if(arg0.getActionCommand().equals("width20")) 
 			panel.setPenWidth(20);
 	}
 //(略)
 }
 

**メニューによって色を変更する [#g6fb08bf]

これも同様です。black, white, yellowなどの色のメニューアイテムを用意して、actionPerformedのなかで判定して、色を換えます。

**色を選ぶときにJColorChooserを使う [#d3e5fa22]

メニューから色の名前を選ぶ他に、教科書の最後のページにあるJColorChooserも使えるようにしましょう。JColorChooserは、例えば、メニューアイテムColorが選ばれたときに開くよう、actionPerformedの中にプログラムします。


**消しゴム機能を作る [#wae903b5]

消しゴムは、白色で描く機能を用意します。消しゴムの太さなども選択できると良いでしょう。



**ウィンドウに直接描かないでバッファに描く [#hf1dfbaf]
**ウィンドウに直接描かないでバッファに描く [#buffer]

上の例では,ウィンドウの面に直接描画していました.いわば描きっぱなしの状況でした.そこで,ウィンドウのサイズを変更したり,ウィンドウをドッグにしまったりすると描いた内容が消えてしまいます.そのほか,描いた内容をセーブするために保持しておきたい,描いている途中の作業を見せないようにしたいなどの理由で,バッファを用意してそちらに描画して,あとで表示するという手法がとられます.バッファを使うには以下のように,バッファに描いたあとで,repaintというメソッドでこれを表示します.repaintが実行されると、paintComponentというメソッドが呼ばれるので、ここでバッファを表示するプログラムを書いておきます。repaintは,ウィンドウのサイズ変更などでも呼び出されるので,描いた内容が消えなくなります.

バッファのために、Imageとこれから作るGraphicsのインスタンスを用意しておきます。ここでは、それぞれのサブクラスであるBufferedImageとGraphics2Dのインスタンスを使ってみました。元のImageとGrahicsと比較して、Javaの最近のバージョンで追加されたクラスです。そのため、上の例で示したように、太い線を引いたり、後の例で示すように、ファイルにセーブしたりする時に楽です。

下の例では、drawLineでバッファが作ってない場合に作るようにしてありますが、実際にはdrawLineが呼び出される前に、あらかじめバッファが作られておくようプログラムすべきです。

 // (略)
 import java.awt.image.*;
 // (略)
 public class DrawPanel extends JPanel {
 	BufferedImage bufferImage=null;
 	Graphics2D bufferGraphics=null;
 // (略) 	
 private void createBuffer(int width, int height) {
        //バッファ用のImageとGraphicsを用意する
 		bufferImage = new BufferedImage(width, height,BufferedImage.TYPE_INT_BGR);
 		bufferGraphics=bufferImage.createGraphics(); //getGraphicsと似ているが、戻り値がGraphics2D。
 		bufferGraphics.setBackground(Color.white);
 		bufferGraphics.clearRect(0, 0, width, height); //バッファクリア
 	}
   // (略)	
 	public void drawLine(int x1, int y1, int x2, int y2){       
 		if(null==bufferGraphics) {
 		 	this.createBuffer(this.getWidth(),this.getHeight());  //バッファをまだ作ってなければ作る
   // (略)	//最初の描画要求なので初期化したいことがもしもあったら書いておく
 		}
 //		bufferGraphics.setColor(  (略)  //Graphics2Dのインスタンスなので今まで通りsetColorで色を、
 //		bufferGraphics.setStroke( (略) //setSctokeで太さなどを設定できる
 		bufferGraphics.drawLine(x1, y1, x2, y2); // バッファに描画する
 		repaint();//再描画するためpaintComponentを呼び出す。
 		//repaintメソッドは親Classで定義されているがpaintComponentを呼び出す。
  	}
 // (略) 
 // repaintメソッドがpaintComponentを呼ぶので、バッファを表示する作業を追加しておく
 	public void paintComponent(Graphics g) {
 		super.paintComponent(g);//他に描画するものがあるかもしれないので親を呼んでおく
 		if(null!=bufferImage) g.drawImage(bufferImage, 0,0,this);//バッファを表示する
 	}
 // (略) 
 }

この例では、ウィンドウの大きさのバッファを作っています。このサイズより大きな絵は描けないことになります。もう少し大きめのバッファにしてもよかったかもしれません。実際には、バッファの大きさは、たとえば、A4の紙に印刷するならその大きさにする、など、ユーザに指定させることになります。


最低限でもここまでの内容を完成して、提出してください。



*以下は、応用編です [#c56aa808]

以下のテーマに挑戦してください。
ここより先の機能が実現できていれば成績に加算いたします.



**JPEGファイル(写真)などを表示してこれにお絵描きする [#z5d0491f]

ImageIOというクラスのクラスメソッドreadを使うと,JPEGやGIFファイルからBufferedImageインスタンスを作ることができます.これを,上記のbufferGraphicsに読み込めば画像ファイルを表示して,さらにそこにマウスで書き込むことができます.

以下のopenFileメソッドでは,引数のFile型インスタンスで指定されるファイルを開いて読み込みます.読み終わったら,イメージファイルの大きさのbufferImageを作り直して,bufferGraphicsを取得し直して,イメージファイルを読み込み,最後にrepaintを呼んで表示させています.

これを上記のSimpleDrawのインスタンスの中で呼び出すためには、たとえば、

 panel.openFile(new File("sample.jpg")); 

というようにします。こうするとこのプログラムが置かれた場所(~/Documents/workspace/SimpleDraw) にあるsample.jpgというJPEGファイルを開きます。適当なJPEGファイルを用意して試してみてください。

 public class DrawPanel extends JPanel {
 // (略)
 	BufferedImage bufferImage=null;
 	Graphics2D bufferGraphics=null;
 // (略)
 	private void createBuffer(int width, int height) {
         //バッファ用のImageとGraphicsを用意する
 		bufferImage = new BufferedImage(width, height,BufferedImage.TYPE_INT_BGR);
 		bufferGraphics=bufferImage.createGraphics(); //getGraphicsと似ているが、戻り値がGraphics2D。
 		bufferGraphics.setBackground(Color.white);
 	}
 // (略)
 	public void openFile(File file2open){
 		BufferedImage pictureImage;
 		try {
 			pictureImage = ImageIO.read(file2open);
 		} catch(Exception e){
 			System.out.println("Error: reading file="+file2open.getName());
 			return;
 		}
 		//画像に合わせたサイズでbufferImageとbufferGraphicsを作りなおして画像を読み込む
 			//ImageIO.readの戻り値をbufferImageに代入するのでは駄目みたいです。
 		this.createBuffer(pictureImage.getWidth(),pictureImage.getHeight());
 		bufferGraphics.drawImage(pictureImage,0,0,this);
 		repaint(); //画像を表示するためにpaintComponentを呼ぶ
 	}
 // (略)
 }
 

**お絵描きした内容をJPEGファイルとして保存する [#o677b605]


これもImageIOというクラスのクラスメソッドwriteを使います。これによりBufferedImageインスタンスの内容を、JPEGやGIFファイルとして書き出すことが出来ます。bufferGraphicsを書き出せば、お絵描きした内容をファイルとして保存できます。

以下のsaveFileメソッドでは,引数のFile型インスタンスで指定されるたファイルに書き込みます。

 public class DrawPanel extends JPanel {
 // (略)
 	BufferedImage bufferImage=null;
 // (略)
 	public void saveFile(File file2save) {
 		try {
 			ImageIO.write(bufferImage, "jpg", file2save);
 		} catch (Exception e) {
 			System.out.println("Error: writing file="+file2save.getName());
 			return;
 		}
 	}
 // (略)
 }

**読み込むファイル/書き込むファイルをダイアログパネルで指定する [#w98d58cc]

JFileChooserを使うと、読み込み/書き込みファイルの指定のためのダイアログパネルを表示します。上記のJColorChooserと違って、showXXXDialogのメソッドがクラスメソッドではない(staticではない)ですので、プログラムの最初のどこかで、JFileChooserのインスタンスを作っておいて、必要になった時点で(例えばファイルを開くメニューが選ばれたと判定されるactionPerformedの中)で呼び出します。

 ipublic class SimpleDraw extends JFrame implements ActionListener,
 		MouseListener, MouseMotionListener {
  // (略)
 	DrawPanel panel;
 	JFileChooser fileChooser;
  // (略)
 	public void actionPerformed(ActionEvent arg0) {
  // (略)
 		else if(arg0.getActionCommand().equals("Open")) {
 			int returnVal = fileChooser.showOpenDialog(this);
 	        if (returnVal == JFileChooser.APPROVE_OPTION) {
 	        	panel.openFile(fileChooser.getSelectedFile());
 	        }
 		}
 		else if(arg0.getActionCommand().equals("Save")) {
 			int returnVal = fileChooser.showSaveDialog(this);
 	        if (returnVal == JFileChooser.APPROVE_OPTION) {
 	        	panel.saveFile(fileChooser.getSelectedFile());
 	        }
 		}
  // (略)
  }
  // (略)
  //どこか最初に実行される場所
 		fileChooser = new JFileChooser(); 
 //(略)
 }



**このほかいろいろと機能を追加してみてください [#extra]

あとは工夫次第で便利な/面白い機能を追加してください。どのような機能が欲しいかについては、世の中のお絵描きソフトを参考にしても良いかと思います。また、どのような機能が簡単に追加できそうかについては、Graphic2Dのメソッドなどを見て行くと見当がつくかもしれません。以下に、思いつくままに書いてみますが、これにとらわれずに、皆さんの感性でユニークな機能を追加してください。

-便利な機能
--描画面全体を消して白紙にする機能
--領域を指定してコピーペースト移動等を行う機能
--描画中の内容を消さずに、その内容の任意の場所に、写真を貼付ける機能
--イメージを拡大縮小する機能
--指定した文字列が画像に書き込まれる機能
--指定した矩形を塗りつぶす/消す機能
--円や矩形や多角形を線で書く機能
-- 矩形や多角形を塗りつぶして書く機能
--大きな写真の場合、スクロールして閲覧/編集する機能
--絵の任意の場所をクリックするとそこの色を取得してペンなどの色にするスポイト機能

-使いやすい工夫
--ペンの太さなどをスライダで指定する機能
--現在の色、ペン太さ、フォント種類、描画モード(手書きか、スタンプかなど)を描画領域外に常時表示する機能
--市販のお絵かきプログラムにあるような、別ウィンドウでペンなどを選択できるパレット機能

http://gyazo.com/e494357afb0d024c38d19c470e5493e5.png
http://gyazo.com/424f8502af49121709bdc994a2684592.png
http://gyazo.com/86935ab91636a836d7c2bc06dde675b7.png


-おもしろい(かもしれない)機能
--適当な小さなイメージを指定するとそれがスタンプになって、好きな場所に好きなだけ貼付けることが出来る機能
--画面中央を境に、いつでも左右対称に描画される機能。もしくは上下対称。
--ドラッグさせて描いているとどんどん色が変わっていく虹色ペン。もしくは、どんどんかすれていく筆ペン。
--指定された文字列がマウスのドラッグに従って書かれて行く機能(たとえば、ochanomizu universityという文字が、マウスの移動に従ってその場所にぱらぱらと書き込まれる機能
#ref(textpen.png);


**先輩が作った優秀作品紹介 [#b13097a9]

添付の2007.zip, 2008.zip, 2009.zip, 2010sample.zipに,2007, 2008, 2009年, 2010年の先輩の作品を入れておきました.見てください.

//** 2006年度の先輩が作った優秀作品紹介 [#ae3ed1eb]
//
//添付のdraw.jarとdraw2.jarを試してみてください。draw.jarはわりと安定して動いています。
//draw2.jarはすこしバグがあるようで止まってしまうことがありますが機能は意欲的です。

**先輩が作った優秀な取扱説明書 [#xef7c661]
-http://siio.jp/pdf/2013/manual1.pdf
-http://siio.jp/pdf/2013/manual2.pdf
-http://siio.jp/pdf/2013/manual3.pdf
-http://siio.jp/pdf/2013/manual4.pdf
-http://siio.jp/pdf/2013/manual5.pdf

------
このページについてのお問い合わせはsiio@is.ocha.ac.jpまで。

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS