- - PR -
小さな円を描画するといびつになる(Java2D の Ellipse2D や Component の createImage の使用時)
1
| 投稿者 | 投稿内容 | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2003-10-21 14:52
unibon です。こんにちわ。
昔から気になっているのですが、 Java で小さな円を描画すると妙にいびつになる(歪む)ことがあるので悩んでいます。 つぎのコードで再現できます。
このコードは3通りの描画の仕方をおこなっています。 上段(青色)は古くからの AWT だけの機能で描画したものであり、綺麗です。 中段(赤色)は Java2D の Ellipse2D を使って描画したものですが、 少しいびつになっています。左右や上下が対称ではありません。 下段(緑色)は、Java2D を使わずに AWT だけを使っていますが、 これはダブルバッファのために java.awt.Component クラスの createImage メソッドで得られた Image から Graphics を得て それに対して描画しています。 これだと、とてもいびつになってしまいます。 Java2D の Ellipse2D は、おそらく内部的にパスを生成しているため、 小さいものを描画すると誤差がでているのだろうと推測しますので、 分からないでもありません(でも、できれば綺麗に描画してほしい)。 しかし、createImage で得たバッファに書くといびつになる理由が分かりません。 これはなぜなのでしょうか。 また、綺麗に描画できる回避方法はあるでしょうか。 試した環境は、Windows XP + JDK 1.4.2_01 です。 なお、java の起動時のオプションとして、 -Dsun.java2d.noddraw=true を指定すると、形状が変わります。これも謎です。 以前はこれを指定すると良くなることもあったかもしれません(あいまいです)が、 1.4.2_01 だと悪くなるみたいでした。 #件名が長すぎたので修正しました。 [ メッセージ編集済み 編集者: unibon 編集日時 2003-10-21 14:56 ] | ||||||||||||||||
|
投稿日時: 2003-10-21 17:32
こんにちは、さくらばです。
理由は複数あります。 1. JDK1.1 の描画ルーチンと Java2D の描画ルーチンは根本的に異なる JDK1.1 では Windows に描画を任せているが、Java2D では自分で描画しています 2. Java2D ではユーザ空間とデバイス空間が切り離されている。 この両者間のマッピングを行うのが AffineTransform です。 ユーザ空間は座標が浮動小数点数で表されますが、デバイス空間は整数なので 丸め誤差などが発生します。 3. Java2D は円を複数のベジェ曲線で表します。 通常は 4 本のベジェで表していますが、小さいとベジェで表しても誤差が大 きくなってしまいます。(Unibon さんが予想されたことです) drawOval は JDK1.1 の描画ルーチンがコールされるので、Windows が描画を行って います。それで、きれいに描画できるようです。 Java2D では、例えば左上隅が(0, 0)の半径 5 の円は次のコードと同一になります。 小さい円をよく見ると右下のほうが誤差が大きいように見えますが、それは 3.8807118 の方が 1.119288 より丸め誤差が大きいためだと思います。
それではどうすればいいかというと、根本的ではありませんが RenderingHint でアンチエイリアスをかける方法があります。
基本的にはイメージに描画する場合は Java2D が使用されるので、そこでまず いびつになります。次に drawImage を行うときにやはり誤差が出るからでは ないでしょうか。こちらは未確認です。 | ||||||||||||||||
|
投稿日時: 2003-10-22 17:04
unibon です。こんにちわ。
ありがとうございます。 このような変換規則でパスに変換されているとは知りませんでした。
個人的な好みになってしまい申し訳ないのですが、 アンチエイリアシングのボヤっとした画像は好きではないので、 (私に限っては)これは使わないことにしたいと思ってます。 #巷ではアンチエイリアシングをむしろ好ましいと見る向きも多いので、 #これで解決するケースも多いと思いますが。
その後、気づいたのですが、実行環境によっては、たとえ、 paintComponent の引数の Graphics に対して直に drawOval しても、 いびつになることがあるようです(最初のサンプルコードの上段のケースでも)。 これは、どうやら DirectDraw が使えるかどうかで決まるようです。 createImage で得た Image から得られる Graphics についても同様です。 デバッガで追いかけてみたのですが、 DirectDraw が使えない(あるいは効率が悪そう)と判断すると、なにかにつけすぐに、 内部的に Ellipse2D などのパスを使う描画モード(これがいびつ)になってしまうようです。 #sun.awt.windows.WComponentPeer あたりなどで判断しているようです。 このような描画モードを直接、明示的に指定する方法がないようです。 なお、起動時のオプションとして -Dsun.java2d.ddoffscreen=true を指定すると、できるだけ DirectDraw を使うモードになるらしく、 いびつにならなくなります。 しかし、Windows の設定で DirectDraw を使わない設定になっているとダメでした。 ちなみに drawImage に関してはビットマップ転送(いわゆる BitBlt)なので、 これ単体ではいびつにはならないと思っています(が確認はしていません)。 なお、 http://www5.airnet.ne.jp/sakuraba/java/laboratory/JDK1.4/Graphics/VolatileImage/VolatileImage.html を参考にさせていただき createImage の代わりに createVolatileImage を使うことでも解決できました。 しかし、残念ながらこれも DirectDraw の設定に左右されるようであり、 どのような環境でも解決するまでには至っていません。 | ||||||||||||||||
|
投稿日時: 2003-10-23 23:39
unibon です。こんにちわ。
別のスレッド、 「drawRoundRectの角の丸みの設定について」 http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=7038&forum=12 などを拝見して思ったのですが、 どうも、Ellipse2D や RoundRectangle2D の実装がヘボすぎる(小さい図形が苦手)、 と思えてきました。 DirectDraw のモードを気にするよりは、 これらの Shape の実装を改善する方向で検討してみたいと思います。 | ||||||||||||||||
|
投稿日時: 2003-10-24 11:48
unibonさんと同様の現象を体験し、同じようなスレッドを立ててしまったボムです。
unibonさんや、さくらばさんのご説明を読んで見たのですがイマイチ理解できませんでした。 結局、この現象を回避する策は無いのでしょうか? | ||||||||||||||||
|
投稿日時: 2003-10-24 12:53
以下のようにすると改善します。
[ メッセージ編集済み 編集者: Keisuke 編集日時 2003-10-24 12:56 ] | ||||||||||||||||
|
投稿日時: 2003-10-24 18:05
unibon です。こんにちわ。
さくらばさんがご指摘のように、アンチエイリアスをかける方法で、 どのような環境であっても、見栄えの違和感はほぼ回避できます。 ただし、アンチエイリアスによるボケという副作用はありますが。 #以下、アンチエイリアス以外の選択肢について。 もしも、私の最初のサンプルで、 上段(青)が綺麗で、下段(緑)がいびつな環境ならば(その環境に限定した回避策としては)、 私が以前に書いたやりかたである、 -Dsun.java2d.ddoffscreen=true というオプションを起動時に指定するか、 あるいは createVolatileImage を使うやりかたのいずれかで回避できるでしょう。 しかし、このやりかたでは、それ以外の環境でも必ずしも回避できるとは限りません。 さらに、もしも、上段(青)の段階ですでにいびつな環境ならば、 Ellipse2D(drawOval メソッドが内部的に使うクラス)や RoundRectangle2D(drawRoundRect メソッドが内部的に使うクラス)の振る舞いを、 そもそも改善する必要があります。
は、クラスはそのままで引数を調整して、 間接的に振る舞いを調整することになりますが、 Ellipse2D だとかなり効果があり、すごいワザですね。 ただ RoundRectangle2D ではどのような調整を与えればよいのかは 分かりませんでした。 もっとも確実なのは、 Ellipse2D や RoundRectangle2D と同じで精度の高いものを、 新たに作ってしまうことです。が、これがなかなか難しそうです。 RoundRectangle2D はまだ簡単そうですが、Ellipse2D 相当のものは難しそうです。 作りかけですが RoundRectangle2D もどき:
| ||||||||||||||||
|
投稿日時: 2003-10-27 16:57
こんにちは、さくらばです。
半径が小さいときは逆に精度を落とす方がいいのではないでしょうか。 簡単にいえば強引に四捨五入してしまうというものです。 問題は小数部が切り捨てられてしまうことにあるので、切り捨てられる前に 四捨五入をしてしまうわけです。 java.awt.geom.EllipseIterator クラス (パッケージ内パブリックなクラス なので JavaDoc などには出てきませんが、src.zip には入っています) の currentSegment メソッドで return をする前に coords 配列の値を 四捨五入してしまいます。そして、このクラスだけを JAR にして、 -Xbootclasspath/p: で読み込ませるようにします。 これだけで、ずいぶんきれいになりますよ。 # 著作権とかライセンスの問題があるので、ここでコードを出すことができ # ないのです。すいません。 | ||||||||||||||||
1
