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ファイルは、モノクロ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ラインの単純素朴なビットマップデータ。 |
MSX版用のサンプルアニメデータをJava版用に描き直しました。シンプルACDビュワーでご覧ください。
今回一番心配だったのが 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(); } |
// 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(); } } |
// 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(); } } |