C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[8]: 矢印キャップをつかったライン描画の不具合?


(過去ログ 134 を表示中)

[トピック内 11 記事 (1 - 11 表示)]  << 0 >>

■78942 / inTopicNo.1)  矢印キャップをつかったライン描画の不具合?
  
□投稿者/ ゆい (7回)-(2016/02/25(Thu) 11:22:50)

分類:[.NET 全般] 

いつも参考にさせて頂いております。
現在C#を使って図形を描画するプログラムを作成しております。

例えばラインABC(A=始点 B=終点)を描こうとしたとき、
ラインBC間の長さが非常に短い場合、矢印スタイルを使うと正しいラインが描けない時があるようです。

具体的には正常に描画できている状態から縮尺を引いていくと、
ある時点から頂点AとCを結ぶラインが描画されます。

回避方法またはGDI+側で関連するような既知の不具合をご存知の方はいらっしゃいませんでしょうか。
よろしくお願いします。
引用返信 編集キー/
■78944 / inTopicNo.2)  Re[1]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ ゆい (8回)-(2016/02/25(Thu) 11:24:40)
2016/02/25(Thu) 11:31:56 編集(投稿者)
2016/02/25(Thu) 11:31:53 編集(投稿者)

訂正と補足です。
Bが終点と記載しましたが、正しくはCが終点のラインABCです。
また矢印自体はAdjustableArrowCapクラスを使用しています。
引用返信 編集キー/
■78945 / inTopicNo.3)  Re[2]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ shu (820回)-(2016/02/25(Thu) 11:46:18)
No78944 (ゆい さん) に返信

