ACE−D−GENプロトタイプ

と、シンプルACDビュワー


戻る


 モノクロお絵かきプログラムも大事なのですが、アニメのファイルフォーマットやフレームコントロール部分を早く固めないとなんか落ち着かない気分になってきたので、先にACE-D-GENのプロトタイプとシンプルACDビュワーを作ることにしました。

・ACE−D−GENプロトタイプ

 ACE−DRAWのデータは拡張子を .ACD にした「ACDファイル」です。このACE−D−GENプロトタイプは、WindowsのBMPファイル複数からACDファイルを作ります。まだACDファイルのフォーマットが本決まりではないのでとりあえず「プロトタイプ」です。
(本決まりではないと言っても、ヘッダサイズなどが変わることはありません)

 起動するとこのページのトップにあるようなウインドウが現れます。「Add file to list」ボタンを押すとファイルチューザーが現れるのでBMPファイルを選んでください。選ばれたBMPファイルがファイルリスト(ウインドウ中央のテキストエリア)に追加されます。
 すべてのBMPファイルを指定したら、「Create ACD file」ボタンを押すとBMPファイルが変換されてACDファイルができます。なお、BMPファイルは必ず320×240ドットのモノクロ2値画像で無ければいけません。ファイルリスト中にそれ以外のBMPファイルがあった場合、ウインドウ左下にその理由が表示されACDファイルには取り込まれません。このページトップの例では TEST3.BMP がモノクロ2値でないために取り込まれなかったことが表示されています。

ACDファイルのフォーマット

 ACDファイルは、モノクロ2値画像を無圧縮でフレーム(1枚の絵)をフレーム数だけ並べた単純な構造です。ただし、最初のフレームは「ヘッダフレーム」と呼ばれる特殊なフレームであり、実際のフレーム数は 有効な絵の枚数+1 となります。
 1フレームのサイズは9728バイトです。したがってファイルサイズは (有効な絵の枚数+1)×9728バイト となります。

※MSX版とは異なるフォーマットです。

ヘッダフレームのフォーマット

 ファイルID、フォーマットバージョン番号、総フレーム数などが記録されています。

オフセット(バイト単位)

内容


備考

0〜3

ファイルID

'ACD\0'


4

フォーマットバージョン(メジャー)

0

暫定仕様なのでバージョン0.1です。

5

フォーマットバージョン(マイナー)

1

6〜7

リザーブ


8〜11

有効フレーム数

1〜16777215

ヘッダフレームを引いたフレーム数です。

12〜9727

リザーブ

ずいぶん広いリザーブ領域です。MSX版ではここにフレームへのポインタ配列があったのですが・・・
いずれ何か有効な情報を入れたいです。


フレームのフォーマット

 シーンの区切りなどに付ける「マーカ」と画像のバイナリイメージからなります。

オフセット(バイト単位)

内容

備考

0

マーカ

0でマーカ無し、1でマーカ有り

1〜127

リザーブ


128〜9727

画像バイナリデータ

1バイト=8ドット、1ライン=80バイト、計240ラインの単純素朴なビットマップデータ。
ドット(ビット)は1=白、0=黒



・シンプルACDビュワー



 とりあえず、ACDファイルを読み込んで1コマずつ表示するプログラムです。使い方は・・・一目瞭然ですね。「<」「>」ボタンで1フレームずつ前後に移動します。「<<」「>>」ボタンは、マーカのついたフレームがあればそこまで進みます(いまのところフレームにマーカを付けられるツールは存在しませんが)

※あくまでもJava版専用です。MSX版のACDファイルは読めません。

サンプルACDファイル

 MSX版用のサンプルアニメデータをJava版用に描き直しました。シンプルACDビュワーでご覧ください。

サンプルACDファイル(acdsampl.zip)をダウンロードする
※アーカイブ内には acdsampl.acd のみ入っています。
このページを見ていない人がダウンロードしても使い方がわからないので、他で再配布しないでください


 ダウンロードしたファイルは+Lhaca、Winzipなどの解凍ソフトで解凍してください。

