ORANGE pico で画面サイズを取得する

[ORANGE pico](http://www.picosoft.co.jp/orange/) には、画面の幅や高さを取得する関数やコマンドが見当たらない。
そのため、モード (ビデオ出力 / TFT液晶横型 / TFT液晶縦型) にかかわらず図形を画面の中心に描画する、などの処理を行うには、工夫が求められる。
今回は、ORANGE pico で工夫して画面の幅や高さを求める方法を考えてみた。

# テキスト画面のサイズを取得する

今回は、テキスト画面をクリア後、画面の端に到達するまでテキストの出力を続けることで、テキスト画面のサイズを取得する。
幅は、改行なしで最初の行に文字を出力し続け、折り返されて2行目に文字が到達することで検出できる。
高さは、最初の行から改行ありで文字を出力し続け、スクロールされて最初の行に出力した文字が消えることで検出できる。

この方法で、ビデオ出力モード、TFT液晶モードそれぞれにおいて正しい幅と高さを取得できた。

```
10 twidth=0:theight=2
20 cls 1
30 print "*";
40 if vpeek(0,1)<>asc("*") then twidth=twidth+1:goto 30
50 cls 1
60 print "*"
70 print "o"
80 if vpeek(0,0)==asc("*") then theight=theight+1:goto 70
90 cls 1
100 print twidth;" x ";theight
```

このプログラムは、[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/deed.ja) でライセンスする。

# グラフィック画面のサイズを取得する

現行バージョンでは、**テキスト画面の幅と高さをそれぞれ8倍**することで、グラフィック画面の幅と高さが求められる。
しかし、将来テキスト画面とは独立したグラフィック画面が登場する可能性に備え、グラフィック画面のサイズを直接求める方法も考えてみた。

## 共通の方針

ある座標が画面内か画面外かを判定する方法を用意する。
その方法を用い、まず画面外に出るまで座標を `1` から2倍していく。
画面外に出たら、二分探索を用いて画面内と画面外の境界を見つける。
これをx座標 (幅) とy座標 (高さ) それぞれについて行う。

## ビデオ出力モード

ビデオ出力モードでは、画面上の点の色を取得する `point` 関数を使用できる。
テキスト画面の文字を取得する `vpeek` 関数は画面外の座標を指定すると「Illegal function call」と出てプログラムの動作が止まってしまうが、`point` 関数は画面外の座標を指定しても `0` が返るだけで実行を続けてくれる。
したがって、描画を行い、`point` 関数で `1` が返ってくるかどうかで、画面内かどうかを判定することができる。

## TFT液晶モード

TFT液晶モードでは、`point` 関数の返り値に描画内容が反映されない。
`sprite` の設定内容は反映されるが、これもビデオ出力モードでの高さである200ピクセルを超えると `0` になってしまうようで、判定には利用できなそうである。

そこで、TFT液晶は描画のコストが高いことを利用し、`line` による描画にかかる時間による画面内外の判別を試みた。
たとえば、以下のコードにより、幅241ピクセルの横線を描画し、処理時間を計測する。

```
10 cltms
20 line 0,0,240,0,&HFFFF
30 print tickms()
```

すると、`3` が出力された。
20行目を、横線のy座標が画面外となる

```
20 line 0,400,240,400,&HFFFF
```

に変更して実行すると、`0` が出力された。
よって、`line` コマンドの処理時間から座標が画面内か否かを判定できそうである。

この方法により、実際にTFT液晶の4モードすべてにおいて、正しい画面サイズを取得できた。
ただし、描画処理に長時間かかることは保証されない (将来のバージョンやエミュレータなどではより高速に処理される可能性がある) ため、他の手法 (テキスト画面のサイズ取得、ビデオ出力モードにおけるグラフィック画面のサイズ取得) よりも信頼性が劣ると考えられる。

なお、最初に `cltms` を用いるのではなく、時刻の差を利用した方法

```
10 st=tickms()
20 line 0,0,240,0,&HFFFF
30 print tickms()-st
```

では、描画先が画面内であっても `0` が出力され、うまくいかなかった。

## プログラム

最初に、`point` 関数によりTFT液晶モードかどうかの判定を行い、変数 `is_tft` に格納する。
これは画面サイズを取得する処理の使い分けに用いるだけでなく、アプリケーションの処理本体においてカラー表示を行えるかどうかという情報にもなる。

```
10 cls 2:pset 0,0,1:is_tft=!point(0,0):cls 2:gwidth=0:gheight=0
20 if is_tft goto 220
30 gwidth=1
40 pset gwidth,0,1
50 if point(gwidth,0) then gwidth=gwidth*2:goto 40
60 yes=0
70 while yes+1<gwidth
80 m=yes+(gwidth-yes)/2
90 pset m,0,1
100 if point(m,0) then yes=m else gwidth=m
110 wend
120 gheight=1
130 pset 0,gheight,1
140 if point(0,gheight) then gheight=gheight*2:goto 130
150 yes=0
160 while yes+1<gheight
170 m=yes+(gheight-yes)/2
180 pset 0,m,1
190 if point(0,m) then yes=m else gheight=m
200 wend
210 goto 470
220 cltms
230 line 0,0,240,0,&HFFFF
240 if tickms()<=0 then print "ERROR too fast":end
250 gwidth=1
260 cltms
270 line gwidth,0,gwidth,240,&HFFFF
280 if tickms()>0 then gwidth=gwidth*2:goto 260
290 yes=0
300 while yes+1<gwidth
310 m=yes+(gwidth-yes)/2
320 cltms
330 line m,0,m,240,&HFFFF
340 if tickms()>0 then yes=m else gwidth=m
350 wend
360 gheight=1
370 cltms
380 line 0,gheight,240,gheight,&HFFFF
390 if tickms()>0 then gheight=gheight*2:goto 370
400 yes=0
410 while yes+1<gheight
420 m=yes+(gheight-yes)/2
430 cltms
440 line 0,m,240,m,&HFFFF
450 if tickms()>0 then yes=m else gheight=m
460 wend
470 cls 2
480 print gwidth;" x ";gheight
```

このプログラムは、[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/deed.ja) でライセンスする。

# まとめ

テキスト画面のサイズは簡潔なプログラムで求めることができたが、今回のグラフィック画面のサイズを求めるプログラムは長くなってしまった。
現行バージョンのみを考慮するのであれば、やはり**グラフィック画面のサイズはテキスト画面のサイズの8倍**として求めるのがよいだろう。
さらに、現行バージョンではTFT液晶モードかどうかは「テキスト画面の高さが25行**でない**かどうか」として求めることができる。(TFT液晶モードにおけるテキスト画面の高さは30行または40行である)

今回考案した手法は、いずれも各画面のクリアを伴うものである。
そもそも画面をクリアして画面いっぱいに独自の情報を表示するアプリケーションであれば、問題にはならないだろう。