現象が確認出来る簡単なコードを提示された方がよいと思います。
引用返信 編集キー/
■78949 / inTopicNo.4)  Re[3]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ ゆい (9回)-(2016/02/25(Thu) 19:48:55)
少し長いですが確認用コードを作りました。
フォーム上にはPictureBox1つとキャップスタイル設定用にコンボボックスが2つあるイメージです。

        private string[] CapStyleNames = { "キャップなし", "矢印1(大)", "矢印2(大)", "矢印3(中)", "矢印4(中)", "矢印5(小)", "矢印6(小)" };
        private System.Windows.Forms.ColorDialog colorDialog = new ColorDialog();
        private Bitmap moveBitMap = null;
        private List<PointF> inputLinePointList = new List<PointF>();
        private List<PointF[]> inputLinePointsList = new List<PointF[]>();
        /// <summary>
        /// 初期表示の線
        /// </summary>
        PointF[] initLines = new PointF[] { 
            new PointF(342.0f, 121.0f),
            new PointF(334.0f, 120.0f), 
            new PointF(325.0f, 350.0f), 
            new PointF(331.0f, 353.0f), 
        };

        public Form1()
        {
            InitializeComponent();
            this.Init();
        }

        private void Init()
        {

            //始点キャップスタイルのコンボボックス設定
            StartCapStyleCombo.DataSource = CapStyleNames.Clone() as string[];
            StartCapStyleCombo.SelectedIndex = 1;
            this.StartCapStyleCombo.SelectedIndexChanged += new System.EventHandler(this.StartCapStyleCombo_SelectedIndexChanged);
            //終点キャップスタイルのコンボボックス設定
            EndCapStyleCombo.DataSource = CapStyleNames.Clone() as string[];
            EndCapStyleCombo.SelectedIndex = 1;
            this.EndCapStyleCombo.SelectedIndexChanged += new System.EventHandler(this.EndCapStyleCombo_SelectedIndexChanged);

            this.inputLinePointsList.Add(initLines);
            this.DrawLineTest();
        }

        /// <summary>
        /// 画面再描画
        /// </summary>
        private void DrawLineTest()
        {
            double scale = 1;
            double transparency = (100.0 - (double)this.FillColorTransparency.Value) / 100.0;
            Color color = Color.FromArgb(Convert.ToInt32(255 * transparency), this.FillColorPanel.BackColor);
            double size = (double)this.SymbolSizeUpDown.Value;
            string stCapName = Convert.ToString(this.StartCapStyleCombo.SelectedItem);
            string enCapName = Convert.ToString(this.EndCapStyleCombo.SelectedItem);

            using (Bitmap bitmap = new Bitmap(this.MapControl.Width, this.MapControl.Height))
            using (Graphics graphics = Graphics.FromImage(bitmap))
            {
                graphics.SmoothingMode = SmoothingMode.AntiAlias;
                float penSize = this.GetPixelSize(graphics, size);
                using (Pen pen = new Pen(Color.FromArgb((int)(255 * 0.5), Color.Black), 2f))
                {
                    pen.DashStyle = DashStyle.Dot;
                    PointF centerPt = new PointF();
                    int pointSize = 6;
                    foreach (PointF[] points in inputLinePointsList)
                    {
                        //矢印を付けないライン描画
                        graphics.DrawLines(pen, points);
                        //頂点の描画
                        foreach (PointF point in points)
                        {
                            centerPt.X = point.X - pointSize / 2f;
                            centerPt.Y = point.Y - pointSize / 2f;
                            graphics.FillEllipse(Brushes.Black, centerPt.X, centerPt.Y, pointSize, pointSize);
                        }
                        if (this.NomalDrawRbtButton.Checked)
                        {
                            //矢印を付けてライン描画
                            this.DrawLine_Normal(points, graphics, scale, color, penSize, stCapName, enCapName);
                        }
                    }
                }
                if (this.MapControl.Image != null)
                {
                    this.MapControl.Image.Dispose();
                }
                this.MapControl.Image = bitmap.Clone() as Bitmap;
                if (this.moveBitMap != null)
                {
                    this.moveBitMap.Dispose();
                }
                this.moveBitMap = bitmap.Clone() as Bitmap;
            }
        }

        /// <summary>
        /// 標準描画
        /// </summary>
        /// <param name="points"></param>
        /// <param name="graphics"></param>
        /// <param name="scale"></param>
        /// <param name="color"></param>
        /// <param name="penSize"></param>
        /// <param name="stCapName"></param>
        /// <param name="enCapName"></param>
        private void DrawLine_Normal(PointF[] points, Graphics graphics, double scale, Color color, float penSize, string stCapName, string enCapName)
        {
            if (points == null || points.Length < 2)
                return;
            bool isStArrowFill = false, isEndArrowFill = false;
            float[] stwh = this.GetArrowSize(graphics, scale, penSize, stCapName, ref isStArrowFill);
            float[] endwh = this.GetArrowSize(graphics, scale, penSize, enCapName, ref isEndArrowFill);
            using (Pen pen = new Pen(color, penSize))
            {
                if (stwh != null)
                {
                    pen.CustomStartCap = new AdjustableArrowCap(stwh[0], stwh[1], isStArrowFill);
                }
                if (endwh != null)
                {
                    pen.CustomEndCap = new AdjustableArrowCap(endwh[0], endwh[1], isEndArrowFill);
                }
                graphics.DrawLines(pen, points);
            }
        }

        /// <summary>
        /// スタイルに合わせた矢印のサイズを取得します。
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="scale"></param>
        /// <param name="penSize"></param>
        /// <param name="capName"></param>
        /// <param name="retIsFill"></param>
        /// <returns></returns>
        private float[] GetArrowSize(Graphics graphics, double scale, float penSize, string capName, ref bool retIsFill)
        {
            switch (capName)
            {
                case "矢印2(大)":
                case "矢印4(中)":
                case "矢印6(小)":
                    retIsFill = false;
                    break;
                case "矢印1(大)":
                case "矢印3(中)":
                case "矢印5(小)":
                    retIsFill = true;
                    break;
                default:
                    return null;
            }

            //  .netの仕様でキャップの塗りつぶしをしない場合、幅と高さを同じにした場合エラーが発生する                            
            float fwidth = 0;
            float fheight = 0;
            //  Terminateがついているものは端点に矢印を書く際に指定するサイズです。
            switch (capName)
            {
                case "矢印1(大)":
                case "矢印2(大)":
                    fwidth = 8.0F;
                    fheight = 10.0F;
                    break;
                case "矢印3(中)":
                case "矢印4(中)":
                    fwidth = 6.0F;
                    fheight = 7.5F;
                    break;
                case "矢印5(小)":
                case "矢印6(小)":
                    fwidth = 4.0F;
                    fheight = 5.0F;
                    break;
                default:
                    return null;
            }
            fwidth *= (float)scale;
            fheight *= (float)scale;
            //  塗りつぶしの場合、線幅が2.0未満だと矢印の大きさが補正されてしまう。
            //  調整のため70%の値にしている。
            if (penSize < 2.0F && retIsFill)
            {
                fwidth *= 0.7F;
                fheight *= 0.7F;
            }

            float[] floats = new float[2]; //   幅、高さ
            floats[0] = fwidth;
            floats[1] = fheight;

            return floats;
        }

        /// <summary>
        /// Point⇒Pixelへ変換
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="pointSize"></param>
        /// <returns></returns>
        private float GetPixelSize(Graphics graphics, double pointSize)
        {
            float result = (float)(graphics.DpiX / 72.0 * pointSize);
            return result;
        }
        
        private void StartCapStyleCombo_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.DrawLineTest();
        }

        private void EndCapStyleCombo_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.DrawLineTest();
        }

