ColorPicker mit Wheel

    • VB.NET
    • .NET (FX) 4.0

    Es gibt 26 Antworten in diesem Thema. Der letzte Beitrag () ist von J.Herbrich.

      ColorPicker mit Wheel

      Hallo,

      Da ich mit dem Custom-Colordialog nicht zufrieden war, habe ich mir die Grundlage für einen eigenen geschrieben, ein ColorWheel. Das ColorWheel basiert auf dem HSV Farbraum:

      Der Winkel (in Grad) gibt den Hue-Wert an (0-360). Das Verhältnis der Strecke vom Kreismittelpunkt zu Farbpunkt in Hinblick auf den Radius gibt die Sättigung (Saturation an, 0-1). Value ist quasi die Abstufung mit Schwarz. Häufig wird die Value ebenfalls als Prozentzahl (0-1) dargestellt, ich wollte diese allerdings absolut bestimmbar haben.

      Grundlage für das ColorWheel ist die Klasse HSVColor, die die Konvertierung der beiden Farbräume ineinander ermöglicht (RGB<->HSV):
      HSVColor.vb

      VB.NET-Quellcode

      1. <TypeConverter(GetType(ExpandableObjectConverter))> _
      2. Public Class HSVColor
      3. Public Property Hue As Double
      4. Public Property Saturation As Double
      5. Public Property Value As Double
      6. Public Sub New(ByVal hue As Double, ByVal saturation As Double, ByVal value As Double)
      7. _Hue = hue
      8. _Saturation = saturation
      9. _Value = value
      10. End Sub
      11. Public Sub FromARGBColor(ByVal col As Color)
      12. Dim maximum = max({col.R, col.G, col.B})
      13. Dim minimum = min({col.R, col.G, col.B})
      14. If maximum = minimum OrElse col.R = col.B AndAlso col.R = col.G AndAlso col.G = col.B Then
      15. Hue = 0
      16. ElseIf maximum = col.R Then
      17. Hue = 60 * ((col.G - col.B) / (maximum - minimum))
      18. ElseIf maximum = col.G Then
      19. Hue = 60 * (2 + (col.B - col.R) / (maximum - minimum))
      20. ElseIf maximum = col.B Then
      21. Hue = 60 * (4 + (col.R - col.G) / (maximum - minimum))
      22. End If
      23. If Hue < 0 Then Hue = Hue + 360
      24. If maximum = 0 OrElse (col.R = 0 AndAlso col.G = 0 AndAlso col.B = 0) Then
      25. Saturation = 0
      26. Else
      27. Saturation = (maximum - minimum) / maximum
      28. End If
      29. Value = maximum
      30. End Sub
      31. Public Function min(ByVal arg() As Double) As Double
      32. Dim lowest As Double = 255
      33. For Each ar As Double In arg
      34. If ar < lowest Then
      35. lowest = ar
      36. End If
      37. Next
      38. Return lowest
      39. End Function
      40. Public Function max(ByVal arg() As Double) As Double
      41. Dim highest As Double = 0
      42. For Each ar As Double In arg
      43. If ar > highest Then
      44. highest = ar
      45. End If
      46. Next
      47. Return highest
      48. End Function
      49. Public Function ToColor() As Color
      50. Dim hi As Integer = Convert.ToInt32(Math.Floor(Hue / 60))
      51. Dim f As Double = Hue / 60 - hi
      52. Dim p As Integer = Convert.ToInt32(Value * (1 - Saturation))
      53. Dim q As Integer = Convert.ToInt32(Value * (1 - Saturation * f))
      54. Dim t As Integer = Convert.ToInt32(Value * (1 - Saturation * (1 - f)))
      55. Dim val As Integer = Convert.ToInt32(Value)
      56. Select Case hi
      57. Case 0
      58. Return Color.FromArgb(val, t, p)
      59. Case 1
      60. Return Color.FromArgb(q, val, p)
      61. Case 2
      62. Return Color.FromArgb(p, val, t)
      63. Case 3
      64. Return Color.FromArgb(p, q, val)
      65. Case 4
      66. Return Color.FromArgb(t, p, val)
      67. Case 5
      68. Return Color.FromArgb(val, p, q)
      69. Case 6
      70. Return Color.FromArgb(val, t, p)
      71. End Select
      72. End Function
      73. Public Overrides Function ToString() As String
      74. Return String.Format("HSVColor [H={0}, S={1}, V={2}]", {Hue, Saturation, Value})
      75. End Function
      76. End Class
      77. Public Class HSVEventArgs
      78. Inherits EventArgs
      79. Private _HSVColor As HSVColor
      80. Public ReadOnly Property HSVColor As HSVColor
      81. Get
      82. Return _HSVColor
      83. End Get
      84. End Property
      85. Public Sub New(col As HSVColor)
      86. _HSVColor = col
      87. End Sub
      88. End Class


      ColorPicker.vb

      VB.NET-Quellcode

      1. Imports System.Drawing
      2. Imports System.ComponentModel
      3. Imports System.Windows.Forms
      4. Imports System.Drawing.Drawing2D
      5. <DefaultEvent("ColorChanged")>
      6. <ToolboxBitmap(GetType(ColorDialog))>
      7. Public Class Colorpicker
      8. Inherits UserControl
      9. Protected Overrides ReadOnly Property CreateParams() As CreateParams
      10. Get
      11. Dim cp As CreateParams = MyBase.CreateParams
      12. cp.ExStyle = cp.ExStyle Or &H20
      13. Return cp
      14. End Get
      15. End Property
      16. Public Event ColorChanged As EventHandler(Of HSVEventArgs)
      17. Sub New()
      18. MyBase.SetStyle(ControlStyles.SupportsTransparentBackColor, True)
      19. MyBase.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
      20. End Sub
      21. 'das rectangle das den kreis beinhaltet
      22. Dim ellipserectangle As New Rectangle(2, 2, 200, 200)
      23. 'das rectangle das den valueregler beinhaltet
      24. Dim valuerectangle As New Rectangle(250, 2, 20, 200)
      25. 'Mittelpunkt des Wheels
      26. Dim middlepoint As PointF = New PointF(Convert.ToInt32(ellipserectangle.X + ellipserectangle.Width / 2), Convert.ToInt32(ellipserectangle.Y + ellipserectangle.Height / 2))
      27. 'Momentaner Cursor Standort auf den Reglern
      28. Dim ellipsept As PointF = middlepoint
      29. Dim rectanglept As New PointF()
      30. 'das Makierende Rectangle auf den Reglern
      31. Dim ellipsepointerrectangle As New Rectangle()
      32. Dim rectanglepointrectangle As New Rectangle()
      33. Private _hsvcolor As New HSVColor(0, 0, 255)
      34. Public Property HSVColor As HSVColor
      35. Get
      36. Return _hsvcolor
      37. End Get
      38. Set(value As HSVColor)
      39. _hsvcolor = value
      40. Me.Invalidate(New Rectangle(Convert.ToInt32(ellipsept.X - 3), Convert.ToInt32(ellipsept.Y - 3), 7, 7))
      41. If HSVColor.Saturation > 0 Then
      42. Dim angle As Double = value.Hue * (Math.PI / 180)
      43. Dim r As Double = ellipserectangle.Width / 2 * HSVColor.Saturation
      44. Dim xkoor As Double = Math.Cos(angle) * r + middlepoint.X
      45. Dim ykoor As Double = Math.Sin(angle) * r + middlepoint.Y
      46. ellipsept = New Point(Convert.ToInt32(xkoor), Convert.ToInt32(ykoor))
      47. Else
      48. ellipsept = middlepoint
      49. End If
      50. ellipsepointerrectangle = New Rectangle(Convert.ToInt32(ellipsept.X - 3), Convert.ToInt32(ellipsept.Y - 3), 6, 6)
      51. Me.Invalidate(New Rectangle(Convert.ToInt32(ellipsept.X - 3), Convert.ToInt32(ellipsept.Y - 3), 7, 7))
      52. Dim mouseheight As Integer = Convert.ToInt32(((-1 * HSVColor.Value + 255) / 255) * valuerectangle.Height + valuerectangle.Y)
      53. Me.Invalidate(New Rectangle(Convert.ToInt32(rectanglept.X - 3), Convert.ToInt32(rectanglept.Y - 3), 7, 7))
      54. rectanglept = New Point(Convert.ToInt32(valuerectangle.X + valuerectangle.Width / 2), mouseheight)
      55. rectanglepointrectangle = New Rectangle(Convert.ToInt32(rectanglept.X - 3), Convert.ToInt32(rectanglept.Y - 3), 6, 6)
      56. Me.Invalidate(valuerectangle)
      57. RaiseEvent ColorChanged(Me, New HSVEventArgs(Me._hsvcolor))
      58. End Set
      59. End Property
      60. Protected Overrides Sub OnPaint(e As PaintEventArgs)
      61. MyBase.OnPaint(e)
      62. With e.Graphics
      63. .SmoothingMode = Drawing2D.SmoothingMode.HighSpeed
      64. .CompositingQuality = CompositingQuality.HighQuality
      65. .DrawEllipse(Pens.Gray, ellipserectangle)
      66. 'Hier das eigentliche Wheel mit Farbe je nach Winkel
      67. Dim hsv As New HSVColor(0, 1, 255)
      68. Dim angle As Double
      69. Dim outherpoint As PointF
      70. For u = 0 To 360 Step 1
      71. hsv.Hue = u
      72. angle = Math.PI / 180 * u
      73. outherpoint = New PointF(Convert.ToSingle(Math.Cos(angle) * (ellipserectangle.Width / 2) + middlepoint.X), Convert.ToSingle(Math.Sin(angle) * (ellipserectangle.Width / 2) + middlepoint.Y))
      74. Using lbg As New LinearGradientBrush(middlepoint, outherpoint, Color.White, hsv.ToColor)
      75. Dim pt As New GraphicsPath
      76. pt.AddPie(ellipserectangle, u, 1.5)
      77. .FillPath(lbg, pt)
      78. End Using
      79. Next
      80. 'Schwarzen Punkt in der Mitte entfernen
      81. .FillRectangle(Brushes.White, New Rectangle(Convert.ToInt32(middlepoint.X - 1), Convert.ToInt32(middlepoint.Y - 1), 2, 2))
      82. 'Hier wird der Regler für Value gezeichnet
      83. hsv.Hue = HSVColor.Hue
      84. hsv.Saturation = HSVColor.Saturation
      85. Dim uppercolor = hsv.ToColor
      86. hsv.Value = 0
      87. Using lgb As New LinearGradientBrush(valuerectangle, uppercolor, hsv.ToColor, LinearGradientMode.Vertical)
      88. .FillRectangle(lgb, valuerectangle)
      89. End Using
      90. .DrawRectangle(Pens.Black, ellipsepointerrectangle)
      91. .DrawRectangle(Pens.Black, rectanglepointrectangle)
      92. End With
      93. End Sub
      94. Private _Color As Color
      95. Public Property [Color] As Color
      96. Get
      97. _Color = HSVColor.ToColor
      98. Return _Color
      99. End Get
      100. Set(value As Color)
      101. _Color = value
      102. Dim hsv As New HSVColor(0, 0, 0)
      103. hsv.FromARGBColor(value)
      104. HSVColor = New HSVColor(hsv.Hue, hsv.Saturation, hsv.Value)
      105. End Set
      106. End Property
      107. Private Sub Colorpicker_MouseClick(sender As Object, e As MouseEventArgs) Handles Me.MouseClick
      108. Dim distance As Double = Math.Sqrt(Math.Pow(middlepoint.X - e.X, 2) + Math.Pow(middlepoint.Y - e.Y, 2))
      109. Dim radius As Double = ellipserectangle.Width / 2
      110. If radius > distance Then
      111. Dim Saturation As Double = distance / radius
      112. Dim angle = Math.Atan2(e.Y - middlepoint.Y, e.X - middlepoint.X) / Math.PI * 180
      113. If angle <= 0 Then angle = angle + 2 * 180
      114. _hsvcolor.Saturation = Saturation
      115. _hsvcolor.Hue = angle
      116. _hsvcolor.Value = 255
      117. HSVColor = New HSVColor(angle, Saturation, 255)
      118. ElseIf valuerectangle.Contains(e.Location) Then
      119. Dim val As Double = 255 - 255 * ((e.Y - valuerectangle.Y) / valuerectangle.Height)
      120. HSVColor = New HSVColor(_hsvcolor.Hue, _hsvcolor.Saturation, val)
      121. End If
      122. End Sub
      123. End Class

      Ich wünsche euch viel Spaß damit :) .
      Dateien

      8-) faxe1008 8-)

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „faxe1008“ ()

      @faxe1008 Option Strict On.
      und
      (-1 * x + y) ==> (y - x)
      und das Default-Rect würde ich so groß machen, dass der ganze Farbkreis dargestellt wird.
      ---

      VB.NET-Quellcode

      1. Public Function max(ByVal arg() As Double) As Double
      Initialisiere so:

      VB.NET-Quellcode

      1. Dim highest As Double = arg(0)
      min analog, auch wenn das sicher funktioniert.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „RodFromGermany“ ()

      hmm - kein ColorPicker.ColorChanged-Event? Wie soll man das denn testen?

      Bemerkenswert auch zeile#143

      faxe1008 schrieb:

      VB.NET-Quellcode

      1. CurrentHue = CurrentHue


      Und in diesem Farbraum gibts keine abgedunkelten Farben?
      Ah ja - das Problem mit jedem Colorpicker-ansatz: ein Farbraum ist 3-dimensional.
      Und bei deim Picker fehlt iwie die Möglichkeit, darunter liegende Farbscheiben sichtbar zu machen.

      Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Ich werde das projekt nochmal überarbeiten und dann neu hochladen, bin es einfach noch von Java und C# gewöhnt dass der Compiler immer meckert, wenn was net passt.

      Jap dadurch spar ich mir an der Stelle Berechnungen, die ich dann einfach in der anderen Property rechnen lasse, das ist über eine Metode schöner, das stimmt.

      8-) faxe1008 8-)

      faxe1008 schrieb:

      Ich wünsche euch viel Spaß damit .


      Jo - dankeschön.
      Aber da musst Du noch mal ran.
      Bevor ich auch überhaupt den ColorChangeEvent suche, hätte ich gern eine bedienbare Oberfläche.
      Die Auswahlbereiche machen keinen Spaß und hakeln und die rechte Bar zappelt/flickert wie verrückt beim Verschieben der Auswahl.

      Aber vielen Dank fürs Teilen und zukünftige Verbesserungen!
      Hallo,
      bisher kannte ich das HSV-Modell gar nicht... aber ich finde es äußerst spannend...

      kurze Frage:
      Der Winkel (in Grad) gibt den Hue-Wert an (0-360).


      Kennst du die Hue Lampen von Philips? Falls ja, hat der Name der Hue Lampen (eher Leuchtmittel) was mit diesem System zu tun?
      Das weiß ich nicht, tut mir Leid.

      @all: Habe gestern den Code nochmal verschönert, mittlerweile laggt es auch nicht mehr so tierisch. Neue Version ist im Startpost oben :) . Jetzt ist es hoffentlich angenehm

      8-) faxe1008 8-)

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „faxe1008“ ()

      faxe1008 schrieb:

      Jetzt ist es hoffentlich angenehm
      Ich nöl mal noch ne Runde. ;)
      Gib der Klasse HSVColor eine überladene ToString()-Funktion,
      Dein Event entspricht nicht der .NET-Spezifikation.
      Mach Dir eine eigene EventArgs-Klasse, in die Du Deine HSVColor reinpacken kannst, deren Instanz übergib dann mit dem Event:

      VB.NET-Quellcode

      1. Public Class HsvEventArgs
      2. Inherits EventArgs
      3. Private _MyColor As HSVColor
      4. Public ReadOnly Property MyColor As HSVColor
      5. Get
      6. Return _MyColor
      7. End Get
      8. End Property
      9. Public Sub New(col As HSVColor)
      10. _MyColor = col
      11. End Sub
      12. End Class

      VB.NET-Quellcode

      1. Public Event ColorChanged As EventHandler(Of HsvEventArgs)

      VB.NET-Quellcode

      1. Dim mouseheight As Integer = Convert.ToInt32(((255 - HSVColor.Value) / 255) * valuerectangle.Height + valuerectangle.Y)
      2. ' ...
      3. RaiseEvent ColorChanged(Me, New HsvEventArgs(Me._hsvcolor))

      VB.NET-Quellcode

      1. Private Sub ColorPicker1_ColorChanged(sender As Object, e As HsvEventArgs) Handles ColorPicker1.ColorChanged
      2. Me.Label1.Text = e.MyColor.ToString
      3. End Sub

      VB.NET-Quellcode

      1. Public Overrides Function ToString() As String
      2. ' z.B.
      3. Return Me.ToColor().ToString()
      4. End Function

      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Mir ist gerade ein Bug aufgefallen, den ich mir selbst überhaupt nicht erklären kann. Wenn ich die Color-Property im Propertygrid ändere und dafür eine Farbe rechts, also eher ein violett, verwende bekomme ich die Fehlermeldung "arithmetischer Überlauf". Im Folgenden das Bild zur Fehlermeldung in der HSVColor-Klasse:



      Der Fehler rühre daher, dass ich durch 0 teile, allerdings ist 255-0 ungleich 0 ... so weit ich weiß :D

      Weiß jemand von euch was das Problem daran sein könnte?

      8-) faxe1008 8-)

      faxe1008 schrieb:

      Fehlermeldung
      Algorithmisch hast Du hier die seltene Chance, den kompletten Wertebereich Deiner Daten einzeln abtesten zu können:

      VB.NET-Quellcode

      1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      2. For a = 0 To 255
      3. For r = 0 To 255
      4. For g = 0 To 255
      5. For b = 0 To 255
      6. Dim col = Color.FromArgb(a, r, g, b)
      7. ' Deine Klasse testen
      8. Next
      9. Next
      10. Next
      11. Next
      12. End Sub
      Wenn Du die Schleifen mit Step 4 oder Step 8 durchlaufen lässt, solltest Du relativ schnell an die interessante Fehlerstelle kommen.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Ich habe ihn mal kompiliert und in eine Test Anwendung eingebaut, nun sieht zwar ganz nett aus aber nicht so cool wie das Bild in der GRaphic. In ersten Post. Kann man den auch noch Cooden??

      Und warum nutzt du .net 4.5 wen 4.0 schon ausreichend ist, lieber immer die niedrigste Version von Framework benutzen aus gründen der Kompatiblität.

      LG, Herbrich

      J.Herbrich schrieb:

      Und warum nutzt du .net 4.5 wen 4.0 schon ausreichend ist, lieber immer die niedrigste Version von Framework benutzen aus gründen der Kompatiblität.

      Das ist nicht wahr, da das FW nicht 100% abwärtskompatibel ist. FW4.0 sollte aber drin sein.
      »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais

      J.Herbrich schrieb:

      Ich habe ihn mal kompiliert und in eine Test Anwendung eingebaut, nun sieht zwar ganz nett aus aber nicht so cool wie das Bild in der GRaphic. In ersten Post. Kann man den auch noch Cooden??


      Die 3D-Variante des Wheels wäre extrem Benutzer unfreundlich, da die Auswahl nur sehr ungenau wäre und das Wheel riesengroß sein müsste. Programmierbar wäre sie vermutlich schon

      J.Herbrich schrieb:

      Und warum nutzt du .net 4.5 wen 4.0 schon ausreichend ist, lieber immer die niedrigste Version von Framework benutzen aus gründen der Kompatiblität.


      Das sind meine Standarteinstellungen was das Kompilieren angeht - wenn irgendjemand wirklich Interesse am Control hat kann er sich die Projektdatei ansehen und einfach die Kompilierungseinstellungen ändern ;)

      8-) faxe1008 8-)

      faxe1008 schrieb:

      Die 3D-Variante des Wheels
      Mach V auf das Wheel, H und S über Mouse(x, y).
      Wenn das ganz geschickt programmmiert wird, kannst Du dynamisch die Kegelachse mit dem Mausrad koppeln.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Ich würde gerne mal die 3D variante haben. Ich selber habe es unter 4.0 Compiliert, und ja die Standart einstellungen merkt man am Icon im Debug ordner, das zu ziemlich den veralteten vb6 design endspricht was ms wohl für VS2012 wieder hervorgekramt hat.

      LG, Herbrich
      Jennifer sagt: Standard*
      "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

      Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!