none
Graphics.DrawLineで線を引いても描画されない場合がある RRS feed

  • 質問

  • Microsoft Visual Studio 2015(.NET Framework 4.6)でソフトの開発を行っています。

    Graphics.DrawLineでFloat型の引数を指定して1pxの線を引いたときに、描画されない場合がありました。

    ※途中までは描画されるがその先が描画されない

    線の両端の座標も合わせて表示すると、その間で線の描画が欠落しているように見えます。

    ■ソースサンプル

    Bitmap canvas = new Bitmap(this.pctDrawArea.ClientSize.Width, this.pctDrawArea.ClientSize.Height);
    using (Graphics g = Graphics.FromImage(canvas))
    {
          g.DrawLine(Pens.Black, beginX, beginY, endX, endY);

          g.DrawRectangle(Pens.Red, beginX - 3.0f, beginY - 3.0f, 6.0f, 6.0f);
          g.DrawRectangle(Pens.Blue, endX - 3.0f, endY - 3.0f, 6.0f, 6.0f);

          this.pctDrawArea.Image = canvas;
    }

    ■描画した線が欠落した場合があった座標

    ◯パターン1

    beginX:829.5399  beginY:439.7916

    endX:829.3369  endY:-16.88076

    ◯パターン2

    beginX:852.552  beginY:417.3053

    endX:852.3605  endY:-13.05302

    ◯パターン3

    beginX:783.5132  beginY:484.7633

    endX:783.2898  endY:-20.97979

    以下のようにすると描画されることを確認しています。

    1.線の太さを2pxにする。

    2.アンチエイリアスをかける。

    Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

    上記のような現象が発生する原因をご教示いただけないでしょか?

    ご回答お待ちしております。

    また、Graphics.DrawLineの引数にFloat型とInt型がある理由、その使い分けについてもご教示いただけましたら幸いです。


    2017年12月21日 12:36

