絵を別のところに描く

JPanelに直接描かない

どんな時にも今までに描いた絵を消すことなく一度見えなくなったウインドウが戻ることなどでも絵が消えないようにするには、絵をJPanelではなく別のところに描いておいて、paintComponentはその絵をJPanelにコピーするだけにします。

EventRandom

まず別の場所の用意ですが、

BufferedImage buffimg = new BufferedImage(int width,int height, int TYPE);
Graphics bfg = buffimg.createGraphics();

width, height はピクセル数、TYPEはカラーなのかグレースケールか透明度(アルファチャンネル)を持つのかの指定です。

このBufferedImageのインスタンスに createGraphics() メソッドで Graphics クラスのインスタンスを割り当てておきます。

こうしておくと、後で

bfg.fillOval(x,y,w,h);

などと描画ができます。

paintComponentには

myg.drawImage(buffimg, 0, 0, this);

と書いて、bufimg内のデータをJPanel内にコピーします。

ファイル名 EventRandom2.java

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

public class EventRandom2 extends JFrame implements ActionListener{
    JButton mybtn;
    MyPanel mypnl;
    int pcct=0;    //paintComponentが呼び出された回数
    int clct=0;    //クリックした回数
    public EventRandom2() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("イベントで変化する模様");
        setLayout(new BorderLayout());
        mypnl = new MyPanel(400,300);
        mybtn = new JButton("draw");
        add(mypnl, BorderLayout.CENTER);
        add(mybtn,BorderLayout.SOUTH);
        mybtn.addActionListener(this);
        //mypnl.setBackground(new Color(255,255,191));
        //mypnl.setPreferredSize(new Dimension(400,300));
        pack();
        setVisible(true);
    }
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == mybtn) {
            clct++;
           mypnl.drawToBuff();
           //mypnl.repaint();
        }
    }
    public static void main(String[] args){
        EventRandom2 myframe = new EventRandom2();
    }

    public class MyPanel extends JPanel{
        BufferedImage buffimg;
        Graphics bfg;
        Color bgcolor = new Color(255,255,191);
        public MyPanel(int width, int height){
            setPreferredSize(new Dimension(width,height));
            buffimg = new BufferedImage(
                width,height,BufferedImage.TYPE_INT_RGB);
            bfg = buffimg.createGraphics();
            bfg.setColor(bgcolor);
            bfg.fillRect(0, 0, width, height);
        }

      @Override
      public void paintComponent(Graphics myg){
         //super.paintComponent(myg);
         pcct++;
         myg.drawImage(buffimg, 0, 0, this);
      }
      public void drawToBuff(){
         for(int i=0; 10>i; i++){
                Color rcolor = randomColor();
                bfg.setColor(rcolor);
                int x = (int)(400*Math.random());
                int y = (int)(300*Math.random());
                int h = (int)(50*Math.random()+5);
                bfg.fillOval(x-h/2,y-h/2,h,h);
          }
          bfg.setColor(bgcolor);
          bfg.fillRect(10,4,60,20);
          bfg.setColor(Color.black);
          bfg.drawString(pcct+" "+clct,10,20);
          repaint();
       }
       public Color randomColor(){
          int r=0;
          int g=0;
          int b=0;
          int dc = 256;
          g = (int)(dc*Math.random());
          if (g > 255){
             g = 0;
           }
          Color c = new Color(r,g,b);
          return c;
       }
    }
}

フレームの大きさを変えても

これで、ウインドウを最小化して戻したり、ウインドウの大きさを変える場合も描きためた円はなくなりません。

EventRandom

でも絵の出ない部分ができてしまいます。これをどうするかは次の改良点です。

課題

上記プログラムをつくって動作を確認しなさい。

ファイル名 EventRandom2.java

余計な話

BufferedImageのTYPE

Javaの 「API 仕様書」 に14種類のTYPEがフィールドとして定義されているがその違いの説明は十分でない。

たとえば透明度を持たないカラーでも、

の3つ

たとえば透明度を持つカラーでは、

の4つがあります。

javaではintは4バイトなので、能力的には同等。データの構造が異なるだけなのでしょう。どうせGraphicsのメソッドで描画しますから、24ビットカラーなら上記3つのうちのどれか、アルファチャンネルを持つ24ビットカラーなら上記4つのうちのどれかで構わないのではないかと思われます。

インデックスカラーとかグレースケールを使おうとするならまた別の話です。

BufferedImageを作る場所

今回はJPanel(を継承したクラスのインスタンス)に描画される画像を保持するという流れなのでBufferedImageはJPanelが持つことにしました。JPanel(を継承したクラスのインスタンス)に対してランダムな円の追加を指示すると黙ってBufferedImageに描画します。その指示はmypnl.drawToBuff();ですが、drawToBuff()という名前がバッファを使うという雰囲気を示しますが、完全にmypnlまかせに出来ます。Mypanelの外ではBufferedImageのことを考える必要がありません。

ただこの方法でBufferedImageのサイズをMypanelから取得しようとすると困難に出会います。

今回コンストラクタで作っていますが、new MyPanel() の時点ではMyPanelのインスタンスのサイズは決まっていませんのであらかじめ400,300と大きさを指示する必要があります。

ユーザーがウィンドウを拡大してから描画を始めたらそれに合わせたサイズのBufferedImageを作るという場合には、コンストラクタではなく、初めて drawToBuff() メソッドが呼ばれたときに作るしかないでしょう。 drawToBuff()が呼ばれるときはボタンが表示されていますから、パネルも表示されていて大きさが決まっています。パネルの大きさを取得するメソッドがありますからそれに合わせてBufferedImageを作るという算段になります。

興味の対象がパネルではなく、イメージそのものの場合は真っ先にBufferedImageを作っておくことも考えられます。大きなサイズにしておいて、印刷したりファイルに保存したりするのが主体ならば、パネルにBufferedImageを付属させるのは自然ではないでしょう。

Graphics

なぜ buffimg.fillRect(x, y, width, height) でないのか

createGraphics()は何度やっても画像は壊れない。

Graphics と Graphics2D

Graphics を拡張した Graphics2D クラスは混用しても大丈夫?

もくじ

Javaプログラミング
聖愛中学高等学校
http://www.seiai.ed.jp/
Dec.2003
Oct.2010 Oct.2011