引用返信 編集キー/
■78963 / inTopicNo.5)  Re[4]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ shu (823回)-(2016/02/26(Fri) 12:01:47)
No78949 (ゆい さん) に返信
> 少し長いですが確認用コードを作りました。

FillColorTransparency
FillColorPanel  ... PictureBox?
SymbolSizeUpDown
MapControl
NomalDrawRbtButton

この辺のものがどういうものでどういう設定か分からないと
現象が確認出来なさそうです。

using System.Drawing.Drawing2D;
は必要ですね。

引用返信 編集キー/
■78973 / inTopicNo.6)  Re[4]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ ito (16回)-(2016/02/26(Fri) 14:50:35)
No78949 (ゆい さん) に返信
> 少し長いですが確認用コードを作りました。
細かい突っ込みはありますが、単純なFormで動作できるように改造して確認しました。
> ある時点から頂点AとCを結ぶラインが描画されます。
確かにご指摘の動作をしますね。
簡単に調べてみましたが、何らかの情報を見つけることはできませんでした。
動きを見ていると、現象が発生する条件として
・Capを塗りつぶす場合 &
・最初/最後の線分がCapHeightより短い場合
に発生しているように思います。
Capを塗りつぶさないでは駄目でしょうか!?


//---ここからコードです
namespace TestLines
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Threading;
    using System.Windows.Forms;

    /// <summary>
    /// Form1
    /// </summary>
    public partial class Form1 : Form
    {
        private string[] CapStyleNames = { "キャップなし", "矢印1(大)", "矢印2(大)", "矢印3(中)", "矢印4(中)", "矢印5(小)", "矢印6(小)" };
        private System.Windows.Forms.ColorDialog colorDialog = new ColorDialog();
        private Bitmap moveBitMap = null;
        private List<PointF> inputLinePointList = new List<PointF>();
        private List<PointF[]> inputLinePointsList = new List<PointF[]>();
        /// <summary>
        /// 初期表示の線
        /// </summary>
        PointF[] initLines = new PointF[] { 
            new PointF(342.0f, 121.0f),
            new PointF(334.0f, 120.0f), 
            new PointF(325.0f, 350.0f), 
            new PointF(331.0f, 353.0f), 
        };

        public Form1()
        {
            InitializeComponent();

            inputLinePointsList.Add(initLines);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            DrawLineTest(e.Graphics);
        }

        /// <summary>
        /// 画面再描画
        /// </summary>
        private void DrawLineTest(Graphics inGraphics)
        {
            double scale = 1;
            //double transparency = (100.0 - (double)this.FillColorTransparency.Value) / 100.0;
            Color color = Color.Red;// Color.FromArgb(Convert.ToInt32(255 * transparency), this.FillColorPanel.BackColor);
            // ※size, stCapName, enCapNameは適宜変更してください
            double size = 1.0;// (double)this.SymbolSizeUpDown.Value;
            string stCapName = CapStyleNames[1];// Convert.ToString(this.StartCapStyleCombo.SelectedItem);
            string enCapName = CapStyleNames[1];// Convert.ToString(this.EndCapStyleCombo.SelectedItem);

            //using (Bitmap bitmap = new Bitmap(this.MapControl.Width, this.MapControl.Height))
            //using (Graphics graphics = Graphics.FromImage(bitmap))
            Graphics graphics = inGraphics;
            {
                graphics.SmoothingMode = SmoothingMode.AntiAlias;
                float penSize = this.GetPixelSize(graphics, size);
                using (Pen pen = new Pen(Color.FromArgb((int)(255 * 0.5), Color.Black), 2f))
                {
                    pen.DashStyle = DashStyle.Dot;
                    PointF centerPt = new PointF();
                    int pointSize = 6;
                    foreach (PointF[] points in inputLinePointsList)
                    {
                        //矢印を付けないライン描画
                        graphics.DrawLines(pen, points);
                        //頂点の描画
                        foreach (PointF point in points)
                        {
                            centerPt.X = point.X - pointSize / 2f;
                            centerPt.Y = point.Y - pointSize / 2f;
                            graphics.FillEllipse(Brushes.Black, centerPt.X, centerPt.Y, pointSize, pointSize);
                        }
                        //if (this.NomalDrawRbtButton.Checked)
                        {
                            //矢印を付けてライン描画
                            this.DrawLine_Normal(points, graphics, scale, color, penSize, stCapName, enCapName);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 標準描画
        /// </summary>
        /// <param name="points"></param>
        /// <param name="graphics"></param>
        /// <param name="scale"></param>
        /// <param name="color"></param>
        /// <param name="penSize"></param>
        /// <param name="stCapName"></param>
        /// <param name="enCapName"></param>
        private void DrawLine_Normal(PointF[] points, Graphics graphics, double scale, Color color, float penSize, string stCapName, string enCapName)
        {
            if (points == null || points.Length < 2)
                return;
            bool isStArrowFill = false, isEndArrowFill = false;
            float[] stwh = this.GetArrowSize(graphics, scale, penSize, stCapName, ref isStArrowFill);
            float[] endwh = this.GetArrowSize(graphics, scale, penSize, enCapName, ref isEndArrowFill);
            using (Pen pen = new Pen(color, penSize))
            {
                if (stwh != null)
                {
                    pen.CustomStartCap = new AdjustableArrowCap(stwh[0], stwh[1], isStArrowFill);
                }
                if (endwh != null)
                {
                    isEndArrowFill = false;
                    pen.CustomEndCap = new AdjustableArrowCap(endwh[0], endwh[1], isEndArrowFill);
                }
                graphics.DrawLines(pen, points);
            }
        }

        /// <summary>
        /// スタイルに合わせた矢印のサイズを取得します。
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="scale"></param>
        /// <param name="penSize"></param>
        /// <param name="capName"></param>
        /// <param name="retIsFill"></param>
        /// <returns></returns>
        private float[] GetArrowSize(Graphics graphics, double scale, float penSize, string capName, ref bool retIsFill)
        {
            switch (capName)
            {
                case "矢印2(大)":
                case "矢印4(中)":
                case "矢印6(小)":
                    retIsFill = false;
                    break;
                case "矢印1(大)":
                case "矢印3(中)":
                case "矢印5(小)":
                    retIsFill = true;
                    break;
                default:
                    return null;
            }

            //  .netの仕様でキャップの塗りつぶしをしない場合、幅と高さを同じにした場合エラーが発生する
            float fwidth = 0;
            float fheight = 0;
            //  Terminateがついているものは端点に矢印を書く際に指定するサイズです。
            switch (capName)
            {
                case "矢印1(大)":
                case "矢印2(大)":
                    fwidth = 8.0F;
                    fheight = 10.0F;
                    break;
                case "矢印3(中)":
                case "矢印4(中)":
                    fwidth = 6.0F;
                    fheight = 7.5F;
                    break;
                case "矢印5(小)":
                case "矢印6(小)":
                    fwidth = 4.0F;
                    fheight = 5.0F;
                    break;
                default:
                    return null;
            }
            fwidth *= (float)scale;
            fheight *= (float)scale;
            //  塗りつぶしの場合、線幅が2.0未満だと矢印の大きさが補正されてしまう。
            //  調整のため70%の値にしている。
            if (penSize < 2.0F && retIsFill)
            {
                fwidth *= 0.7F;
                fheight *= 0.7F;
            }

            float[] floats = new float[2]; //   幅、高さ
            floats[0] = fwidth;
            floats[1] = fheight;

            return floats;
        }

        /// <summary>
        /// Point⇒Pixelへ変換
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="pointSize"></param>
        /// <returns></returns>
        private float GetPixelSize(Graphics graphics, double pointSize)
        {
            float result = (float)(graphics.DpiX / 72.0 * pointSize);
            return result;
        }
    }
}

引用返信 編集キー/
■78974 / inTopicNo.7)  Re[5]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ ito (17回)-(2016/02/26(Fri) 15:37:46)
No78973 (ito さん) に返信
> ■No78949 (ゆい さん) に返信
一部変更して、現象回避してみました。
こんな感じで如何でしょう!?

//---変更コード
        private void DrawLine_Normal(PointF[] points, Graphics graphics, double scale, Color color, float penSize, string stCapName, string enCapName)
        {
            if (points == null || points.Length < 2)
                return;
            bool isStArrowFill = false, isEndArrowFill = false;
            float[] stwh = this.GetArrowSize(graphics, scale, penSize, stCapName, ref isStArrowFill);
            float[] endwh = this.GetArrowSize(graphics, scale, penSize, enCapName, ref isEndArrowFill);

            int numPoints = points.Length;
            Pen penStart = new Pen(color, penSize);
            if (stwh != null)
            {
                penStart.CustomStartCap = new AdjustableArrowCap(stwh[0], stwh[1], isStArrowFill);
            }
            graphics.DrawLine(penStart, points[0], points[1]);

            Pen penMid = new Pen(color, penSize);
            for (int i = 1; i < numPoints - 2; i++)
            {
                graphics.DrawLine(penMid, points[i], points[i + 1]);
            }

            Pen penEnd = new Pen(color, penSize);
            if (endwh != null)
            {
                penEnd.CustomEndCap = new AdjustableArrowCap(endwh[0], endwh[1], isEndArrowFill);
            }
            graphics.DrawLine(penEnd, points[numPoints - 2], points[numPoints - 1]);
        }

引用返信 編集キー/
■78975 / inTopicNo.8)  Re[6]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ ゆい (10回)-(2016/02/26(Fri) 17:15:21)
>>shuさん、itoさん お返事ありがとうございます!
グラフィック系のプログラミングに慣れておらず、
サンプルコードが拙くて申し訳ありません。

itoさんから頂いた回避策で意図するラインが描画できました。
ただラインを個別に描画することによる弊害として、
塗りつぶしに透過率が設定されていると
キャップ部分とラインの重なり部分の色が変わってしまいます。

ラインの幅を太くすると少し目立ってきてしまうので
この部分で何か良い案はないものでしょうか。

度々の質問で恐れ入ります。

引用返信 編集キー/
■78976 / inTopicNo.9)  Re[7]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ 魔界の仮面弁士 (654回)-(2016/02/26(Fri) 17:45:55)
No78975 (ゆい さん) に返信
> グラフィック系のプログラミングに慣れておらず、
同じく。


> 塗りつぶしに透過率が設定されていると
> キャップ部分とラインの重なり部分の色が変わってしまいます。
透明背景な別の Bitmap に不透明色で描画してから
http://dobon.net/vb/dotnet/graphics/hadeinimage.html
でアルファ値を変更して転写するとか…検証していませんけれども。


> この部分で何か良い案はないものでしょうか。
良案は思いつかないです。orz
引用返信 編集キー/
■78977 / inTopicNo.10)  Re[7]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ ito (18回)-(2016/02/26(Fri) 19:58:52)
No78975 (ゆい さん) に返信
Capを塗りつぶさなければ現象は発生しませんでしたので、
おそらく、これが一番簡単な解決方法ですが...塗りつぶしは必須なのでしょうか!?