すべての返信

  • 1. penの太さもfloatなので、1.1, 1.2, 1.3, 1.4...と太さを変えたらどうなりますか?
    (教えてください。)

     2. (0, 0)から(1, 100)に線を引いたとする(長さ約100)。同じ傾きで長さ約50の線を引くには終点(0.5, 50)とせざるを得ない(その線が見えるかどうかは関係ない... ほんとかいな)。アウトラインフォントなども座標上必ずfloatになる。ハイレゾのディスプレイで1.5倍で拡大して表示するとすれば、127dotは190.5dotとなるので 描画はもともとfloatです。

    (私のは答えではありませんから... きっとマチガイ多し)

    2017年12月21日 13:57
  • ビットマップは1pxごとにしか色を指定することはできません。
    小数座標を指定しても丸め(四捨五入)されて整数座標に対して色づけを行われます。

    パターン1を例にすると、X座標は829.3369から829.5399であり、これは小数部だけ見ると0.3369から0.5399に変化しています。
    これでおよそ0.5の位置を考えた場合に幅1の線を引いたとすると、0と1の間幅の点ということになります。
    ただし、この点を描画するには先に説明したように整数座標にまるめる必要があります。
    そして浮動小数というのは有限の精度しか持たないため、わずかですが誤差が発生してしまいます。
    そのため、X=829の座標に点が描画されるか、X=830の座標に点が描画されるかという境界にあると、どちらにも描画されないという事が発生する可能性があります。
    (どちらにも描画してしまったら幅2の線になってしまいますから)
    傾きが大きければ途切れるのは僅かなので目立たなかったのでしょうが、傾きがごくわずかであるために途切れが広く見えてしまっているのでしょう。

    線を太くすると、少なくとも1ドットは描画されることになるため、途切れることがなくなります。
    アンチエイリアスは、計算された点だけでなく、その周辺の色も考慮して描画されるため、全く描画されないという事にはならず、途切れていないように見えるようになります。(線のふちが若干にじんだような描画のため周辺にも少し色がにじむ感じです)

    DrawLineの引数にfloat型があるのは、Graphicsの座標系をピクセル単位だけでなくミリ単位なども設定することができ、そのような場合には小さな点を整数だけでは指定できません。そのような場合のために浮動小数が必要になります。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2017年12月21日 14:41
    2017年12月21日 14:32
  • お回答いただきありがとうございます。

    引こうとした線がちょうど2pxの境界線上となってしまいどっちつかずになってしまったということですね。

    さらにお伺いしたいのですが、

    座標パターン1を以下のように整数型に丸めてDrawLineにInt型の引数として与えると描画されました。

    Int型を指定すると、どっちつかずにならず必ずどちらかのピクセルに描画されるということなのでしょうか?

    ◯パターン1

    beginX:830  beginY:440

    endX:829  endY:-17

    また、当該事象が発生したときの単位(PageUnit)が「Display」だったのですが、

    GraphicsがPixel単位の場合はFloat型は避けて、Int型の引数を指定し、

    (↑今回の事象が発生するかもしれないので)

    Float型の引数を指定する場合はGraphicsをミリ単位などに変更する必要があるということでしょうか?

    重ねての質問ですが、ご教示いただければ幸いです。

    2017年12月22日 11:39
  • 座標パターン1を以下のように整数型に丸めてDrawLineにInt型の引数として与えると描画されました。

    Int型を指定すると、どっちつかずにならず必ずどちらかのピクセルに描画されるということなのでしょうか?

    ほとんどの場合はどちらかが描画されるでしょう。しかし描画システムの内部実装に依存するので必ずという保証はありません。

    WindowsにはGDI,GDI+という描画システムがあり、.NetFrameworkのGraphicsはそのシステムを呼び出して描画を行っています。ですがその内部実装は公開されておらず検証することはできません。またOSのバージョン違いでも全く同じになるかどうかも判りません。
    linux向けにMonoという.NetFrameworkの実行環境がありますが、その場合はlinuxの描画システムあるいはMonoが独自に実装した描画システムにより描画されることになります。
    ですからすべての環境で完全に同一の描画結果になることは保証されることは無いでしょう。

    どちらにせよ描画結果が望んでいるような結果にならない場合は、望む結果になるように自分で計算を行って1ピクセルごとに描画する必要があるでしょう。

    また、Graphicsの命令は抽象化されており、描画先がビットマップなのか、プリンタなのかを特に意識せずに同じ方法で描画することができます。
    今回はたまたまビットマップでだいたい96dpiのような低い解像度ですが、プリンタだと1000dpi以上の解像度は普通にあります。その場合はより細かい線を引くことができるため、同じDrawLineを行っても途切れない線が印刷されることになるでしょう。(この場合でもどのように描画するかはプリンタ次第なので、異なるプリンタで同じ結果が得られる保証はありません)

    Float型の引数を指定する場合はGraphicsをミリ単位などに変更する必要があるということでしょうか?

    変更する必要はありません。
    プリンタへの描画でも単位をDisplayとすることはできます。この場合でも先に説明したように解像度が異なるためより細かい線を描画することができ、小数点以下の精度を持っていても問題なく行えます。逆に言うとfloatでないと細かい描画ができません。
    整数で表すことができる精度の場合ならint型の引数を使うという事です。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2017年12月23日 2:21
    2017年12月23日 2:20
  • 細かい突込みで申し訳ありませんが、

    > (この場合でもどのように描画するかはプリンタ次第なので、異なるプリンタで同じ結果が得られる保証はありません)

    ドライバ依存です。

    テキストが主体のドキュメント(Web印刷)をA4 2分割で印刷する事がよくあるのですが、
    あるメーカーのインクジェックは、文字の線がよく消えます。別のメーカーのははっきりでるのに、、と。
    解像度的には印刷出来て良い筈なのですが、、、。

    画像で印刷しているので、縮小を行う時に、白黒のどっちを優先しているかでは無いかと推測しています。

    2017年12月23日 3:55