ライン描画について

返信する


答えを正確に入力してください。答えられるかどうかでスパムボットか否かを判定します。

BBCode: ON
[img]: ON
[url]: ON
スマイリー: OFF

トピックのレビュー
   

展開ビュー トピックのレビュー: ライン描画について

レス有難うございます。

by jacoby » 2009年2月21日(土) 14:54

その前に「本当に今のままではまずいのか」考えてみて
くださいね。
苦労してそのように書き換えるだけの価値があるのかどうか、
ということです。かなり高度な処理を行うわけでもない限り、
GDI 関数でも十分だと思うので。
GDI 関数で間に合わないのでビットマップに直接アクセスする
アプローチを採るにしても、CreateDIBSection() よりは
DirectX を扱うほうが効率的ですし、近代的です。
DirectXについては、確かに速そうですしその点での憧れもあるん
ですが、ヘルプセンターにも記述があるように、
D3Dデバイスが生成されると、DirectX特有の問題が生じます。
それは、Windowsが使用しているビデオメモリをすべてDirectXが
占有してしまうので、他のウィンドウは表示できない環境になることです。
つまり「ウインドウ・アプリケーション」を作ることは出来ないのかなと、
フルスクリーン(というか他のウインドウが表示できない状態)でのみでの
実行になるアプリになってしまうというのだとちょっと、というので二の足を
踏んでいるところです。
今のGDIで描いているプロジェクトもハナからやり直しなのかなという
印象もあって。

今の現実的な選択肢としては上記の自前のDrawLine関数を使用する
ことだろうと思っています。
作っているアプリの性質から言って「描画されるピクセルが安定している
こと」は、どうにもマストですし、それにプログラム速度についても
「時々速いが時々遅い」よりも、「安定して遅い」方を取らなければならない
だろうと思っているので。
関数の仕様は調べればわかる。
関数を実行すれば、その仕様を満たした結果が得られることもわかる。
ただ、関数の中身まではわかりません。
①の直線も②の直線も、遠くから見れば確かに直線(ほんとは線分)
に見えます。
LineTo() を実行すればそのどちらかが出力されるのだから、
確かに LineTo() は仕様を満たしていると言えます。
ブラックボックスであるのだから、仕様の範囲内で出力にブレが
あることは仕方ないことなのです。
ただ今回のケースは、そのブレが大変不気味なのですが。
そうなんですよね。。
GDI、されどGDI。。ようやく掴んだと思えたとこで何となく指の間を
すり抜けていくような、奥が深いといえばその通りなんですが。。(汗)
恐らく元来ウインドウの部品を描画するために用意されているものか
と思うので、ナナメのラインを引いたりするのではなく、ボックスとか、
タテヨコ真っ直ぐのラインを引くには俄然安定しているものなのかも
知れないなぁ、と。。
そうだったらいいなと勝手に思ってるだけですが。(笑)

takさん、再びのレスありがとうございました。
またよろしくお願いします。

by tak » 2009年2月21日(土) 09:13

数点だけ補足させてもらいますね。
jacoby さんが書きました:結局のところラインを引くのに必要な情報は「始点」と「終点」。
直線を描画するアルゴリズムに他に偶然的な要素が入る余地は無い
と思うので、(以下略)
そうなんですよね。僕もそこが不気味に感じます。
そういうことが起き得るいくつかの仮説は立てられないこともないです。
ただ、そのような仮説を立てたところで検証が困難なので、深くは追求はしません。
たとえば、 GDI は複数の直線描画アルゴリズムを有していて、状況によって使い分けているとか。
そんなのどうやって調べろと(笑)
jacoby さんが書きました:正直なところGDIが"一定の描画結果を得られる保証がない。"ってのは
ちょっと後ろ髪を引かれる思いではいるんです。
これは、関数の中身は基本的にブラックボックスと考えることに由来します。
関数の仕様は調べればわかる。
関数を実行すれば、その仕様を満たした結果が得られることもわかる。
ただ、関数の中身まではわかりません。

①の直線も②の直線も、遠くから見れば確かに直線(ほんとは線分)に見えます。
LineTo() を実行すればそのどちらかが出力されるのだから、確かに LineTo() は仕様を満たしていると言えます。
ブラックボックスであるのだから、仕様の範囲内で出力にブレがあることは仕方ないことなのです。
ただ今回のケースは、そのブレが大変不気味なのですが。
肝心なところを答えられなくてすみません(汗)
jacoby さんが書きました:いずれにしても「CreateDIBSection()」で描画関係を自作。
ちょっと道は険しそうですが出来れば少しずつやっていきたいと
思います。
ちょっと待って。
その前に「本当に今のままではまずいのか」考えてみてくださいね。
jacobyさんがどのようなことをしたくてどんなプログラムを作ろうとされているのかは存じませんので的確な指摘はできませんが、苦労してそのように書き換えるだけの価値があるのかどうか、ということです。
かなり高度な処理を行うわけでもない限り、GDI 関数でも十分だと思うので。