他の案としては...
(1)一度、Cap無しで描画、あとで先頭&最後の線分をCap有りで描画。
ただし、透明度(アルファ)を気にされるのであれば、やはり1度塗り部分と2度塗り部分の色が変わってしまうのでNGでしょう。
(2)魔界の仮面弁士さんがアイデア出してくれていますが、メモリビットマップに描画して、アルファ値を調整後、画面に転送する。
ただし、Antialiasがあると、Antialias部分のアルファ値設定または画面転送時の色の混合具合によりNGが出る可能性大。
(3)線分およびCap部分を自力で描画する。
できないことではないです。ただし、計算がすごく面倒でしょう。
(4)そもそもの線分の位置ずれを気にしない...
あたりでしょうか...

引用返信 編集キー/
■79002 / inTopicNo.11)  Re[8]: 矢印キャップをつかったライン描画の不具合?
□投稿者/ ゆい (11回)-(2016/02/29(Mon) 09:28:25)
>>魔界の仮面弁士さん、itoさん
お返事ありがとうございます。

利用可能なキャップスタイルはすべて選択可能なものとして
実装を進めていたため、今になって塗りつぶしは使用不可能とする調整が難しい状況だったのです。

とはいえその他にご提案頂いた方法も私にはかなり敷居が高そうなので、
なんらかの妥協案を探そうと思います。
これまでのところキャップが描画される付近に始終点以外の頂点がなければ問題なさそうなので、
スケールに応じた制限チェックなどを入れるくらいしか思いつきませんが・・・。

いろいろとアドバイスありがとうございました。

解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -