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

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

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

曲線に座標を取得するには?

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

■92026 / inTopicNo.1)  曲線に座標を取得するには?
  
□投稿者/ 犬 (3回)-(2019/08/18(Sun) 19:47:03)

分類:[.NET 全般] 

http://bbs.wankuma.com/index.cgi?mode=al2&namber=90669&KLOG=156

4ヶ月前にここで質問させていただいたのですが、
仕事が忙しくすぐに試すことができませんでした。
先ほど試してみたのですが、
ラインを描画することには成功したものの、
補間されたデータを取得しても、
カクカクしたおかしなデータしか取得できません。

コードは以下の通りです。


    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles PictureBox1.Paint




                Dim g = e.Graphics

                Dim point1 As New Point(30, 20)
                Dim point2 As New Point(60, 150)
                Dim point3 As New Point(120, 30)
                Dim point4 As New Point(200, 140)
                Dim point5 As New Point(220, 100)
                Dim point6 As New Point(190, 60)
                Dim curvePoints As Point() = {point1, point2, point3, point4, point5, point6}

                '幅3の赤色のPenオブジェクトを作成
                Dim redPen As New Pen(Color.Red, 3)




                    'GraphicsPathオブジェクトの作成
                    Dim myPath As New GraphicsPath

                    '新しい図形を開始する
                    myPath.StartFigure()




                    '直線を2本追加する
                    myPath.AddCurve(curvePoints, 1)




                    'パス図形を描画する
                    g.DrawPath(redPen, myPath)


                    Using writer As New IO.StreamWriter("D:\test.csv", False, System.Text.Encoding.Default)

                        For i As Integer = 0 To myPath.PathPoints.Length - 1

                            writer.WriteLine(myPath.PathPoints(i).X & "," & myPath.PathPoints(i).Y)

                        Next i

                    End Using



    End Sub




出力されたCSVファイルは以下のようになっています。

30,20
40,63.33333
30,146.6667
60,150
89.99999,153.3333
73.33333,33.33333
120,30
166.6667,26.66666
166.6667,116.6667
200,140
233.3333,163.3333
223.3333,126.6667
220,100
216.6667,73.33334
200,73.33333
190,60

Excelでグラフ化してもらうと分かりますが、
全く補間されていないどころか、
PictureBoxに描画されたラインとは全く異なる位置にポイントが追加されています。

これは一体どういうことなのでしょうか?


引用返信 編集キー/
■92027 / inTopicNo.2)  Re[1]: 曲線に座標を取得するには?
□投稿者/ Hongliang (865回)-(2019/08/18(Sun) 20:02:45)
精密な曲線上の点を取得したいのであれば、PathPointsを取得する前にFlattenメソッドを呼び出してください。
引用返信 編集キー/
■92028 / inTopicNo.3)  Re[2]: 曲線に座標を取得するには?
□投稿者/ 犬 (4回)-(2019/08/18(Sun) 20:31:51)
ありがとうございます。

かなり所望するものに近い物が得られました。

これって、ラインが直線に近い場所は
ポイントが間隔が広くなり、
ラインが折れ曲がっている場所はポイントの間隔が狭くなっています。

できれば、等間隔でポイントを取得したいのですが可能でしょうか?

PCモニター上でラインを見ますと
アンチエイリアスのかかった滑らかなラインではなく、
ドット線になっているようなのですが、
このそれぞれのドットの座標は取得できないでしょうか?

引用返信 編集キー/
■92029 / inTopicNo.4)  Re[3]: 曲線に座標を取得するには?
□投稿者/ Hongliang (866回)-(2019/08/18(Sun) 21:16:20)
> できれば、等間隔でポイントを取得したいのですが可能でしょうか?

標準ライブラリには存在しません。
// (10,10)→(10,11)と(10,11)→(11,12)は等間隔なんですかね。
.Flatten(New Matrix(), flatness)
のflatnessの値を小さくすればより細かい座標集合にすることはできます(既定値は0.25だそうです)。

> PCモニター上でラインを見ますと
> アンチエイリアスのかかった滑らかなラインではなく、
> ドット線になっているようなのですが、
> このそれぞれのドットの座標は取得できないでしょうか?

Bitmapに描画して、"隣接"する色付きピクセルを探索するような処理を記述することはできるでしょう。
// 詳しく解説できるほど知識はありませんのであしからず。
引用返信 編集キー/
■92030 / inTopicNo.5)  Re[4]: 曲線に座標を取得するには?
□投稿者/ 犬 (6回)-(2019/08/18(Sun) 21:47:06)
そうですか、標準ライブラリにはありませんか・・・