GDI 関数で間に合わないのでビットマップに直接アクセスするアプローチを採るにしても、CreateDIBSection() よりは DirectX を扱うほうが効率的ですし、近代的です。
もっとも、メモリデバイスコンテキストに割り当てたビットマップを描画面に blt する技法をご存知のようなので難易度に大差ないかもしれませんが。

レス有難うございます。

by jacoby » 2009年2月21日(土) 06:27

NoWestさん、レス有難うございます。

早速頂いたレスを読ませてもらっていたのですが、ただ、
「何故、同じように始点と終点がセットされたライン描画が
時と場合により①と②のように違うピクセルを点灯して描画
されるのか」ということが分からないでいました。

結局のところラインを引くのに必要な情報は「始点」と「終点」。
直線を描画するアルゴリズムに他に偶然的な要素が入る余地は無い
と思うので、もし座標(0,0)-(2,1)にラインを描くとすると例えば
①のようにピクセルを点すアルゴリズムならその後何度実行しても
①のように、また、もし②のようにピクセルを点灯するアルゴリズム
ならその後何度実行しても②の様に同じように描かれる筈じゃないか、
と思って。実行した時によって①になったり②になったりというのは、
どうにもしっくり来なかったんです。

とりあえず直線を描画する関数を自作してみたんですが、
(x1,y1に始点、x2,y2に終点をセットしてDrawLineをコールしてみて
下さい。) この直線描画のアルゴリズムはどこにでもある、最も基本的なものの
一つだと思います。確か自分が知ったときには
「多くの場合コンピューター内部ではこのようにして直線を描画
している」とあったように記憶しています。

このルーチンを使って(0,0)-(2,1)のラインを引くと、
まず間違いなく「①」のようにピクセルが点灯したラインを
描画します。その後何度やっても①となり、決して②のようには
描画されることはありません。(「始点」と「終点」を入れ替えでも
しない限り。)

いずれにしてもこのようなコンピューターの処理において、
"①になったり②なったり"とというような「偶発的」な要素が
入り込む余地はないんじゃないかと思ったのですが。。


もう一つ気になるのが、処理速度の問題です。
①と描画される時と、②と描画される時であまりにも差がある
場合があるのでどうしてなんだろうかと思っています。
今のところ速度について分かっているのは次のようなことです。
「①のように描画される時、プログラムは非常に速く実行
される時と、非常に遅く実行される時がある」
「②のように描画される時、プログラムは①の"非常に遅い時"
よりもやや速い程度で実行される」
「①の"非常に速い時"と"非常に遅い時"の速さの差は
ひどいとき10倍以上あるように感じられる」

(速さの差がライン描画に起因するものなのかどうかは全く
分かりません。ので「ライン描画の速度」とせず
「プログラムの実行速度」と書きました。)

元々はこの"プログラムは全く改変していないのに実行速度が
時と場合であまりにも違う"ということを調べていて、
そうする内にラインの点灯ピクセルがどうも同様に違うみたい
だと。
当初はその時々の速度の違いは「ハードディスクがひどい
フラグメンテーションでも起こしてて、その関係で」かとも
思っていたんですか、ラインの描画具合が違うということに至って、
これは何か直さなければならない所があるんじゃないかと
思ったんです。


先に投稿した質問に記載のテストプログラム(メインウインドウと
プロンプトウインドウに(0,0)-(2,1)のラインを描画するもの)で
現在実行していて頻繁に現れるパターンは
「メインに①、プロンプトに②が描画され」、
「実行後前面に来ているプロンプトウインドウを
何か他のウインドウで一旦隠してから、もう一度前面に持って
くると②だったラインが①に変わっている」というものです。
プロンプトへはABが見えているスクリーンと
バックメモリスクリーン両方に二度描画を行うのでその際に変化が
起こっているものと考えますが、やはり不思議に感じられます。
ただしいつもいつもこのように出るのかといえばそうでは無く、
「メインに②が、プロンプトに①が」というときもまま見られます。

いずれにしてもこの問題が
「プログラマにはどうしようもない問題」ということならば
あきらめざるを得ない、ことですね。
自作の関数を使うか、どうにかして方法を探りたいと思っています。


それから、takさん、レス有難うございます。
1. に関して、他の環境にプログラムを持って行ったら違った描画になる
ことも考えられます。
ドキュメントなどで動作結果が厳密に保証されているわけではないので、
GDI を使う限り諦めるしかないです。
2. についても、それがよほど大問題でなければ受け入れるしかないでしょうね。
そもそも速度的にシビアなプログラムなら GDI は使うべきでないです。
では何を使えばよいか、というのは既にNoWestさんが提案されているとおり。
あきらめざるを得ないというのは自分にとっては少し残念な結果
なんですが、、やっぱり仕方ないことなんですね。
正直なところGDIが"一定の描画結果を得られる保証がない。"ってのは
ちょっと後ろ髪を引かれる思いでいます。
最近やっと、どうにかこうにかGDIが使えるようになってきたかって
所だったので。(笑)

いずれにしても「CreateDIBSection()」で描画関係を自作。
ちょっと道は険しそうですが出来れば少しずつやっていきたいと
思います。

長くなってしまいましたが
NoWestさん、takさん、レスありがとうございました。
またよろしくお願いします。

