前後のフレームを透かしてみる

戻る



 第2回で作ったシンプルACDビュワーに、前後のフレームを重ねて表示する機能を追加しました。MSX版と同様に1つ前のフレームは薄い赤、次のフレームは薄い青で表示します。新たに追加されたボタン「P」「N」で前後のフレームの表示をON/OFFします。
(第2回のプログラムをちょっと直しただけなのに第3回というのもちょっと気が引けるので第2.5回ということにしました)

現在のフレームのみ表示しているところ

前のフレームを透かして表示したところ

次のフレームを透かして表示したところ

前後のフレームを透かして表示したところ

画面の一部のアップ。このように前後の絵を透かして見ることができれば、パラパラアニメ作成がかなり楽になることがおわかりいただけると思います。

・今後の展望

 今回のプログラムにさらに描画機能を追加すればACEDRAWはかなり完成に近づくことになります。

 いままではJavaのGraphics2Dを使用して直線などを描画しようと思っていたのですが、なんか使いにくいので独自の描画ルーチンを作って使おうと思います。
 JavaのGraphics2Dのどこが使いにくいかというと、ANDやORといった論理演算で描画することができなかったり、TYPE_BYTE_BINARYやTYPE_BYTE_INDEXEDでイメージを作ってもインデックス番号を指定して描画することができないところです。

・今回のポイント

 以下のパレットデータと
  private final int paletts[][] = {
    // CPN      CPn      CpN      Cpn      cPN      cPn      cpN      cpn         
    {0x000000,0x000000,0x000000,0x000000,0xffffff,0xffffff,0xffffff,0xffffff}, // cur
    {0xe00000,0xe00000,0x000000,0x000000,0xffb0b0,0xffb0b0,0xffffff,0xffffff}, // cur,prv
    {0x0000e0,0x000000,0x0000e0,0x000000,0xb0c0ff,0xffffff,0xb0c0ff,0xffffff}, // cur,nxt
    {0xd000d0,0xe00000,0x0000e0,0x000000,0xffb0ff,0xffb0b0,0xb0c0ff,0xffffff}  // all
  };

 以下のイメージ更新処理で前後の絵に色を付けて重ねて表示します。
  private void updateImg() {
    int bc,bp,bn;
    int pal,ix;
    int srcp = 0;

    pal = 0;
    if(frmPBtn.isSelected() && (curFrmNo > 0)) pal = pal + 1;
    if(frmNBtn.isSelected() && (curFrmNo < totalFrms-1)) pal = pal + 2;

    for(int y=0; y<imgHeight; y++) {
      for(int x=0; x<imgByteWidth; x++) {
        bc = curFrmImg[srcp];
        bp = prvFrmImg[srcp];
        bn = nxtFrmImg[srcp++];
        for(int i=0; i<8; i++) {
          ix = ((bc >> (7-i)) & 1)*4 + ((bp >> (7-i)) & 1)*2 + ((bn >> (7-i)) & 1);
          mainImg.setRGB(x*8+i,y,paletts[pal][ix]);
        }
      }
    }

    grpPnl.repaint();
  }



・SimpleAcdViewer2のソース

// Java版AceDrawのためのステップ
// 「シンプルACDビュワー2」SimpleAcdViewer2.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.filechooser.*;

public class SimpleAcdViewer2 {
  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 final int paletts[][] = {
    // CPN      CPn      CpN      Cpn      cPN      cPn      cpN      cpn         
    {0x000000,0x000000,0x000000,0x000000,0xffffff,0xffffff,0xffffff,0xffffff}, // cur
    {0xe00000,0xe00000,0x000000,0x000000,0xffb0b0,0xffb0b0,0xffffff,0xffffff}, // cur,prv
    {0x0000e0,0x000000,0x0000e0,0x000000,0xb0c0ff,0xffffff,0xb0c0ff,0xffffff}, // cur,nxt
    {0xd000d0,0xe00000,0x0000e0,0x000000,0xffb0ff,0xffb0b0,0xb0c0ff,0xffffff}  // all
  };

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

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


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

  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 bc,bp,bn;
    int pal,ix;
    int srcp = 0;

    pal = 0;
    if(frmPBtn.isSelected() && (curFrmNo > 0)) pal = pal + 1;
    if(frmNBtn.isSelected() && (curFrmNo < totalFrms-1)) pal = pal + 2;

    for(int y=0; y<imgHeight; y++) {
      for(int x=0; x<imgByteWidth; x++) {
        bc = curFrmImg[srcp];
        bp = prvFrmImg[srcp];
        bn = nxtFrmImg[srcp++];
        for(int i=0; i<8; i++) {
          ix = ((bc >> (7-i)) & 1)*4 + ((bp >> (7-i)) & 1)*2 + ((bn >> (7-i)) & 1);
          mainImg.setRGB(x*8+i,y,paletts[pal][ix]);
        }
      }
    }

    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();
      if(curFrmNo < totalFrms-1) {
        nxtFrmImg = aniFrms.get(curFrmNo+1).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++;
        prvFrmImg = curFrmImg;
        curFrmImg = aniFrms.get(curFrmNo).getImg();
        if(curFrmNo < totalFrms-1) {
          nxtFrmImg = aniFrms.get(curFrmNo+1).getImg();
        }
        updateImg();
        lblFrmIndicate.setText(Integer.toString(curFrmNo+1)+
                               " / "+Integer.toString(totalFrms));
      }
    }
  };

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

  ItemListener frmPBtnLsn = new ItemListener() {
    public void itemStateChanged(ItemEvent e) {
      updateImg();
    }
  };

  ItemListener frmNBtnLsn = new ItemListener() {
    public void itemStateChanged(ItemEvent e) {
      updateImg();
    }
  };

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

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

    JFrame mainFrm = new JFrame("シンプルACDビュワー2");
    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,6,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);

    frmPBtn = new JToggleButton("P");
    frmPBtn.addItemListener(frmPBtnLsn);
    frmNBtn = new JToggleButton("N");
    frmNBtn.addItemListener(frmNBtnLsn);

    frmFBPnl.add(frmBMBtn);
    frmFBPnl.add(frmBBtn);
    frmFBPnl.add(frmFBtn);
    frmFBPnl.add(frmFMBtn);
    frmFBPnl.add(frmPBtn);
    frmFBPnl.add(frmNBtn);

    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) {
    SimpleAcdViewer2 myProg = new SimpleAcdViewer2();
    myProg.initProg();
    myProg.makeFrame();
  }
}