やりたいこととしては
イラレのペンツールみたいに、ライン上のポイントのない場所をマウスでクリックすると
そこに新たにポイントが挿入されるようなことがしたいのですが。
そのためには、ポイントのない場所の当たり判定を行う必要があるのですが、
PathPointsで得られた配列から自分でピクセルを内挿して、
二次元Boolean配列でライン近傍だけをTrueにして、
判定するとかしかないですかね
相当面倒な処理が必要ですよね

Bitmapを使った方法だと、処理が遅くなりそうです。

もしご助言あればよろしくお願いいたします。


引用返信 編集キー/
■92034 / inTopicNo.6)  Re[5]: 曲線に座標を取得するには?
□投稿者/ Hongliang (867回)-(2019/08/19(Mon) 09:30:27)
> そのためには、ポイントのない場所の当たり判定を行う必要があるのですが、

これであれば、GraphicsPath.IsVisibleメソッドおよびGraphicsPath.IsOutlineVisibleを使用できそうです。
引用返信 編集キー/
■92042 / inTopicNo.7)  Re[6]: 曲線に座標を取得するには?
□投稿者/ 犬 (7回)-(2019/08/20(Tue) 15:32:29)
ありがとうございます。

試してみたのですが、

GraphicsPath.IsVisibleメソッドはライン上にカーソルを合わせた時にTrueを返し
GraphicsPath.IsOutlineVisibleはポイント上にカーソルを合わせた時にTrueを返すと思うのですが
合っていますか?

GraphicsPath.IsOutlineVisibleでは実際に描画したPenの種類を合わせる必要があるのでしょうか?

例えば、太さ1ptのpenでラインを引いたとして
太さ3pt以内にカーソルが入った場合にはTrueを返すようなことってできますでしょうか?




引用返信 編集キー/
■92043 / inTopicNo.8)  Re[7]: 曲線に座標を取得するには?
□投稿者/ Hongliang (869回)-(2019/08/20(Tue) 15:42:17)
> GraphicsPath.IsOutlineVisibleでは実際に描画したPenの種類を合わせる必要があるのでしょうか?
>
> 例えば、太さ1ptのpenでラインを引いたとして
> 太さ3pt以内にカーソルが入った場合にはTrueを返すようなことってできますでしょうか?

描画されているのちょうどで判定してほしければ描画で使用したPenを流用してもいいし、そうでなければ新しく作ってもいいです。
// というか、これぐらいなら一々質問するよりも実際に試してみた方が手っ取り早いのでは。
引用返信 編集キー/
■92044 / inTopicNo.9)  Re[8]: 曲線に座標を取得するには?
□投稿者/ 犬 (8回)-(2019/08/20(Tue) 17:27:33)
もちろん自分で試してはいるのですが
よく分からない挙動をしています。


IsOutlineVisibleは太さ指定でライン上にカーソルが乗っているかを判定するのに対し、
IsVisibleはラインに囲まれた領域にはいったことを判定していますか?
そのため、ラインが直線上だと
IsVisibleがTrueになることはないということですか?

そもそも、IsVisibleってどういう時に使うのでしょうか?

引用返信 編集キー/
■92045 / inTopicNo.10)  Re[9]: 曲線に座標を取得するには?
□投稿者/ 魔界の仮面弁士 (2311回)-(2019/08/20(Tue) 18:35:41)
No92044 (犬 さん) に返信
> よく分からない挙動をしています。
閉じたパスを使うと分かりやすいかも。


> そもそも、IsVisibleってどういう時に使うのでしょうか?
大まかに言えば、
DrawPath に対して、線分上の座標かどうかを判定するのが IsOutlineVisible で、
FillPath に対して、塗り潰し領域内か否かを判定するのが IsVisible という感じじゃないですかね。


