MicroPythonで、ひとコマアニメーション

[X68000 Z](https://www.zuiki.co.jp/x68000z/)の[Micro Python](https://dnjiro.hatenablog.com/entry/micropython)で、ひとコマアニメーションアプリ「まいくろえほん」を作成したので、紹介します。

# ひとコマアニメーションとは

- 2018年[キッズプラザ大阪コンピュータ工房](https://www.kidsplaza.or.jp/gallery/2020/000405/)ワークショップで子供むけに開発された、アニメ制作手法です。１コマ絵を描くだけで[ホワイトボードアニメーション](https://tsuta-world.com/lp/vau/whiteboard_anime/)が作れます。[出典はこちら](https://9vae.blogspot.com/p/blog-page_15.html)

![1koma-mac.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/7bdbd338-1cfb-5453-7edc-bcb3a43b2999.gif)

幼稚園の子供でもできるので、[キッズプラザ大阪](https://www.kidsplaza.or.jp/gallery/2020/000405/)では、毎日 150-200本のひとコマアニメーションが作られています

# ひとコマアニメーションが作れるアプリ

- [うるさいえほん](http://www.kinet-tv.ne.jp/~minor/to_onep.html)
- まいくろえほん
- [9VAeきゅうべえ](https://9vae.blogspot.com/p/9vae-download.html)
- [PEAS motch!](https://9vae.blogspot.com/p/9vae.html)

うるさいえほんは、JavaScriptで記述された、音声合成つきひとコマアニメーションアプリです。[使い方、説明はこちら](https://qiita.com/uminor/items/885a0c33be4552038418)

まいくろえほんは、うるさいえほんを、[X68000 Z で再現しよう](https://dnjiro.hatenablog.com/entry/micropython)とつくったアプリです。

[9VAeきゅうべえ](https://qiita.com/danjiro/items/253e5a33a38599098274)は、ひとコマアニメの元になった無料のモーショングラフィックスアプリです。

PEAS motch!は、キッズプラザ大阪で使われているアプリで、9VAeから作られました。


# まいくろえほん

 [うるさいえほん](http://www.kinet-tv.ne.jp/~minor/to_onep.html)に掲載されているひまわり.onep を再生した結果です。作者は、[ごっほこはま(2022)](https://webcon-kobe.jp/2022/anm/c/005/cc.htm)

![ひまわり2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/5582b212-0f2f-df56-de4b-a811c501afed.png)


## 仕様

![popmenu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/b4beb290-f9e2-b9d7-e9a0-01f2dc30ccc8.png)

- メニュー外をクリックしたあと描画できます。線をひくと音が鳴ります。Runで再生
- RYGCBMW：描画色設定（赤黄緑水色青紫白）
- 数字: ペンの太さ。９以上は〇で描きます
- HSV：描画色の調整。マウスカーソルが設定された色。
- <-- -->: アプリと同じフォルダに OnePファイルがある場合、メニューに表示し、クリックで再生できます。そのファイルの切り替え。上のひまわりの例では、ONEP-HAN.ONE, MUKASHI.ONE, HIMAWARI.ONE というOnePファイルが同じフォルダにあります。
- Quit：描画した絵をout.oneファイルに保存して終了。
- X68000 は描画性能が低いため、太い線を４本の直線または円で表現しています。

## MicroPython版「まいくろえほん」 ソースコード
X68000 Z または、エミュレータXM6で動きます。描画、音の発声に x68k.iocs関数を使っています。[x68k.iocs関数の説明はこちら](https://dnjiro.hatenablog.com/entry/micropython)

```py:ehon.py
import sys
import os
from x68k import *
from struct import pack

iocs(i.OS_CUROF)   #Text Cursor OFF
#crtmod(14,1)      #255x255x64kColor
crtmod(12,1)       #512x512x64kColor
iocs(i.MS_CURON)   #Mouse Cursor ON
iocs(i.SKEY_MOD,0) #SoftKeyboard OFF
g=GVRam(0)         #Graphic
tx=TVRam()         #Text VRam


SAVFILE="out.one"
TMPFILE="out.###"

stIS = -1
stSaved = -1
strk = []   # line (x,y)
strc = []   # line color
onep = []   # oneP file name
sx0,sy0 = -999,-999
oneS=0 # oneP start

wrH,wrS,wrV=128,31,15
wrtCol=iocs(i.HSVTORGB,(wrH<<16)|(wrS<<8)|wrV)
wrtW=2

colHue =(0,32,64,96,128,160,0) #RYGCBMR




def fnQuit(x):
  drawMenu(0,0)
  fnSave(x)
  iocs(i.OS_CURON)    #Text Cursor ON
  crtmod(16,1)        #768x512x16
  iocs(i.SKEY_MOD,-1) #SoftKeyboard ON
  iocs(i.B_CLR_ST,2)  #Screen Reset
  sys.exit()


def opm_init():
    opmdrv = iocs(i.B_LPEEK, a1=(0x000400 + 4 * 0xf0))
    if opmdrv < 0 or (opmdrv >= 0xfe0000 and opmdrv <= 0xffffff):
        iocs(i.OS_CURON)    #Text Cursor ON
        crtmod(16,1)        #768x512x16
        iocs(i.SKEY_MOD,-1) #SoftKeyboard ON
        iocs(i.B_CLR_ST,2)  #Screen Reset
        print("OPMDRV is not installed")
        sys.exit()
    iocs(i.OPMDRV, 0x00)

def opm_alloc(size):
    iocs(i.OPMDRV, 0x01, (1 << 16) | size)
    #iocs(i.OPMDRV, 0x01, (2 << 16) | size)
    #iocs(i.OPMDRV, 0x01, (3 << 16) | size)
    iocs(i.OPMDRV, 0x02, (1 << 16) | 1) #assign 1
    #iocs(i.OPMDRV, 0x02, (2 << 16) | 2) #assign 2
    #iocs(i.OPMDRV, 0x02, (3 << 16) | 3) #assign 3

def opm_free():
    ret = 0
    ret |= iocs(i.OPMDRV, 0x07, 1)
    #ret |= iocs(i.OPMDRV, 0x07, 2)
    #ret |= iocs(i.OPMDRV, 0x07, 3)
    return ret

def opm_trk(trk, mml):
    mml += "\x00"
    iocs(i.OPMDRV, 0x06, trk, a1=mml)


opm_init()
opm_alloc(1000)
iocs(i.OPMDRV, 0x05, 200) #tempo 32-200
mml='@65A'
opm_trk(1,mml)


def setPal():
  global wrtCol,wrH,wrS,wrV
  wrtCol=iocs(i.HSVTORGB,((wrH%192)<<16)|((wrS%32)<<8)|(wrV%32))
  iocs(i.TPALET,8,wrtCol) #Mouse Color
  if wrtCol&0xf800 > 0xc800:
    iocs(i.TPALET,7,0x30) #Mouse Edge
  else:
    iocs(i.TPALET,7,0xfffe)



def fnRGBW(x):
  global wrtCol,wrH,wrS,wrV
  wrS,wrV=31,31
  if x/8<6:
    wrH=colHue[int(x/8)]
  else:
    wrS=0
  setPal()

def fnHue(x):
  global wrH
  if len(onep)>0:
    fnRun(-1)
    return
  if x/8<4:
    wrH-=8
  else:
    wrH+=8
  setPal()

def fnSat(x):
  global wrS
  if len(onep)>1:
    fnRun(-2)
    return
  if x/8<4:
    wrS-=4
  else:
    wrS+=4
  setPal()

def fnVal(x):
  global wrV
  if len(onep)>2:
    fnRun(-3)
    return
  if x/8<4:
    wrV-=4
  else:
    wrV+=4
  setPal()


def getPal(col):
    rr=int("0x"+col[0:2])
    gg=int("0x"+col[2:4])
    bb=int("0x"+col[4:6])
    return ((gg<<8)&0xf800)|((rr<<3)&0x07c0)|((bb>>2)&0x3e)

def getRGB(cc):
    rr=(cc & 0x7c0)>>3
    gg=(cc & 0xf800)>>8
    bb=(cc & 0x3e)<<2
    return '#{:02x}{:02x}{:02x}'.format(rr,gg,bb)



def drawLine(lin):
    global mml

    x=lin.find('width=')
    wid=1
    if x>0:
      y=lin[x+7:].find('\"')
      wid=int(float(lin[x+7:x+7+y])/2)

    x=lin.find('stroke=')
    pal=15
    if x>0:
      y=lin[x:].find('#')
      x+=y+1
      pal=getPal(lin[x:x+6])    

    y=lin.find('d="M')
    x=y+4
    x0,y0=-999,-999
    while 1:
      y=lin[x:].find(',')
      xx=int(float(lin[x:x+y]))
      x+=y+1
      y=lin[x:].find('L')
      if y<0:
        return 0
      yy=int(float(lin[x:x+y]))
      x+=y+1
      if x0!=-999 or y0!=-999:
        if wid>8:
          g.circle(x0,y0,wid,pal,0,360,388)
        else:
          g.line(x0,y0,xx-wid,yy,pal)
          if wid>0:
            g.line(x0,y0,xx,yy-wid,pal)
            g.line(x0,y0,xx+wid,yy,pal)
            g.line(x0,y0,xx,yy+wid,pal)        
        iocs(i.OPMDRV, 0x08, pack('L',1), 0,0) #play 1ch
      x0,y0=xx,yy
      btn = iocs(i.MS_GETDT)&0xff #R-btn
      if btn>0:
         return 1


def fnRun(x):
  g.wipe()
  if x<0:
    drawMenu(0,0)
    g.fill(0,0,512,512,wrtCol)
    fn=onep[-x-1+oneS]
    print(fn)
  else:
    fn=SAVFILE

  try:
    with open(fn) as f:
      for line in f:
        if '</page' in line:
          break       
        if '<path' in line:  
          brk=drawLine(line)
          if brk>0:
            break
  except:
    pass
  drawMenu(mx,my)


def fnSave(x):
  global mx,my,stIS,stSaved
  if stIS < 0:
     return
  if stIS<=stSaved:
     return
  to = open(TMPFILE,'w')
  to.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n')
  to.write('<onep viewBox="0 0 512 512">\n')
  to.write('<page>\n')
  wrt=0
  try:
    with open(SAVFILE) as f:
      for line in f:
        if '</page' in line:
           break
        if wrt>0:
           to.write(line) 
        if '<page' in line:
           wrt=1
  except:
    pass
  for st in range(stIS+1):
     if stIS<=stSaved:
       continue
     to.write( strc[st] +' d="'+strk[st]+'" />\n')
  stSaved = stIS
  to.write('</page>\n')
  to.write('</onep>\n')
  to.close()
  try:
    os.remove(SAVFILE)
  except:
    pass
  os.rename(TMPFILE,SAVFILE)

def fnSaveRun(x):
  drawMenu(0,0) #menu off
  fnSave(x)
  fnRun(x)

def fnSelect(x):
  global oneS
  if x/8<4:
    if oneS>0:
      oneS-=1
  else:
    if oneS+3<len(onep):
      oneS+=1
  drawMenu(0,0)
  drawMenu(mx,my)

def fnWidth(x):
  global wrtW
  if x/8<4:
    if wrtW>=2:
      wrtW=int(wrtW/2)
  else:
    if wrtW<=32:
      wrtW*=2
  drawMenu(0,0)
  drawMenu(mx,my)

#-------------------
menu=('RYGCBMW','-  %d  +','-  H  +','-  S  +','-  V  +','<-- -->','Run','Quit')
func=(fnRGBW   ,fnWidth   ,fnHue   ,fnSat   ,fnVal      ,fnSelect  ,fnSaveRun,fnQuit)
#-------------------

files=[f for f in os.listdir(".")]
for fn in files:
  if fn.find(SAVFILE)>=0:
    continue
  if fn.find('.one')>0 or fn.find('.ONE')>0:
    onep.append(fn)

mnOn=0
mh=len(menu)*16
mw=0
for it in menu:
  if len(it)>mw:
     mw=len(it)
for it in onep:
  if len(it)>mw:
     mw=len(it)
mw *= 8
mx,my=100,100

def drawMenu(x,y):
  global mnOn,mx,my
  if mnOn>0:
      mnOn=0
      iocs(i.B_CLR_ST,2)
      return
  
  if x+mw>511:
    x=511-mw
  if y+mh>511:
    y=511-mh
  iocs(i.TPALET,2,0x1e) 
  mnOn,mx,my = 1,x-(x%8), y-(y%16)
  tx.box(0,mx-4,my-4,mw+8,mh+8)
  y=int(my/16)
  z=0
  for it in menu:
      iocs(i.B_LOCATE,int(mx/8),y)
      if z==1:
        print("- ",wrtW," +")
      elif z==5 and len(onep)==0:
        print("No OneP")
      elif z>=2 and z-2<len(onep) and z<=4:
        print(onep[z-2+oneS]) #File Name
      else: 
        print(it)
      y+=1
      z+=1
  tx.rev(1,mx-4,my-4,mw+8,mh+8)

def execMenu(x,y):
  global mnOn,mx,my #for Menu
  if mnOn==0 or x<mx or x>mx+mw or y<my or y>my+mh:
     return 0
  if y-my<len(func)*16:
    func[int((y-my)/16)](x-mx) 
  return 1


def addLine():
  global strk,stIS,wrtW
  strk.append('x')
  strc.append('  <path stroke="'+ getRGB(wrtCol)+'"' +' width="'+str(wrtW)+'"' )
  stIS +=1
  sx0,sy0 = -999,-999

def addPoint(x,y):
  global sx0,sy0,wrtCol,wrtW
  if abs(x-sx0)<3 and abs(y-sy0)<3:
     return
  if strk[stIS][0]!='M':
     strk[stIS]='M'+str(x)+','+str(y)
  else:
     strk[stIS]=strk[stIS]+'L'+str(x)+","+str(y)
     if wrtW>8:
       g.circle(sx0,sy0,wrtW,wrtCol,0,360,388)
     else:
       g.line(sx0,sy0,x-wrtW,y,wrtCol)
       g.line(sx0,sy0,x,y-wrtW,wrtCol)
       g.line(sx0,sy0,x+wrtW,y,wrtCol)
       g.line(sx0,sy0,x,y+wrtW,wrtCol)
     iocs(i.OPMDRV, 0x08, pack('L',1), 0,0) #play 1ch
  sx0,sy0 = x,y
  

# Main Loop
mnOn=1
fnRun(0)
drawMenu(100,100)

bt0=0
while 1:
  pos = iocs(i.MS_CURGT)
  x = (pos >>16)&0xffff
  y = pos & 0xffff
  btn = iocs(i.MS_GETDT)&0xffff
  if btn==bt0:
    if mnOn==0 and btn & 0xff00: #left Down
      addPoint(x,y)
    continue
  bt0=btn
  if btn & 0xff: #right
    drawMenu(x,y)
  if btn & 0xff00: #left
    if execMenu(x,y):
      continue
    if mnOn:
      drawMenu(x,y)
    addLine()


iocs(i.OS_CURON)
input("End")
crtmod(16,1)
iocs(i.SKEY_MOD,-1)
iocs(i.B_CLR_ST,2)

```

|関数|処理内容|引数|
|:--:|:--|:--|
|fnQuit|終了処理||
|opm_init|FM音源初期化||
|opm_alloc|FM音源トラック用メモリ確保||
|opm_free|FM音源メモリ解放||
|opm_trk|音の追加|mml=音|
|setPal|テキストグラフィックのパレット設定||
|fnRGBW|色の設定|x=クリック水平位置|
|fnHue|色合い設定|x=クリック水平位置|
|fnSat|彩度設定|x=クリック水平位置|
|fnVal|明度設定|=クリック水平位置|
|getPal|線の色からX68Kの色への変換|col=RRGGBB文字|
|getRGB|X68K色からRGB文字列への変換|cc=X68K色|
|drawLine|線を描く|lin=<path文字列|
|fnRun|再生||
|fnSave|ファイル保存||
|fnSaveRun|保存して再生||
|drawMenu|ポップアップメニュー表示|(x,y)表示位置|
|execMenu|メニュー項目の実行|(x,y)クリックした位置|
|addLine|線のタグを１つ追加||
|addPoint|線の座標点を追加し、描画も行う|(x,y)座標|

## 実行のしかた

1. [X68000 Z ](https://www.zuiki.co.jp/x68000z/)または、X68Kエミュレータが必要です。
1. Micro Python用SDカードを作ります。[作り方はこちら](https://dnjiro.hatenablog.com/entry/micropython)。automount.xdfというフロッピーディスクイメージを作ります。
1. 上のソースコードを ehon.py ファイルに保存し、automount.xdf の中にいれます。
1. X68000 Z のコマンドプロンプトから以下の命令を実行します。
```bat
micropyt ehon.py
```
音を出すには、OPMDRV をいれておく必要があります。


# OneP データ解説

- ひとコマアニメーション（One Picture Animation）を保存するXMLテキストフォーマットです。拡張子は「.onep」または「.one」
- [うるさいえほん](http://www.kinet-tv.ne.jp/~minor/to_onep.html)の「はっぴょう」にサンプルデータがあります。「むかしばなし」がおすすめ
- 再生したあと「ファイル」「ファイルにほぞん」でダウンロードできます。
- OnePデータは[9VAeきゅうべえ](https://9vae.blogspot.com/p/9vae-download.html)で読み込み、保存ができます。
- 文字コードは UTF8 ですが、まいくろえほんは ShiftJIS 

## サンプルデータ（はあと）

[うるさいえほん](http://www.kinet-tv.ne.jp/~minor/to_onep.html)の「はっぴょう」にある「はあと.onep」です

![onep-hana.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/5131f7b3-12f0-0c51-e332-03595f35fe71.gif)


```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<onep viewBox="0 0 640 480">
    <page>
        <path stroke="#000000" width="10" d="M312.33,332L308.33,332L307.33,332L297.33,332L281.33,329L259.33,318L232.33,297L212.33,273L205.33,252L205.33,236L209.33,221L215.33,212L225.33,207L234.33,207L245.33,208L256.33,216L269.33,225L279.33,232L282.33,235L287.33,230L300.33,218L316.33,206L339.33,199L353.33,199L361.33,199L365.33,202L367.33,210L368.33,228L365.33,249L356.33,266L341.33,286L331.33,300L326.33,306L322.33,312L319.33,316L315.33,321L309.33,327L307.33,332L305.33,334L304.33,334"/>
        <path stroke="#ff0000" width="50" d="M307.33,372L307.33,372L304.33,370L294.33,368L280.33,366L262.33,362L255.33,360L250.33,357L244.33,355L237.33,353L230.33,350L221.33,346L213.33,342L209.33,341L205.33,338L202.33,334L197.33,332L191.33,325L186.33,318L181.33,311L175.33,305L174.33,301L171.33,298L169.33,292L167.33,286L163.33,280L161.33,272L160.33,266L157.33,260L156.33,251L155.33,245L155.33,238L155.33,231L155.33,222L155.33,216L156.33,208L157.33,204L159.33,197L163.33,190L167.33,183L171.33,178L172.33,176L177.33,172L180.33,168L184.33,164L189.33,162L195.33,158L199.33,156L201.33,154L203.33,153L206.33,152L210.33,151L216.33,151L220.33,151L222.33,151L226.33,151L231.33,151L237.33,155L239.33,156L244.33,159L246.33,161L250.33,162L255.33,164L257.33,166L261.33,167L262.33,168L263.33,170L265.33,170L268.33,171L270.33,172L273.33,173L279.33,175L282.33,179L283.33,180L284.33,180L286.33,178L295.33,175L305.33,171L314.33,168L326.33,165L338.33,162L349.33,158L358.33,156L362.33,155L367.33,154L369.33,154L369.33,154L371.33,154L373.33,154L379.33,154L388.33,154L396.33,158L399.33,159L403.33,162L408.33,164L411.33,169L415.33,176L419.33,183L421.33,189L422.33,195L422.33,203L422.33,211L422.33,217L422.33,225L422.33,231L421.33,237L419.33,246L417.33,252L413.33,259L411.33,267L408.33,272L406.33,278L405.33,283L401.33,287L399.33,291L395.33,298L392.33,302L387.33,310L383.33,315L381.33,319L377.33,325L373.33,329L370.33,334L365.33,340L361.33,346L358.33,350L352.33,357L346.33,362L341.33,368L337.33,370L332.33,373L326.33,375L323.33,376L323.33,376L321.33,377L321.33,378"/>
        <path stroke="#29ad63" width="1" d="M314.33,373L311.33,371L307.33,370L301.33,369L297.33,368L293.33,368L287.33,367L281.33,367L275.33,366L272.33,365L268.33,364L263.33,362L259.33,361L253.33,359L251.33,357L244.33,350L237.33,344L230.33,338L220.33,332L213.33,328L209.33,326L206.33,324L205.33,324L202.33,322L200.33,321L197.33,320L193.33,318L191.33,316L188.33,315L185.33,313L183.33,312L181.33,307L177.33,300L173.33,293L171.33,286L167.33,279L164.33,271L161.33,262L160.33,256L158.33,250L157.33,242L157.33,237L157.33,231L157.33,225L157.33,220L157.33,214L157.33,210L157.33,204L157.33,198L159.33,193L161.33,186L162.33,182L165.33,176L167.33,174L172.33,169L174.33,164L178.33,163L181.33,158L185.33,158L187.33,157L191.33,155L192.33,154L195.33,154L199.33,153L203.33,153L208.33,153L216.33,153L221.33,153L226.33,154L230.33,155L235.33,156L239.33,158L243.33,160L247.33,161L250.33,163L255.33,166L259.33,168L260.33,170L261.33,172L262.33,173L265.33,175L267.33,176L268.33,177L269.33,180L271.33,180L273.33,182L273.33,182L274.33,183L276.33,184L277.33,184L277.33,184L278.33,185L279.33,185L279.33,186L282.33,186L283.33,188L284.33,190L284.33,191L284.33,194L282.33,196L281.33,197L280.33,198L279.33,198L278.33,198L275.33,194L275.33,194L275.33,193L275.33,189L278.33,186L285.33,182L290.33,180L295.33,178L297.33,177L298.33,176L302.33,176L307.33,175L313.33,172L318.33,171L323.33,170L327.33,169L332.33,167L336.33,166L339.33,164L343.33,164L348.33,163L350.33,163L356.33,162L361.33,161L367.33,160L369.33,160L373.33,160L376.33,160L381.33,160L388.33,164L392.33,164L393.33,165L397.33,168L399.33,170L403.33,173L407.33,176L410.33,180L411.33,182L415.33,186L417.33,189L418.33,194L421.33,196L421.33,202L421.33,205L421.33,209L421.33,216L421.33,222L421.33,227L421.33,230L421.33,233L421.33,239L421.33,242L421.33,246L421.33,249L420.33,253L419.33,256L418.33,260L417.33,262L415.33,266L413.33,272L411.33,276L408.33,279L405.33,287L401.33,294L399.33,297L397.33,300L395.33,306L393.33,310L390.33,317L388.33,320L385.33,324L383.33,329L379.33,334L372.33,342L369.33,346L364.33,350L358.33,356L352.33,360L348.33,362L341.33,367L333.33,370L327.33,373L321.33,373L318.33,376L317.33,376L317.33,376L316.33,376L315.33,376L314.33,376L313.33,376L313.33,376L312.33,376L309.33,374L306.33,373L304.33,371L301.33,370"/>
        <path stroke="#2c8bc4" width="40" d="M163.33,369L162.33,369L160.33,369L157.33,370L156.33,372L150.33,375L142.33,382L131.33,388L123.33,392L122.33,393L123.33,394L125.33,394"/>
        <path stroke="#2c8bc4" width="40" d="M216.33,406L217.33,406L218.33,406L216.33,415L208.33,428L203.33,436L203.33,440"/>
        <path stroke="#2c8bc4" width="40" d="M300.33,426L301.33,429L301.33,441L300.33,453L300.33,460L300.33,460L300.33,461L300.33,462"/>
        <path stroke="#2c8bc4" width="40" d="M387.33,406L387.33,406L391.33,406L395.33,410L400.33,418L404.33,426L408.33,434L410.33,437"/>
        <path stroke="#2c8bc4" width="40" d="M430.33,364L431.33,364L435.33,364L441.33,364L450.33,365L471.33,375L485.33,380L489.33,381L489.33,381L491.33,381L491.33,381"/>
        <path stroke="#2c8bc4" width="40" d="M473.33,298L473.33,297L474.33,296L479.33,296L485.33,296L497.33,293L513.33,290L521.33,288"/>
        <path stroke="#2c8bc4" width="40" d="M483.33,218L483.33,216L486.33,216L490.33,216L497.33,214L511.33,209L526.33,202L539.33,195L543.33,193L542.33,193L539.33,193"/>
        <path stroke="#2c8bc4" width="40" d="M472.33,149L471.33,148L475.33,146L484.33,141L493.33,137L499.33,135"/>
        <path stroke="#2c8bc4" width="40" d="M422.33,103L423.33,103L423.33,102L431.33,96L445.33,82L454.33,72L454.33,72"/>
        <path stroke="#2c8bc4" width="40" d="M363.33,98L363.33,97L363.33,94L361.33,86L357.33,69L353.33,56L352.33,51L351.33,51"/>
        <path stroke="#2c8bc4" width="40" d="M293.33,113L293.33,113L293.33,112L292.33,106L292.33,97L292.33,82L292.33,70L292.33,58L292.33,52"/>
        <path stroke="#2c8bc4" width="40" d="M209.33,91L209.33,88L209.33,78L197.33,57L191.33,46L191.33,44"/>
        <path stroke="#2c8bc4" width="40" d="M159.33,110L155.33,108L145.33,100L135.33,92L126.33,86L121.33,84L120.33,84L119.33,84"/>
        <path stroke="#2c8bc4" width="40" d="M113.33,167L112.33,169L108.33,169L105.33,169L102.33,168L97.33,166L86.33,161L64.33,153L56.33,150L53.33,149L53.33,149L53.33,149L54.33,149L55.33,149"/>
        <path stroke="#2c8bc4" width="40" d="M91.33,215L89.33,216L87.33,218L85.33,219L85.33,220L79.33,222L61.33,227L37.33,230L23.33,232L18.33,232L18.33,232"/>
        <path stroke="#2c8bc4" width="40" d="M86.33,273L86.33,274L86.33,275L81.33,281L59.33,292L39.33,302L29.33,305L28.33,305L32.33,305L33.33,305"/>
        <path stroke="#2c8bc4" width="40" d="M107.33,324L109.33,324L103.33,328L92.33,334L83.33,338L81.33,339"/>
    </page>
</onep>
```


# OneP フォーマットの拡張計画

- 今後、OnePフォーマットを拡張し、9VAeの全データを保存できるようにしたいと考えています。今、9VAeは、SVG形式で保存していますが、ブラウザで再生できるようにするため、かなり複雑な構造です。それをもっと可読性の高いテキストフォーマットで保存したいと思っています。