簡易フォトレタッチソフト第一回

 Swingアプリケーション開発の演習として簡易フォトレタッチソフトを開発してみます。あくまでSwingアプリケーションの実験なので、機能はごく簡単なもののみにして、GUI部品の扱いやダイアログの作成などを試していくことにしましょう。
 今回は、ビットマップ読み込みプログラムを元にビットマップをBufferedImageに読み込み、それをスクロール領域内に表示するフレームベースのアプリケーションを作ってみます。さらに、フレームの下方にステータスバーを配置し、画像の上でマウスカーソルを動かすと座標とRGBを表示するようにしてみましょう。

BMPパネルコンポーネント

 ビットマップの表示領域には、JPanelを継承したコンポーネントを使用します。このコンポーネント内にビットマップのピクセル列をint型配列として、表示用のビットマップをBufferedImageとして保持するようにしましょう。

  class BMPanel extends JPanel implements MouseMotionListener {

      BufferedImage biImg;
      int iWidth,iHeight;
      int [] pixel;

 ビットマップのピクセル列を扱う処理では、まずビットマップのピクセル列の大きさを設定してフレームバッファ用配列pixelを確保し、そこにピクセル列を設定してから以下のメソッドでbiImgにそのピクセル列を設定するようにします。

  void setPixelToImage() {

      // BufferedImageのピクセル列設定
      biImg.getRaster().setDataElements(0,0,iWidth,iHeight,pixel);

  }

 今回は、ピクセル列に関してはビットマップの読み込みのみを行うので、とりあえずビットマップ読み込みをもとに具体的な処理を考えてみましょう。ただ、Javaではバッファの任意の領域をキャストで任意の型とみなしたり、符号なし整数として扱ったりと言うことができないので、読み取ったバイト列内の数値をJavaの符号付整数型で容易に扱えるように以下の数値の型変換関数を用意することにしました。

  // 指定されたbyteを符号なし整数(0-255)のint値として返す
  private int getByte(byte bArg) {

      if (bArg<0)
          return 256+bArg;
      else
          return bArg;

  }

  // 指定された位置からの4バイトを正のint値として返す
  private int getInt(byte bArg[],int iArg) {

      if (bArg[iArg+3]<0)
          return 0;

      int iNum=0,iP=1;

      for (int i=0;i<4;i++) {

          iNum+=getByte(bArg[iArg+i])*iP;
          iP*=256;

      }

      return iNum;

  }

  // 指定された位置からの3バイトをint値として返す
  private int getRGB(byte bArg[],int iArg) {

      int iNum=0,iP=1;

      for (int i=0;i<3;i++) {

          iNum+=getByte(bArg[iArg+(2-i)])*iP;
          iP*=256;

      }

      return iNum;

  }

 上のメソッドは、まず8ビット符号なし整数を取得するgetByte()を定義し、後はそれを使って24/32ビットの符号なし整数を取得するわけですね。BMPファイルの情報では各種のサイズなどは32ビット整数として、各ピクセルの色は24ビット整数として取得・記録することになります。
 この関数を使うと、たとえばバッファdat[]に読み込んだビットマップファイルのオフセット(先頭から10−13バイト)は以下のように取得できます。

  offset=getInt(dat,10); // ピクセル列までのオフセット

 また、幅・高さとピクセル列の取得は

  width=getInt(dat,18);
  height=getInt(dat,22);

  if (width % 4==0) /* バッファの1ラインの長さを計算 */
      length=width*3;
  else
      length=width*3+(4-(width*3) % 4);

  // ピクセル列配列取得
  pixel=new int[width*height];

  iWidth=width;
  iHeight=height;

  // 24bit-32bit変換を行いながらピクセル列設定
  for (int i=0;i<height;i++)
      for (int j=0;j<width;j++)
          pixel[j+(height-i-1)*width]=getRGB(dat,offset+j*3+i*length);

 といった感じです。getRGB()は、渡されたバッファの3バイト(BMPピクセル列内で3バイト形式で記録された各ピクセルのRGB値)を32ビットのint値に変換するので、バッファのBMPの各ピクセル先頭を渡せばフレームバッファのint型配列pixel[]に格納できる32ビット形式に変換できます。

 BMPファイルを読み込むload()の全体は以下のようになります。

  public boolean load(String stArg) {

      int size=0,offset=0,width=0,height=0,length=0;

      File f=new File(stArg);

      size=(int)(f.length());

      if (size<1)
          return false;

      byte dat[]=new byte[size];

      try { // ファイルの内容をdatに読み込む

          FileInputStream fs=new FileInputStream(stArg);

          fs.read(dat);

          fs.close();

      } catch (FileNotFoundException e) {}
        catch (IOException e) { return false; }

      // 24BitBMPでなければ戻る
      if (dat[0]!='B' || dat[1]!='M' || dat[28]!=24)
          return false;

      offset=getInt(dat,10); // ピクセル列までのオフセット

      width=getInt(dat,18);
      height=getInt(dat,22);

      if (width % 4==0) /* バッファの1ラインの長さを計算 */
          length=width*3;
      else
          length=width*3+(4-(width*3) % 4);

      // ピクセル列配列取得
      pixel=new int[width*height];

      iWidth=width;
      iHeight=height;

      // 24bit-32bit変換を行いながらピクセル列設定
      for (int i=0;i<height;i++)
          for (int j=0;j<width;j++)
              pixel[j+(height-i-1)*width]=getRGB(dat,offset+j*3+i*length);

      // BufferedImage生成
      biImg=new BufferedImage(width,height,BufferedImage.TYPE_INT_BGR);

      // pixelの内容をBufferedImageのピクセル列に設定
      setPixelToImage();

      setPanelSize(width,height);

      return true;

  }

 ピクセル列を設定したbiImgは、コンポーネントの再描画(paintComponent()呼び出し)時に、コンポーネント全面に描画します。

  public void paintComponent(Graphics g) {

      g.drawImage(biImg,0,0,this);

  }

 最後に、マウスの処理。マウスカーソルがパネル内部を移動したら、その座標とRGBを文字列化し、アプリケーションのステータスバー文字列設定メソッドsetStatus()を呼び出します。

  public void mouseMoved(MouseEvent e) {

      if (e.getX()>=iWidth || e.getY()>=iHeight)
          return;

      int rgb=pixel[e.getX()+e.getY()*iWidth];

      int b=(rgb >> 16) & 0xff;
      int g=(rgb >> 8) & 0xff;
      int r=rgb & 0xff;

      String mes=" ("+e.getX()+","+e.getY()+")"+":";
      mes=mes+r+","+g+","+b;

      frm.setStatus(mes);

  }

コンポーネントの配置

 ビットマップ表示用のパネルができたので、次にアプリケーションのフレームを作りそこにパネルとステータスバーを配置してみましょう。今回は、JFrameを継承するアプリケーションクラスapp内でJScrollPaneに格納したパネルを左上に、また一番下にステータスバーを配置することにします。

  Container cp=getContentPane();

  bp=new BMPanel(this);

  jp=new JScrollPane(bp,
      ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
      ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

  lState=new JLabel(" ",JLabel.LEFT);

  GridBagLayout gl=new GridBagLayout();
  cp.setLayout(gl);

  GridBagConstraints gc=new GridBagConstraints();

  gc.fill=GridBagConstraints.BOTH;
  gc.weightx=100;
  gc.weighty=100;
  gc.gridx=0;
  gc.gridy=0;
  gc.gridwidth=1;
  gc.gridheight=1;

  cp.add(jp,gc);

  gc.weightx=100;
  gc.weighty=0;
  gc.gridx=0;
  gc.gridy=1;
  gc.gridwidth=1;
  gc.gridheight=1;

  cp.add(lState,gc);

プログラム

javac jpht1.java
java jpht1

 でコンパイル・実行できます(要JDK1.3以上)。実行したら、メニューのOpenBMPファイルを読み込んでください。表示されている画像の上にマウスカーソルを持っていくと、そこの座標とRGBが表示されます。

プログラムソース表示

戻る