Imports System.Drawing.Drawing2D
Public Class Form1
  Private gStar(3) As GraphicsPath
  Private Const PenWidth As Single = 2.25F
  Private redPen As New Pen(Color.Red, PenWidth)
  Private bluePen As New Pen(Color.Blue, PenWidth)
  Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
    For Each gp In gStar
      gp.Dispose()
    Next
    redPen.Dispose()
  End Sub

  Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Me.Text = "左=IsVisible、右=IsOutlineVisible"

    For n = 0 To 3
      Dim s As String = "★☆★☆".Substring(n, 1)
      Dim p As New Point((n \ 2) * 80 + 20, (n Mod 2) * 80 + 20)
      Debug.WriteLine($"{n} {p}")
      gStar(n) = New GraphicsPath()
      gStar(n).AddString(s, Me.Font.FontFamily, 0, 80.0F, p, StringFormat.GenericTypographic)
    Next
  End Sub

  Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
    Dim g = e.Graphics
    Dim p = PictureBox1.PointToClient(Cursor.Position)

    For n = 0 To 1
      If gStar(n + 0).IsVisible(p) Then
        g.FillPath(Brushes.Blue, gStar(n + 0))
      Else
        g.FillPath(Brushes.Red, gStar(n + 0))
      End If
      If gStar(n + 2).IsOutlineVisible(p, bluePen) Then
        g.DrawPath(bluePen, gStar(n + 2))
      Else
        g.DrawPath(redPen, gStar(n + 2))
      End If
    Next
  End Sub

  Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
    PictureBox1.Invalidate()
  End Sub
End Class
引用返信 編集キー/
■92046 / inTopicNo.11)  Re[10]: 曲線に座標を取得するには?
□投稿者/ 犬 (10回)-(2019/08/20(Tue) 19:28:14)
ありがとうございます。

> DrawPath に対して、線分上の座標かどうかを判定するのが IsOutlineVisible で、
> FillPath に対して、塗り潰し領域内か否かを判定するのが IsVisible という感じじゃないですかね。


そういうことだったのですね
説明書きを読んでもそういう書き方ではなかったので
混乱しました。


あと、IsOutlineVisibleで線分上からどうか判定することができますが、
どのポイントの間の線分かは調べることはできないのでしょうか?


引用返信 編集キー/
■92048 / inTopicNo.12)  Re[5]: 曲線に座標を取得するには?
□投稿者/ 魔界の仮面弁士 (2313回)-(2019/08/21(Wed) 10:13:23)
No92030 (犬 さん) に返信
> イラレのペンツールみたいに、ライン上のポイントのない場所をマウスでクリックすると
> そこに新たにポイントが挿入されるようなことがしたいのですが。

『イラレ』を使ったことがないので、どういう処理なのか分かりませんが、
想像で補って作ってみました。

・マウスカーソルは消して、代わりに点滅破線円で表現しています。
・ライン上に無いエリアはクリックしても反応しません。
・クリック可能なエリアでは、カーソル円の色が変わります。
・スプラインの制御点近くに来ると、カーソルを吸着させています。
・制御点はドラッグでドラッグで移動できます。ただし PictureBox の枠外にドラッグすることはできません。
・制御点ドラッグ中に Shift キーを押すと、その制御点が削除されます。
・制御点を 2 個未満にすることはできません。


> PathPointsで得られた配列から自分でピクセルを内挿して、
> 二次元Boolean配列でライン近傍だけをTrueにして、
> 判定するとかしかないですかね
> 相当面倒な処理が必要ですよね

区間判定が手抜き実装なので、しばしば誤判定します…。orz


