ColorBlender - 动态渐变颜色混合创建控件 (VB.NET)






4.90/5 (29投票s)
如何创建 ColorBlend 以及两个颜色混合用户控件以简化操作。
引言
创建正确的颜色混合可能需要大量的试错、调整代码和多次运行代码。我认为拥有一个像绘图程序中那样的控件会很好,它可以直观地创建混合以获得正确的颜色放置。我还将尝试解释制作颜色混合画笔进行绘制的基础知识。这是我原始 ColorBlender 的更新。它仍然是相同的核心思想,但我从未真正满意它的布局和使用方式。
构建混合
构建双色颜色混合相当简单。使用 System.Drawing.Drawing2D
命名空间中的 LinearGradientBrush
,可以用从一种颜色到另一种颜色的混合来绘制一个区域。
Dim rect As New Rectangle(0, 0, 100, 100)
Using br As New LinearGradientBrush( _
rect, _
Color.White, _
Color.Black, _
LinearGradientMode.Horizontal)
'Fill the rect with the blend
g.FillRectangle(br, rect)
End Using
如果需要两种以上的颜色,则需要 ColorBlend
。首先,为混合中的每个位置创建颜色数组。然后,创建表示颜色数组中每个颜色的位置的值数组。第一个颜色的位置为 0,最后一个颜色的位置为 1。所有介于两者之间的其他颜色都具有介于 0 和 1 之间的小数位置值。创建数组后,将它们分配给 ColorBlend
的 Colors
和 Positions
属性。然后,像以前一样创建相同的 LinearGradientBrush
,将两种颜色设置为任何颜色作为占位符。将 Brush
的 InterpolationColors
属性设置为刚刚创建的 ColorBlend
,以设置新的多色混合。LinearGradientBrush
可以替换为 PathGradientBrush
以用于更复杂的形状。
Dim blend As ColorBlend = New ColorBlend()
'Add the Array of Color
Dim bColors As Color() = New Color() { _
Color.Red, _
Color.Yellow, _
Color.Lime, _
Color.Cyan, _
Color.Blue, _
Color.Violet}
blend.Colors = bColors
'Add the Array Single (0-1) colorpoints to place each Color
Dim bPts As Single() = New Single() { _
0, _
0.327, _
0.439, _
0.61, _
0.777, _
1}
blend.Positions = bPts
Dim rect As New Rectangle(0, 0, 100, 100)
Using br As New LinearGradientBrush( _
rect, _
Color.White, _
Color.Black, _
LinearGradientMode.Horizontal)
'Blend the colors into the Brush
br.InterpolationColors = blend
'Fill the rect with the blend
g.FillRectangle(br, rect)
End Using
gColorBlender 控件
gColorBlender
用户控件由一个带有起始和结束颜色渐变的水平颜色条组成。沿着条形单击会添加一个新的颜色指针。可以用左键将指针拖动到条形上的任何位置,并用右键移除。预设的颜色样本、带有所有已知颜色的自绘组合框、调光滑块和 ARGB 滑块可以更改以改变所选指针的颜色和透明度。控件上有一个样本预览以显示当前的混合。LinearGradientBrush
沿着从点 A 到点 B 的线绘制 ColorBlend
。PathGradientBrush
沿着路径绘制混合。右键单击样本以更改这些。通过将 ShowSample
属性设置为 False
可以隐藏样本。
事件
公共事件 BlendChanged()
此事件将在 BuildABlend
子例程中触发。
控件属性
以下是主要属性列表
gColorBlend
cBlenderItems
类中的属性数组。
cbColor
cbPosition
BorderColor
FocalPoints
BlendGradientType
BlendGradientMode
BlendPathShape
BlendPathCenterPoint
BarHeight
ShowSample
cBlenderItems
类 ColorBlend
中使用的颜色数组。
ColorBlend
中使用的颜色位置数组。
用作边框等强调色。
Drawing.Drawing2D.ColorBlend
的 CenterPoint 和 FocusScales。
用于绘制 ColorBlend
的画笔类型 - Linear
或 Path
。
线性渐变颜色混合的类型。
ColorBlend
的路径形状 - Rectangle
、Ellipse
、Triangle
、Polygon
。
路径 ColorBlend
中心的位置。
颜色混合条的高度。
显示或隐藏样本。
方法
在控件外部使用的方法。
GetColorBlendForBrush
- 返回Drawing.Drawing2D.ColorBlend
。BlendConvertCenterPoint
- 返回给定Rectangle
相对于样本CenterPoint
的CenterPoint
。
鼠标事件
跟踪光标是否位于指针上方以选择或添加指针,或任何其他矩形(如样本、边框选择器或箭头按钮)。通过检查鼠标的 X、Y 是否包含在矩形的边界内,可以轻松检查矩形,即 rectSample.Contains(X, Y)
。检查路径需要额外一步,即先将其转换为区域。
'Convert to Region.
Using PointerRegion As New Region(BuildPointer(GetpX(1)))
'Is the point inside the region.
Return PointerRegion.IsVisible(X, Y)
End Using
绘图
包含绘制指针、构建画笔和构建 ColorBlend
的例程。
绘制
将控件绘制到 Bitmap
以创建缓冲区,消除闪烁。
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
MyBase.OnPaint(e)
'Go through each Pointer in the collection
'to get the current Color and Position arrays
BuildABlend()
'Create a canvas to paint on the same size as the control
Dim bitmapBuffer As Bitmap = New Bitmap(ClientSize.Width, ClientSize.Height)
Dim g As Graphics = Graphics.FromImage(bitmapBuffer)
g.Clear(BackColor)
g.SmoothingMode = SmoothingMode.AntiAlias
g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit
' Paint the ColorBlender Bar with the Linear Brush
Dim br As Brush = LinearBrush(rectBar, LinearGradientMode.Horizontal)
g.FillRectangle(br, rectBar)
' Paint the Current Color and Position
br = New HatchBrush(HatchStyle.LargeCheckerBoard, Color.White, Color.Silver)
g.FillRectangle(br, rectCurrColor)
g.FillRectangle(New SolidBrush(Color.FromArgb(gszAlpha.Value, _
gszRed.Value, _
gszGreen.Value, _
gszBlue.Value)), rectCurrColor)
g.DrawRectangle(Pens.Black, rectCurrColor)
If CurrPointer > -3 Then
TextRenderer.DrawText(g,
CurrPos,
New Font("Arial", 8, FontStyle.Bold),
New Rectangle(rectCurrColor.Left - 3,
rectCurrColor.Bottom + 1, 50, 30),
Color.Black, BackColor, TextFormatFlags.Left)
g.FillRectangles(Brushes.White, New Rectangle() {rectLeft, rectRight})
g.FillPolygon(Brushes.MediumBlue, ptsLeft)
g.FillPolygon(Brushes.MediumBlue, ptsRight)
g.DrawRectangles(Pens.Black, New Rectangle() {rectLeft, rectRight})
End If
'Draw all the pointers in their Color at their Position along the Bar
Using pn As New Pen(Color.Gray, 1) With {.DashStyle = DashStyle.Dash}
g.DrawLine(pn, rectBar.Left, BarHeight + 7, rectBar.Right, BarHeight + 7)
pn.Color = Color.Black
pn.DashStyle = DashStyle.Solid
DrawPointer(g, StartPointer.ARGB, 0, StartPointer.pIsCurr)
DrawPointer(g, EndPointer.ARGB, 1, EndPointer.pIsCurr)
If MiddlePointers IsNot Nothing Then
For I As Integer = 1 To MiddlePointers.Count
DrawPointer(g, MiddlePointers(I).ARGB, _
MiddlePointers(I).pPos, I = CurrPointer)
Next
End If
End Using
If _showSample Then
'Draw Border
Dim rectHatch As Rectangle = rectBorderSelect
rectHatch.Inflate(-1, -1)
g.FillRectangle(br, rectHatch)
g.FillRectangle(New SolidBrush(
gColorBlend.BorderColor),
rectBorderSelect)
If CurrPointer = -3 Then
Using pn As New Pen(Brushes.Red, 1)
g.DrawLines(pn, New Point() {
New Point(rectBorderSelect.Left - 2, rectBorderSelect.Top + 4),
New Point(rectBorderSelect.Left - 2, rectBorderSelect.Top - 2),
New Point(rectBorderSelect.Right + 2, rectBorderSelect.Top - 2),
New Point(rectBorderSelect.Right + 2, rectBorderSelect.Top + 4)
})
End Using
End If
' Paint the ColorBlender Sample with the chosen Shape and BrushType
Using gp As New GraphicsPath, gph As New GraphicsPath
rectHatch = rectSample
rectHatch.Inflate(-1, -1)
If _gColorBlend.BlendGradientType = eBlendGradientType.Linear Then
gph.AddRectangle(rectHatch)
g.FillPath(br, gph)
gp.AddRectangle(rectSample)
br = LinearBrush(rectSample, _gColorBlend.BlendGradientMode)
Else
gph.AddPath(GetShapePath(rectHatch), False)
g.FillPath(br, gph)
gp.AddPath(GetShapePath(rectSample), False)
br = PathBrush(rectSample)
TextRenderer.DrawText(g, String.Format(
"cp: {1:0.00}, {2:0.00}{0}fs: {3:0.00}, {4:0.00}",
vbNewLine,
gColorBlend.FocalPoints.CenterPtX,
gColorBlend.FocalPoints.CenterPtY,
gColorBlend.FocalPoints.FocusPtX,
gColorBlend.FocalPoints.FocusPtY),
New Font("Arial", 8, FontStyle.Regular),
New Rectangle(rectSample.Left - 2,
rectSample.Bottom + 2,
rectSample.Width + 2, 30),
Color.Black, BackColor, TextFormatFlags.HorizontalCenter)
End If
g.FillPath(br, gp)
Using pn As New Pen(gColorBlend.BorderColor, 2)
g.DrawPath(pn, gp)
End Using
End Using
End If
'Draw the entire image to the control in one shot to eliminate flicker
e.Graphics.DrawImage(bitmapBuffer.Clone, 0, 0)
bitmapBuffer.Dispose()
br.Dispose()
g.Dispose()
End Sub
SortCollection
使用 CallByName
函数按 pPos
属性值对指针 Collection
进行排序的方法。
ColorBox
是一个自绘组合框,其 DrawItem
事件被重写以列出已知颜色。
我终于有了一种将命名颜色按颜色顺序添加到列表中的方法。KnownColor
枚举是按字母顺序排列的,所以以前唯一的方法是使用硬编码的颜色名称字符串列表,按照您想要的顺序,而不是内置枚举。
Dim cList As New List(Of Color)
For Each s As String In [Enum].GetNames(GetType(KnownColor))
If Not Color.FromName(s).IsSystemColor Then
cList.Add(Color.FromName(s))
End If
Next
cList.Sort(AddressOf SortColors)
Friend Function SortColors(ByVal x As Color, ByVal y As Color) As Integer
'To use it first add all non-system colors to a List(Of Color),
'sort it by calling colors.Sort(AddressOf SortColors),
'then add all the list colors to the combo Items.
Dim huecompare As Integer = x.GetHue.CompareTo(y.GetHue)
Dim satcompare As Integer = x.GetSaturation.CompareTo(y.GetSaturation)
Dim brightcompare As Integer = x.GetBrightness.CompareTo(y.GetBrightness)
If huecompare <> 0 Then
Return huecompare
ElseIf satcompare <> 0 Then
Return satcompare
ElseIf brightcompare <> 0 Then
Return brightcompare
Else
Return 0
End If
End Function
现在将排序后的列表添加到 ComboBox
With ColorBox
.Items.Clear()
For Each c As Color In cList
.Items.Add(c.Name)
Next
End With
ColorExtensions 模块
在使用 Color
对象时,命名颜色有时会更改为其 ARGB 等效项。因此 Color.Yellow
变为 Color.FromArgb(255, 255, 255, 0)
。在视觉上,颜色是相同的,实际的 A
、R
、G
和 B
属性值是相等的,但 (Color.Yellow = Color.FromArgb(255, 255, 255, 0))
为 False
。将颜色设置为 Color.Yellow
,Name
属性返回“Yellow”。使用 ARGB 值设置颜色,例如 Color.FromArgb(255, 255, 255, 0)
,Name
属性返回“ffffff00”。这可能是一个真正的麻烦,尤其是在处理只包含命名颜色的 ColorBox
时。为了保持颜色的一致性,我使用 GetColorNearestName
、GetColorNearestKnown
、GetColorBestName
和 GetColorBest
函数。这些函数保持事物有序,可以限制颜色仅限于 KnownColors
,或只获得最佳名称。
GetColorNearestName
:返回与给定颜色最接近的KnownColor
的名称GetColorNearestKnown
:返回与给定颜色最接近的KnownColor
GetColorBestName
:如果给定颜色与KnownColor
匹配,则返回该名称;否则返回当前名称GetColorBest
:如果给定颜色与KnownColor
匹配,则返回KnownColor
;否则返回 ARGB 颜色
此模块中的其他函数包括
DimTheColor
:根据给定值使给定颜色变亮或变暗GrayTheColor
:将给定颜色转换为其灰色等效色
我通过添加 System.Runtime.CompilerServices
命名空间中的 <Extension()>
属性,将这些函数转换为 Extensions
。现在,任何数据类型为 Color
的对象都将直接关联这些函数。换句话说,在颜色对象后键入一个点将在 IntelliSense 中显示这些函数。
用法
该控件可以作为独立控件直接在窗体上使用,如主源代码项目所示。
如演示项目所示,它也可以在 ToolStripDropDown
中使用。
或作为 PropertyGrid 中的 TypeEditor。
UITypeEditor
Type Converters 和编辑器的使用在我的 UITypeEditor 文章中得到了充分解释:UITypeEditorsDemo[^]。
指针
cPointer
类包含四个属性。
pPos
- 颜色的位置。pColor
- 位置值处的颜色。pAlpha
- 介于 0 到 255 之间的值,表示颜色的透明度级别。pIsCurr
- 当前选定的指针。
历史
- 版本 1.0 - 2008 年 7 月。
- 版本 2.0.3 - 2012 年 7 月。