・今回の感想

 今回一番心配だったのが SimpleAcdViewer.java の以下の部分です。
  private void updateImg() {
    int d,b;
    int srcp = 0;

    for(int y=0; y<imgHeight; y++) {
      for(int x=0; x<imgByteWidth; x++) {
        d = curFrmImg[srcp++];
        for(int i=0; i<8; i++) {
          b = (d >> (7-i)) & 1;
          mainImg.setRGB(x*8+i,y,0xffffff*b);
        }
      }
    }

    grpPnl.repaint();
  }

 こんな風にチマチマと1ビットずつビット→ピクセルの変換を高級言語でやっていてスピード出るのかと。昔のパソコンならこの部分だけでもアセンブラで書きたくなります。(実際MSX版ではここに該当する部分だけマシン語になってます)
 でも作ってみると、軽快にスパスパと画面が切り替わります。本当にJavaとパソコンは速くなったものですね。



ACE−D−GENプロトタイプのソース

// Java版AceDrawのためのステップ
// 「ACE-D-GENプロトタイプ」AceDGenProto.java
// Copyright(c)2007 A.Hiramatsu

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.text.*;
import javax.swing.filechooser.*;

public class AceDGenProto {
  private final int animeFrmSize = 9728;
  private byte[] inBuf = new byte[animeFrmSize];
  private byte[] outBuf = new byte[animeFrmSize];

  private JFileChooser fChoose = new JFileChooser(".");
  private JFrame mainFrm;
  private JTextArea listTxt;
  private JTextField oFnTxt;
  private JTextArea logTxt;

  MouseListener addLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      File f;
      int n,p;
      String s;

      n = fChoose.showOpenDialog(null);
      if(n==JFileChooser.APPROVE_OPTION ) {
        f = fChoose.getSelectedFile();
        if(f != null) {
          s = fChoose.getName(f).toUpperCase();;

          if(s.length()>4) {
            if(s.substring(s.length()-4).equals(".BMP")) {
              listTxt.append(s + "\n");
            } else {
              new JOptionPane().showMessageDialog(mainFrm,"ファイルの拡張子が.BMPではありません","Error",
                                                     JOptionPane.ERROR_MESSAGE);
            }
          }
        }
      }
    }
  };

  MouseListener creLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      boolean canOut,canRead;
      boolean warn;
      FileOutputStream fos = null;
      FileInputStream fis = null;
      String s,oFn;
      int lc,fc;
      int bOfs,eOfs;
      ArrayList<String> fList = new ArrayList<String>();
      int fsize,bmWid,bmHei,bmDataP;

      canOut=false;
      warn=false;

      logTxt.setBackground(Color.WHITE);

      lc = listTxt.getLineCount();
      for(int i=0; i<lc; i++) {
        try {
          bOfs = listTxt.getLineStartOffset(i);
          eOfs = listTxt.getLineEndOffset(i);
          s = listTxt.getText(bOfs,eOfs - bOfs).trim();
        } catch (Throwable se) {
          s = "";
        } 
        if(s.length()>0) fList.add(s);
      }

      if(fList.size()==0) {
        new JOptionPane().showMessageDialog(mainFrm,"ファイルリストが空です","Error",
                                            JOptionPane.ERROR_MESSAGE);
      } else {

        oFn = oFnTxt.getText().trim();
        if(oFn.length()==0) {
          new JOptionPane().showMessageDialog(mainFrm,"出力ファイル名が空です","Error",
                                            JOptionPane.ERROR_MESSAGE);
        } else {
          if(oFn.indexOf('.')<0) {
            oFn = oFn + ".acd";
          }

          try {
            fos = new FileOutputStream(oFn);
            canOut = true;
          } catch(Throwable fe) {
            new JOptionPane().showMessageDialog(mainFrm,"出力ファイルのオープンに失敗しました","Error",
                                                JOptionPane.ERROR_MESSAGE);
          }

          if(canOut) {
            for(int i=0; i<animeFrmSize; i++) outBuf[i]= 0;

            outBuf[0] = (byte)'A';		// ファイルID
            outBuf[1] = (byte)'C';
            outBuf[2] = (byte)'D';
            outBuf[3] = 0;

            outBuf[4] = 0;			// バージョン番号
            outBuf[5] = 1;

            outBuf[6] = 0;			// リザーブ
            outBuf[7] = 0;

            outBuf[8] = (byte)(fList.size() & 0xff);	// フレーム数
            outBuf[9] = (byte)((fList.size() >> 8) & 0xff);
            outBuf[10] = (byte)((fList.size() >> 16) & 0xff);
            outBuf[11] = (byte)((fList.size() >> 24) & 0xff);

            try {
              fos.write(outBuf);
            } catch(Throwable fe) {
              new JOptionPane().showMessageDialog(mainFrm,"出力中にエラーが発生しました","Error",
                                                  JOptionPane.ERROR_MESSAGE);
              canOut = false;
            }


            for(String fn: fList) {
              if(!canOut) break;

              for(int i=0; i<128; i++) outBuf[i]=0;
              for(int i=128; i<animeFrmSize; i++) outBuf[i]=0;

              canRead = false;
              try {
                fis = new FileInputStream(fn);
                canRead=true;
              } catch(Throwable fe) {
                logTxt.append(fn + " : Can't open\n");
                fis = null;
              }

              fsize = 0;
              if(canRead) {
                try {
                  fsize = fis.read(inBuf);
                } catch(Throwable fe) {
                  logTxt.append(fn + " : Can't read\n");
                  canRead = false;
                  warn = true;
                }
              }

              if(canRead) {
                if((fsize<62) || (inBuf[0]!='B') || (inBuf[1]!='M') ||
                   (inBuf[14]!=40) || (inBuf[15]!=0) || (inBuf[16]!=0) || (inBuf[17]!=0)
                ) {
                  logTxt.append(fn + " : Bad BMP file\n");
                  canRead = false;
                  warn = true;
                }

                bmDataP = (inBuf[10] & 0xff) + (inBuf[11] & 0xff) * 256;
                bmWid = (inBuf[18] & 0xff) + (inBuf[19] & 0xff) * 256;
                bmHei = (inBuf[22] & 0xff) + (inBuf[23] & 0xff) * 256;
                if((inBuf[20]!=0) || (inBuf[21]!=0) || (inBuf[24]!=0) ||
                   (inBuf[25]!=0) || (bmWid != 320) || (bmHei != 240)
                ) {
                  logTxt.append(fn + " : Bad BMP size\n");
                  canRead = false;
                  warn = true;
                }

                if((inBuf[28]!=1) || (inBuf[29]!=0)) {
                  logTxt.append(fn + " : Bad BMP colors\n");
                  canRead = false;
                  warn = true;
                }

                if(canRead) {
                  for(int y=239; y>=0; y--) {
                    for(int x=0; x<40; x++) {
                      if(bmDataP < animeFrmSize) {
                        outBuf[y*40+x+128]=inBuf[bmDataP++];
                      }
                    }
                  }

                  if(canOut) {
                    try {
                      fos.write(outBuf);
                    } catch(Throwable fe) {
                      new JOptionPane().showMessageDialog(mainFrm,
                            "出力中にエラーが発生しました","Error",
                            JOptionPane.ERROR_MESSAGE);
                      canOut = false;
                    }
                  }

                  if(fis != null) {
                    try {
                      fis.close();
                    } catch(Throwable fe) {
                      logTxt.append(fn + " : Close error\n");
                    }
                  }
                }
              }
            }

            try {
              fos.flush();
              fos.close();
            } catch(Throwable fe) {
              new JOptionPane().showMessageDialog(mainFrm,
                    "出力ファイルのクローズに失敗しました","Error",
                    JOptionPane.ERROR_MESSAGE);
              canOut = false;
            }
          }

          if(canOut) {
            logTxt.append("Output done.\n");
            if(warn) {
              logTxt.setBackground(Color.YELLOW);
            }
            new JOptionPane().showMessageDialog(mainFrm,
                  "ファイル出力完了しました","Information",
                  JOptionPane.INFORMATION_MESSAGE);
          }
        }
      }
    }
  }; 


  private void makeFrame() {
    JFrame.setDefaultLookAndFeelDecorated(true);
    JDialog.setDefaultLookAndFeelDecorated(true);
    mainFrm = new JFrame("ACE-D-GENプロトタイプ");
    mainFrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


    JLabel listLbl = new JLabel("File list");
    listTxt = new JTextArea(12,64);
    listTxt.setFont(new Font("Monospaced",Font.PLAIN,12));
    JScrollPane listScr = new JScrollPane(listTxt);
    JButton listBtn = new JButton("Add file to list");
    listBtn.addMouseListener(addLsn);
    JPanel listPnl = new JPanel(new BorderLayout(2,2));
    listPnl.setBorder(new LineBorder(Color.BLACK));
    listPnl.add(listLbl,BorderLayout.NORTH);
    listPnl.add(listScr,BorderLayout.CENTER);
    listPnl.add(listBtn,BorderLayout.SOUTH);

    JLabel oFnLbl = new JLabel("Output file name:");
    oFnTxt = new JTextField();
    oFnTxt.setFont(new Font("Monospaced",Font.PLAIN,12));
    JPanel oFnPnl = new JPanel(new BorderLayout(2,2));
    oFnPnl.add(oFnLbl,BorderLayout.WEST);
    oFnPnl.add(oFnTxt,BorderLayout.CENTER);
    logTxt = new JTextArea(4,64);
    logTxt.setFont(new Font("Monospaced",Font.PLAIN,10));
    JScrollPane logScr = new JScrollPane(logTxt);
    JButton creBtn = new JButton("Create ACD File");
    creBtn.addMouseListener(creLsn);
    JPanel crePnl = new JPanel(new BorderLayout(2,2));
    crePnl.add(oFnPnl,BorderLayout.NORTH);
    crePnl.add(logTxt,BorderLayout.CENTER);
    crePnl.add(creBtn,BorderLayout.EAST);

    JPanel mainPnl = new JPanel(new BorderLayout(4,4));
    mainPnl.add(listPnl,BorderLayout.CENTER);
    mainPnl.add(crePnl,BorderLayout.SOUTH);
    mainPnl.setBorder(new EmptyBorder(4,4,4,4));

    mainFrm.getContentPane().add(mainPnl);
    mainFrm.pack();
    mainFrm.setVisible(true);
  }

  public static void main(final String[] args) {
    AceDGenProto myProg = new AceDGenProto();
    myProg.makeFrame();
  }
}

シンプルACDビュワーのソース

// Java版AceDrawのためのステップ
// 「シンプルACDビュワー」SimpleAcdViewer.java
// Copyright(c)2006 A.Hiramatsu

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.border.*;
import javax.swing.filechooser.*;

public class SimpleAcdViewer {
  private final int imgWidth = 320;
  private final int imgByteWidth = imgWidth / 8;
  private final int imgHeight = 240;
  private final int animeFrmSize = 9728;
  private final int animeFrmImgBytes = imgByteWidth * imgHeight;
  private final int animeFrmHeadSize = 128;

  private class AnimeFrame {
    boolean mark;
    byte[] img;

    private AnimeFrame() {
      this.mark = false;
      this.img = new byte[animeFrmImgBytes];
    }

    private void setMark(boolean b) {
      this.mark = b;
    }

    private boolean getMark() {
      return this.mark;
    }

    private void setImg(byte[] src) {
      int n = src.length;

      if(n > animeFrmImgBytes) n = animeFrmImgBytes;

      for(int i=0; i<n; i++) {
        this.img[i] = src[i];
      }
    }

    private byte[] getImg() {
      return this.img;
    }
  };

  private ArrayList<AnimeFrame> aniFrms = new ArrayList<AnimeFrame>();

  private byte[] fileHeadBuf = new byte[animeFrmSize];
  private byte[] frameHeadBuf = new byte[animeFrmHeadSize];
  private byte[] frameImgBuf = new byte[animeFrmImgBytes];

  private byte[] curFrmImg;
  private int curFrmNo;
  private int totalFrms;


  private JFrame mainFrm;
  private JFileChooser fChoose = new JFileChooser(".");
  private JLabel lblFrmIndicate;

  private BufferedImage mainImg = new BufferedImage(imgWidth,imgHeight,
                                            BufferedImage.TYPE_INT_RGB);

  private JPanel grpPnl = new JPanel() {
    public void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.drawImage(mainImg,0,0,this);
    }
  };

  private void updateImg() {
    int d,b;
    int srcp = 0;

    for(int y=0; y<imgHeight; y++) {
      for(int x=0; x<imgByteWidth; x++) {
        d = curFrmImg[srcp++];
        for(int i=0; i<8; i++) {
          b = (d >> (7-i)) & 1;
          mainImg.setRGB(x*8+i,y,0xffffff*b);
        }
      }
    }

    grpPnl.repaint();
  }
  

  private void doFileOpen() {
    File f;
    int n,p;
    int rlen = 0;
    long fsize;
    String s;
    boolean canRead = false;
    FileInputStream fi = null;
    AnimeFrame aniFrm;

    n = fChoose.showOpenDialog(null);
    if(n==JFileChooser.APPROVE_OPTION ) {
      f = fChoose.getSelectedFile();
      if(f != null) {
        s = fChoose.getName(f).toUpperCase();
        canRead = true;

        if(s.length()>4) {
          if(s.substring(s.length()-4).equals(".ACD")) {
            try {
              fi = new FileInputStream(f);
            } catch(Throwable fe) {
              new JOptionPane().showMessageDialog(mainFrm,"ファイルのオープンに失敗しました","Error",
                                                  JOptionPane.ERROR_MESSAGE);
              canRead = false;
            }

            if(canRead) {
              try {
                rlen = fi.read(fileHeadBuf);
              } catch(Throwable fe) {
                new JOptionPane().showMessageDialog(mainFrm,"ファイルの入力中にエラーが発生しました","Error",
                                                    JOptionPane.ERROR_MESSAGE);
                canRead = false;
              }
            }

            if(canRead) {
              fsize = f.length();

              totalFrms = ((int)fileHeadBuf[8] & 0xff) + 
                            (((int)fileHeadBuf[9] & 0xff) << 8) +
                            (((int)fileHeadBuf[10] & 0xff) << 16);

              curFrmNo = 0;
              aniFrms.clear();

              lblFrmIndicate.setText(Integer.toString(curFrmNo+1)+
                                     " / "+Integer.toString(totalFrms));

              if((rlen < animeFrmSize) ||
                 (fileHeadBuf[0] != 0x41) || (fileHeadBuf[1] != 0x43) || 
                 (fileHeadBuf[2] != 0x44) || (fileHeadBuf[3] != 0x00) ||
                 (totalFrms < 1) || (fileHeadBuf[11] != 0) ||
                 (fsize != (long)animeFrmSize * (totalFrms + 1))           ){
                new JOptionPane().showMessageDialog(mainFrm,"ファイルヘッダが壊れています","Error",
                                                    JOptionPane.ERROR_MESSAGE);
                canRead = false;
              }

              if(canRead) {
                for(int i=0; i < totalFrms; i++) {

                  try {
                    fi.read(frameHeadBuf);
                    fi.read(frameImgBuf);
                  } catch(Throwable fe) {
                    new JOptionPane().showMessageDialog(mainFrm,"ファイルの入力中にエラーが発生しました","Error",
                                                        JOptionPane.ERROR_MESSAGE);
                    canRead = false;
                  }
                  if(!canRead) break;

                  aniFrm = new AnimeFrame();
                  if(frameHeadBuf[0]!=0) aniFrm.setMark(true);
                  aniFrm.setImg(frameImgBuf);
                  aniFrms.add(aniFrm);
                }
              }
            }

          } else {
            new JOptionPane().showMessageDialog(mainFrm,"ファイルの拡張子が.ACDではありません","Error",
                                                  JOptionPane.ERROR_MESSAGE);
            canRead = false;
          }
        }
      }
    }

    if(canRead) {
      curFrmImg = aniFrms.get(curFrmNo).getImg();
      updateImg();
    }
  }

  private ActionListener actLsn = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      if(e.getActionCommand()=="Open") {
        doFileOpen();

      } else if(e.getActionCommand()=="eXit") {
        System.exit(0);

      } else {
      }
    }
  }; 

  MouseListener frmFBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      if(curFrmNo < totalFrms-1) {
        curFrmNo++;
        curFrmImg = aniFrms.get(curFrmNo).getImg();
        updateImg();
        lblFrmIndicate.setText(Integer.toString(curFrmNo+1)+
                               " / "+Integer.toString(totalFrms));
      }
    }
  };

  MouseListener frmBBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      if(curFrmNo > 0) {
        curFrmNo--;
        curFrmImg = aniFrms.get(curFrmNo).getImg();
        updateImg();
        lblFrmIndicate.setText(Integer.toString(curFrmNo+1)+
                               " / "+Integer.toString(totalFrms));
      }
    }
  };

  MouseListener frmFMBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      int frmsv = curFrmNo;

      if((curFrmNo < totalFrms-1) && (aniFrms.get(curFrmNo).getMark())) {
        curFrmNo++;
      }

      while(curFrmNo < totalFrms-1) {
        if(! aniFrms.get(curFrmNo).getMark()) {
          curFrmNo++;
        }
      }

      if(curFrmNo != frmsv) {
        curFrmImg = aniFrms.get(curFrmNo).getImg();
        updateImg();
        lblFrmIndicate.setText(Integer.toString(curFrmNo+1)+
                               " / "+Integer.toString(totalFrms));
      }
    }
  };

  MouseListener frmBMBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      int frmsv = curFrmNo;

      if((curFrmNo > 0) && (aniFrms.get(curFrmNo).getMark())) {
        curFrmNo--;
      }

      while(curFrmNo > 0) {
        if(! aniFrms.get(curFrmNo).getMark()) {
          curFrmNo--;
        }
      }

      if(curFrmNo != frmsv) {
        curFrmImg = aniFrms.get(curFrmNo).getImg();
        updateImg();
        lblFrmIndicate.setText(Integer.toString(curFrmNo+1)+
                               " / "+Integer.toString(totalFrms));
      }
    }
  };

  private void initProg() {
    curFrmImg = new byte[animeFrmImgBytes];
    for(int i=0; i < curFrmImg.length;i++) {
      curFrmImg[i] = (byte)0xff;
    }
  }

  private void makeFrame() {
    JFrame.setDefaultLookAndFeelDecorated(true);
    JDialog.setDefaultLookAndFeelDecorated(true);

    JFrame mainFrm = new JFrame("シンプルACDビュワー");
    mainFrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JPanel mainPnl = new JPanel(new BorderLayout(4,4));
    JMenuBar mainMenu = new JMenuBar();

    grpPnl.setPreferredSize(new Dimension(imgWidth,imgHeight));

    JPanel frmFBPnl = new JPanel(new GridLayout(1,4,2,2));
    JButton frmBMBtn = new JButton("<<");
    frmBMBtn.addMouseListener(frmBMBtnLsn);
    JButton frmBBtn = new JButton("<");
    frmBBtn.addMouseListener(frmBBtnLsn);
    JButton frmFBtn = new JButton(">");
    frmFBtn.addMouseListener(frmFBtnLsn);
    JButton frmFMBtn  = new JButton(">>");
    frmFMBtn.addMouseListener(frmFMBtnLsn);
    frmFBPnl.add(frmBMBtn);
    frmFBPnl.add(frmBBtn);
    frmFBPnl.add(frmFBtn);
    frmFBPnl.add(frmFMBtn);

    JPanel frmInfoPnl = new JPanel(new BorderLayout(2,2));
    lblFrmIndicate = new JLabel("0 / 0");
    JLabel lblFrmFrame = new JLabel("Frame:");
    frmInfoPnl.add(lblFrmFrame,BorderLayout.WEST);
    frmInfoPnl.add(lblFrmIndicate,BorderLayout.CENTER);

    JPanel frmCtrlPnl = new JPanel(new BorderLayout(8,4));
    frmCtrlPnl.add(frmInfoPnl,BorderLayout.NORTH);
    frmCtrlPnl.add(frmFBPnl,BorderLayout.CENTER);

    mainPnl.add(grpPnl,BorderLayout.CENTER);
    mainPnl.add(frmCtrlPnl,BorderLayout.SOUTH);
    mainPnl.setBorder(new EmptyBorder(4,4,4,4));

    JMenu fileMenu = new JMenu("File");
    fileMenu.setMnemonic(KeyEvent.VK_F);
    JMenuItem fileOpen = new JMenuItem("Open",KeyEvent.VK_O);
    fileOpen.addActionListener(actLsn);
    JMenuItem fileeXit = new JMenuItem("eXit",KeyEvent.VK_X);
    fileeXit.addActionListener(actLsn);
    fileMenu.add(fileOpen);
    fileMenu.add(fileeXit);
    mainMenu.add(fileMenu);

    mainFrm.getContentPane().add(mainPnl);
    mainFrm.setJMenuBar(mainMenu);
    mainFrm.pack();
    mainFrm.setVisible(true);
    updateImg();
  }

  public static void main(final String[] args) {
    SimpleAcdViewer myProg = new SimpleAcdViewer();
    myProg.initProg();
    myProg.makeFrame();
  }
}