Imports System.Drawing.Drawing2D
Public Class Form1
  Private Points As New List(Of Point)() 'スプライン曲線用の制御点座標群
  Private LinePen As New Pen(Color.Red, 3.0F) '曲線描画用
  Private GrabPen As New Pen(Color.Transparent, 5.0F) '座標探索用
  Private dragIndex As Integer = -1 'ドラッグ中のグラブ番号

  Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
    LinePen.Dispose()
    GrabPen.Dispose()
  End Sub

  Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Points.AddRange(New Point() {
      New Point(30, 20),
      New Point(60, 150),
      New Point(120, 30),
      New Point(200, 140),
      New Point(220, 100),
      New Point(190, 60)})
  End Sub

  Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
    Dim p = PictureBox1.PointToClient(Cursor.Position)
    Dim g = e.Graphics
    Dim gs = g.Save()
    g.SmoothingMode = SmoothingMode.AntiAlias

    Dim grapColor As Color = Color.Olive

    Using gp As New GraphicsPath()
      '曲線を描画
      gp.AddCurve(Points.ToArray())
      g.DrawPath(LinePen, gp)

      '制御点上に居る場合の処理
      If MouseButtons.HasFlag(MouseButtons.Left) AndAlso 0 <= dragIndex AndAlso dragIndex < Points.Count Then
        grapColor = Color.Cyan
        Points(dragIndex) = p 'ドラッグによりグラブ座標を移動
        If Points.Count > 2 AndAlso ModifierKeys.HasFlag(Keys.Shift) Then
          'ドラッグ中に Shift キーが押された場合は、その制御点を削除する
          Points.RemoveAt(dragIndex)
          dragIndex = -1
        End If
      Else
        dragIndex = -1
      End If

      If dragIndex = -1 Then
        'ドラッグ中で無い場合は、曲線上に居るかどうかで色を変える
        If gp.IsOutlineVisible(p, GrabPen) Then
          grapColor = Color.Magenta '曲線上に居る
        End If
      End If

      '制御点を描画
      For n = 0 To Points.Count - 1
        Dim pt = Points(n)
        Dim rect = Rectangle.FromLTRB(pt.X - 3, pt.Y - 3, pt.X + 3, pt.Y + 3)
        gp.Reset()
        gp.AddEllipse(rect)
        If gp.IsVisible(p, g) Then
          '制御点上にカーソルがある場合は ● として描く
          g.FillEllipse(Brushes.Blue, rect)
          'グラブ番号を更新
          If dragIndex = -1 Then
            dragIndex = n
            grapColor = Color.Magenta
          End If
        Else
          '制御点上にカーソルが無い場合は ○ として描く
          g.DrawEllipse(Pens.Blue, rect)
        End If
      Next

      '点滅するカーソルを描画
      Dim grapPos = If(dragIndex = -1, p, Points(dragIndex))
      Dim grabRect = Rectangle.FromLTRB(grapPos.X - 6, grapPos.Y - 6, grapPos.X + 6, grapPos.Y + 6)
      Using cursorPen As New Pen(grapColor, 1.5F)
        cursorPen.DashStyle = DashStyle.Dash
        cursorPen.DashOffset = Now.Millisecond \ 100
        g.DrawEllipse(cursorPen, grabRect)
      End Using

    End Using
    g.Restore(gs)
    PictureBox1.Invalidate()
  End Sub

  Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
    dragIndex = -1
    Cursor.Clip = Nothing
    PictureBox1.Invalidate()
  End Sub
  Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
    PictureBox1.Invalidate()
  End Sub

  Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
    If Not e.Button.HasFlag(MouseButtons.Left) Then
      Return '左クリックで無ければ無視
    Else
      'ドラッグ可能エリアを PictureBox 内に制限
      Dim clip = PictureBox1.RectangleToScreen(PictureBox1.ClientRectangle)
      clip.Inflate(-7, -7)
      Cursor.Clip = clip
    End If

    If 0 <= dragIndex AndAlso dragIndex < Points.Count Then
      '制御点を掴んでいる最中
      PictureBox1.Invalidate()
      Return
    End If

    '座標の挿入位置を判定
    Dim p = e.Location
    Using gp As New GraphicsPath()
      gp.AddCurve(Points.ToArray())

      '曲線上にあった場合は、平坦化して区間を調べる
      If gp.IsOutlineVisible(p, GrabPen) Then
        gp.Flatten(New Matrix(), 0.0125F)
        Dim pd = gp.PathData
        Dim i As Integer = 1
        Dim p0 = pd.Points(0)
        For j = 1 To pd.Points.Count - 1
          Dim p1 = pd.Points(j)
          gp.Reset()
          gp.AddLine(p0, p1)
          If gp.IsOutlineVisible(p, GrabPen) Then
            '区間内に「挿入」して再描画
            Points.Insert(i, p)
            PictureBox1.Invalidate()
            Return
          End If
          If i < Points.Count AndAlso Points(i) = New Point(CInt(p1.X), CInt(p1.Y)) Then
            i += 1 '次の区間に進む
          End If
        Next
      End If
    End Using
  End Sub

  Private Sub PictureBox1_MouseEnter(sender As Object, e As EventArgs) Handles PictureBox1.MouseEnter
    Cursor.Hide()
    PictureBox1.Invalidate()
  End Sub

  Private Sub PictureBox1_MouseLeave(sender As Object, e As EventArgs) Handles PictureBox1.MouseLeave
    Cursor.Show()
  End Sub
End Class
引用返信 編集キー/
■92050 / inTopicNo.13)  Re[6]: 曲線に座標を取得するには?
□投稿者/ 犬 (11回)-(2019/08/21(Wed) 11:36:09)
ありがとうございます。

やはり地道に探すしかないということなのですね・・・
提示してくださったコードはラインが隣接していると
誤判定しやすいようです。
ただ、ここから改良すれば良い物ができるような気がします。

引用返信 編集キー/

このトピックをツリーで一括表示


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

このトピックに書きこむ