第62回 プログラミングについて『ウインドウとビューポート』

ウインドウという言葉を聞くと、WindowsやXーWindowを思い浮かべると思いますが、Windowsがまだこの世になく、XーWindowも普及していなかった時代は、違ったものを考えたものでした。
ワークステーションが一般的になる前は、コンピュータにグラフィックの端末を接続して描画していたものでした。確かにその端末にはインテリジェントな機能はありましたが、図を拡大、縮小して表示する計算はもっぱら計算機側で行なわなければなりません。この操作は現在のWindowsやXーWindowでも同様です。例えば、四角形全体を表示したり、一部分を拡大して表示したりすることです。そのときに使用される考え方にウインドウとビューポートがあるのです。
以前にもちょっと触れたことがありましたが、私が初めてグラフィックの端末に図を描画したのは、かれこれ15年程前のことでした。そのときはVAXに日本無線のグラフィック端末NWXー235の組み合わせだったと思いますが、一冊のエスケープシーケンスのマニュアルを頼りにコツコツと描画ルーチンを作成していました。ただその頃は、自分の知識が足りなかったのか、頭が悪いのか、グラフィック端末のピクセル数が1250x1000(数値は確かではありません)だったので、それをそのまま実際の図の座標値に対応させてしまったため、拡大、縮小して描画することはおろか、大きな図も表示することができませんでした。今から思うと低能だったなあと頭を掻いてしまいます。
それではウインドウとビューポートとは一体どういうものなのかの説明をしましょう。まず下の図を見て下さい。
┏━━━━━━━━━━━━━┓
┃ ビューポート ┃
ウインドウ ┃ ↓ ┃
↓ ┃┌────────┐ ┃
┌──┐ ┃│ │ ┃
┏┿━┓│ ─→ ┃┝━━━━━┓ │ ┃
┃│ ┃│ ┃│ ┃ │ ┃
┃└─╂┘ ┃│ ┃ │ ┃
┗━━┛ ┃│ ┃ │ ┃
四角形 ┃│ ┃ │ ┃
┃└─────┸──┘ ┃
┗━━━━━━━━━━━━━┛
実際の図 端末の画面
この図を見てもらえれば、だいたいの関係は理解してもらえると思います。実際の図はー∞ ~ +∞の座標系にあり、実数値で表現されます。一般的にはこの座標系をワールド座標系を呼んでいます。そのワールド座標系のどの部分を表示するのかを示すのがウインドウになります。次に端末の画面は整数値の座標系で、XY方向とも0~1000程度の値しかありません。また端末によっても違いますが、画面の左上が(0、0)になるものと、左下が(0、0)になるものとあります(以降左下が原点であるとして考えていきます)。この座標系はスクリーン座標系とかVDC座標系などと呼ばれています。その座標系の中にビューポートがあり、その中にウインドウ内の図形を描画することになります。つまり、『どの部分を』というのがウインドウで、『どの部分に』というのがビューポートということになります。図をビューポートに表示するときには、ビューポートの外側の部分はクリッピングをして描画しないようにする必要がありますが、今回はクリッピングについては特には触れません。
それでは、ウインドウからビューポートへの座標変換を考えてみましょう。
まず最初に考慮しなければならないことは、ウインドウとビューポートを相似形とするか否かです。ウインドウが正方形で、ビューポートが長方形であるとき、ビューポートの縦横の比率に応じて図も変形して構わないときには特に問題はありませんが、そうなっては困るときには、なんらかの対策を施さなければなりません。
ウインドウとビューポートが相似形ではないときには、ウインドウの形状にビューポートの形状が相似形になるようにするか、または、その逆とするかのどちらかの操作を行ないます。通常は、ビューポートの位置と形状はスクリーン上に固定で設定され、ウインドウはマウス等によって設定されることが多いので、ビューポートに相似形になるようにウインドウの形状を補正します。
(WX2、 (SX2,
WY2) SY2)
┌────────┐ ┌─────┐
│ │ │ │
│ │ │ │
│ ・ │ │ │
│ (WXC、 │ │ │
│ WYC) │ │ │
└────────┘ └─────┘
(WX1, (SX1、
WY1) SY1)
ウインドウ ビューポート
いまウインドウとビューポートが上の図のような形状のとき、ウインドウがビューポートと相似形になるように形状の補正を行ないます。ビューポートの横と縦の比率は、
SAXY=(SX2ーSX1)/(SY2-SY1) ー(1)
になります。いま補正後のウインドウのX方向の長さをWXL、Y方向の長さをWYLとすると、
WXL/WYL=SAXY ー(2)
になればいいので、式(2)から
WYL=WXL/SAXY ー(3)
WXL=WYL*SAXY ー(4)
とすればXとY方向の長さを求めることができます。が、しかし、ここで問題が1つあります。WXLまたはWYLのいずれかが決定されないと、もう一方の値を決定できないことです。そこで、補正前のウインドウのXまたはY方向の長さを使用することになるのですが、そのどちらを基準とするかを決めなければなりません。そこで、補正前のウインドウの横と縦の比率、
WAXY=(WX2ーWX1)/(WY2-WY1) ー(5)
を求め、SAXY>WAXYのときにはウインドウの縦の長さを基準に、それ以外のときにはウインドウの横の長さを基準にすれば、マウス等で指定された領域を含んだ補正後のウインドウの横と縦の長さを求めることができます。要するにウインドウを横に伸ばすと相似形になるときには縦を基準に、縦を伸ばすと相似形になるときには横を基準にする訳です。
このようにして、求められた補正後のウインドウの横と縦の長さをwxl、wylとします。ここでもう1つ問題が残っています。縦と横の長さは決まりましたが、位置は決まっていないのです。補正前のウインドウの矩形のいずれか角の点を基準にすることも考えられますが、矩形の中心を基準にした方が自然な感じがします。そこで、
WXC=(WX1+WX2)/2
WYC=(WY1+WY2)/2
として中心位置を求め、ここを基準位置として新しいウインドウの左下と右上の位置を決定します。補正後のウインドウの左下と右上の位置を(wx1、wy1)、(wx2、wy2)とすると、
wx1=WXC-wxl/2
wy1=WYC-wyl/2
wx2=WXC+wxl/2
wy2=WYC+wyl/2
となります。これでウインドウとビューポートが相似形とすることができました。
次にワールド座標系の座標値がスクリーン座標系のどの位置になるのかを考えてみましょう。この座標変換は、単純に比の計算で求めることができます。
(WX2、 (SX2,
WY2) SY2)
┌────────┐ ┌─────┐
│ ・ │ │ ・ │
│(WXP、 │ │(SXP、│
│ WYP) │ │ SYP)│
│ │ │ │
│ │ │ │
└────────┘ └─────┘
(WX1, (SX1、
WY1) SY1)
ウインドウ ビューポート
上の図で、ワールド座標系の点(WXP,WYP)が与えられたときに、それに対応するスクリーン座標系の位置(SXP,SYP)を求めます。この2つの座標値の関係はウインドウとビューポートの関係により求めると、
WXPーWX1:WX2ーWX1=SXPーSX1:SX2-SX1 ー(1)
WYPーWY1:WY2ーWY1=SYPーSY1:SY2-SY1 ー(2)
となりますので、式(3)(4)から(SXP、SYP)を求めると、
SXP=(WXP-WX1)*(SX2-SX1)/(WX2-WX1)+SX1
SYP=(WYP-WY1)*(SY2-SY1)/(WY2-WY1)+SX1
となります。当然のことですが、マウスなどの入力による座標値はスクリーン座標系で指定されますので、上の式を変形して、(SXP,SYP)から(WXP,WYP)を求めるようにすればいいことになります。
以上がウインドウとビューポートの関係を式で考えたものです。次に、実際のアプリケーションではどのような構造でアプリケーションと人間との間のやり取りを行なうかを考えてみましょう。
┌────┐ ┌──────┐ ┌───────┐
│ │ │ ウインドウ │ │ │
│ データ │←(1)→│ ↑ │─(2)→│ 画面 │
│ │ │ ↓ │ │(スクリーン)│
└────┘ │ビューポート│ │ │
└──────┘ └───────┘
↑
(3)
│
┌──────┐
│ロケータ入力│
│(マウス等)│
└──────┘
通常CADのアプリケーションなどを作るときには上の図のような構成にします。この図で『データ』部分は、ウインドウの状態やビューポートの状態を殆ど意識しないように作成します。ただし、画面全体を再描画するときなどは図形をクリッピングする(全く描画しないのか一部分でも描画するのかを分別する)目的で若干の意識は必要です。ですので、上図の(1)では描画するときにはデータの座標値そのままで『ウインドウ←→ビューポート』部にデータを転送します。
『ウインドウ←→ビューポート』部が『画面』部にデータを転送する部分(2)は、『データ』部から転送されたワールド座標系の座標値をスクリーン座標系に座標変換を行い、ビューポートの矩形でクリッピングを行います。
『ロケータ入力』部は、ウインドウの再設定、ビューポートの再設定、データの座標値がスクリーン座標系で与えられますので、そのままスクリーン座標値で『ウインドウ←→ビューポート』部に送り込みます(3)。『ウインドウ←→ビューポート』部は、受け取ったスクリーン座標値をワールド座標値に変換して『データ』部に転送するようにします。
要するに、データを扱う部分と画面やロケータとのやり取りを極力分離することが大切なのです。このように分離することで、データを扱う部分は外部のことを気にせずに作成することが可能になり、開発やメンテナンスの効率向上が図れる訳です。
それではまた次回。