Re: ライン描画について

by tak » 2009年2月20日(金) 23:35

問題点は
  1. LineTo() で、一定の描画結果を得られる保証がない。
  2. 描画速度が安定しない。
ということでよろしいですか。

僕からアドバイスをひとつ贈るとするなら

  「気にするな」

…てことですかね、どっちも。
実際、どうしようもないことだと思うのですよ。

1. に関して、他の環境にプログラムを持って行ったら違った描画になることも考えられます。
ドキュメントなどで動作結果が厳密に保証されているわけではないので、GDI を使う限り諦めるしかないです。

2. についても、それがよほど大問題でなければ受け入れるしかないでしょうね。
そもそも速度的にシビアなプログラムなら GDI は使うべきでないです。
では何を使えばよいか、というのは既にNoWestさんが提案されているとおり。
GDI を(互換性などの観点から)使わざるを得ないとしても、そのような用途なら、自前でビットマップを管理して、描画ルーチンも自分で実装して、LineTo() などのような GDI 標準描画関数には極力頼らない造りにした方がいいです。
その場合(これ以上はこのトピックの趣旨から逸れますので紹介程度にとどめますが)CreateDIBSection() が使えます。
直線描画のアルゴリズムはブレゼンハム (Bresenham) が有名です。

Re: ライン描画について

by NoWest » 2009年2月20日(金) 19:49

> このようなライン描画の違いについて、お気づきのことが
> ありましたら是非教えて下さい。
この問題につきましては,回避する方法は存在しません
ピクセル(ドット)単位でデータを扱う場合、最小単位である線分を描画すると
必ずズレが生じます。
もっともコンピュータが0と1を使ってデータを管理している限りにおいて
直線を厳密に表示することはできません。

この手の問題はデバイスコンテキストの状態やOS、ハードなどの違いで結果は
異なってきますので,プログラマのレベルではどうにもなりません。


そのため、コンピュータのディスプレイ上で
直線が多少ズレていても,ズレていないように見せるために
アンチエイリアスとい呼ばれる技術が開発されました。

基本的な考え方は輪郭をぼやかしてなめらかに見せる技術ですが、
本格的には重み付け計算などをおこなってより直線に見えるようにします。

ですが、これも一プログラマには難しいので現実的ではないです。
※ビットマップを扱うソフトではあえてアンチエイリアスを使わないこともあります。

アンチエイリアスをかけたいなら、自分で処理された直線を作図する関数を自作するか
DirectXやOpenGLなどの描画ライブラリを使うしかないと思います。

P.S.
GDI+でもできるようですね。

ライン描画について

by jacoby » 2009年2月19日(木) 22:00

ラインを引いたときに現れるズレについて
教えて下さい。

例えばスクリーンの座標(0,0)-(2,1)にラインを引く場合、
以下のように書いています。

コード: 全て選択


 MoveToEx(hMemDC,0,0, ByVal NULL)
 LineTo(hMemDC,2,1)
 SetPixel(hMemDC,2,1,penColor)
 InvalidateRect(hMainWnd, ByVal NULL, FALSE) 
 Sleep(1) 
ところが実行していると
時々このライン描画がセットするピクセルの位置が違う
ことに気付きました。

ある時は、

■■□
□□■

のように描画され(これを①とします。■がラインの描画点です)、
また別の時には

■□□
□■■

と描画されます(これを②とします)。

確かにどちらでも(0,0)-(2,1)の座標を繋ぐラインには
なっているのですが、ただ印象としては②の描画
は少し不自然なのではないかという気がします。
しかも実行速度も①と②ではかなり差があり(一回の描画では
違いは分からないのですがループさせるなどして何度も実行
させていると違いは顕著です。元々この速度の違いから
描画点の違いに気が付きました)、
何か自分のプログラムが不正な処理を書いているのでは
と思うようになりました。

(①は描画としては正しいように感じるのですがこのラインが現れる
とき、しはしば描画速度が(あるいはプログラム自体の速度かも
しれませんが)非常に遅くなるときがあります。)


どのような条件下で①と②が場合分けされるのか
何とか捕らえようと思ったのですがそれが
中々掴めず、この現象が自分の環境以外でも現れるものなのか、
それとも自分の環境だけなのかそれすらもハッキリしない
状況で、それでここでお訊き出来ればと思いました。

比較の為に、同時にプロンプトウインドウにもラインを描画する
プログラムを書きました。(AB4.24/プロジェクト)
このプログラムはメイン・ウインドウとプロンプト・ウインドウ
それぞれにLine(0,0)-(2,1)のラインを引きます。

どちらのウインドウに①、②のどのラインが描画される
のは不確定です。
時にはメインに①が、プロンプトに②が描画され、
また別の時にはメインに②が、プロンプトに①が描画されます。
両方のウインドウに①が描画されることもあります。


出来れば①の描画を速度を落とすことなく安定的に実行させたいと
思うのですが、

このようなライン描画の違いについて、お気づきのことが
ありましたら是非教えて下さい。


(環境)
AB 4.24/WinMe/Pentium3

ページトップ