http://docs.oracle.com/javase/jp/6/api/
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(); } }
上のプログラムでは,描画するとゴミが出ます. これを出ないように改良してください.
一筆書きを解消するヒント
解答編はこちら SimpleDraw_Ans1
このプログラムのjavaとclassファイルをフォルダに入れて,学籍番号+ローマ字名前を付けて,zipで圧縮して提出してください.
以下にヒントとなる情報を書いておきます.
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が呼び出すメソッドとして、例えば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); // (略) }
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); } // (略) }
これも、色の場合と同じく、太さの変数を外部から変更するメソッドを用意しておきます。
public class DrawPanel extends JPanel { // (略) Float currentWidth=20.0f; // (略) public void setPenWidth(float newWidth) { currentWidth = newWidth; } // (略) }
教科書の方法ではメニューアイテムを一つ定義するのに、何行も書く必要があります。メニューを用意する部分は大きくなりそうですので、一つのメソッドにしてわかり易くしておきましょう。例えばinitMenu()というメソッドにしておいて、プログラムの初期段階のところでこれを呼び出すようにします。そうすればたとえば
というようなメニューは、
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); } // (略) }
これでしたらこんな
メニューでも、以下のように作れます。
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); } // (略) }
上記のようにメニューを作った場合、メニューが選択されると、自分自信の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); } //(略) }
これも同様です。black, white, yellowなどの色のメニューアイテムを用意して、actionPerformedのなかで判定して、色を換えます。
メニューから色の名前を選ぶ他に、教科書の最後のページにあるJColorChooserも使えるようにしましょう。JColorChooserは、例えば、メニューアイテムColorが選ばれたときに開くよう、actionPerformedの中にプログラムします。
消しゴムは、白色で描く機能を用意します。消しゴムの太さなども選択できると良いでしょう。
最低限でもここまでの内容を完成して、提出してください。
上の例では,ウィンドウの面に直接描画していました.いわば描きっぱなしの状況でした.そこで,ウィンドウのサイズを変更したり,ウィンドウをドッグにしまったりすると描いた内容が消えてしまいます.そのほか,描いた内容をセーブするために保持しておきたい,描いている途中の作業を見せないようにしたいなどの理由で,バッファを用意してそちらに描画して,あとで表示するという手法がとられます.バッファを使うには以下のように,バッファに描いたあとで,repaintというメソッドでこれを表示します.repaintが実行されると、paintComponentというメソッドが呼ばれるので、ここでバッファを表示するプログラムを書いておきます。repaintは,ウィンドウのサイズ変更などでも呼び出されるので,描いた内容が消えなくなります.
バッファのために、Imageとこれから作るGraphicsのインスタンスを用意しておきます。ここでは、それぞれのサブクラスであるBufferedImageとGraphics2Dのインスタンスを使ってみました。元のImageとGrahicsと比較して、Javaの最近のバージョンで追加されたクラスです。そのため、上の例で示したように、太い線を引いたり、後の例で示すように、ファイルにセーブしたりする時に楽です。
下の例では、drawLineでバッファが作ってない場合に作るようにしてありますが、実際にはdrawLineが呼び出される前に、あらかじめバッファが作られておくようプログラムすべきです。
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.drawLine(x1, y1, x2, y2); // バッファに描画する repaint();//再描画するためpaintComponentを呼び出す。 } // (略) public void paintComponent(Graphics g) { super.paintComponent(g);//他に描画するものがあるかもしれないので親を呼んでおく if(null!=bufferImage) g.drawImage(bufferImage, 0,0,this);//バッファを表示する } // (略) }
この例では、ウィンドウの大きさのバッファを作っています。このサイズより大きな絵は描けないことになります。もう少し大きめのバッファにしてもよかったかもしれません。実際には、バッファの大きさは、たとえば、A4の紙に印刷するならその大きさにする、など、ユーザに指定させることになります。
以下のテーマに挑戦してください。 ここより先の機能が実現できていれば成績に加算いたします.
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を呼ぶ } // (略) }
これも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; } } // (略) }
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(); //(略) }
あとは工夫次第で便利な/面白い機能を追加してください。どのような機能が欲しいかについては、世の中のお絵描きソフトを参考にしても良いかと思います。また、どのような機能が簡単に追加できそうかについては、Graphic2Dのメソッドなどを見て行くと見当がつくかもしれません。以下に、思いつくままに書いてみますが、これにとらわれずに、皆さんの感性でユニークな機能を追加してください。
添付の2007.zip, 2008.zip, 2009.zip, 2010sample.zipに,2007, 2008, 2009年, 2010年の先輩の作品を入れておきました.見てください.
このページについてのお問い合わせはsiio@is.ocha.ac.jpまで。