65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (29投票s)

2008年8月1日

CPOL

6分钟阅读

viewsIcon

92448

downloadIcon

4426

如何创建 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 之间的小数位置值。创建数组后,将它们分配给 ColorBlendColorsPositions 属性。然后,像以前一样创建相同的 LinearGradientBrush,将两种颜色设置为任何颜色作为占位符。将 BrushInterpolationColors 属性设置为刚刚创建的 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 的线绘制 ColorBlendPathGradientBrush 沿着路径绘制混合。右键单击样本以更改这些。通过将 ShowSample 属性设置为 False 可以隐藏样本。

事件

  • 公共事件 BlendChanged()
  • 此事件将在 BuildABlend 子例程中触发。

控件属性

以下是主要属性列表

  • gColorBlend
  • cBlenderItems 类中的属性数组。

  • cbColor
  • cBlenderItemsColorBlend 中使用的颜色数组。

  • cbPosition
  • ColorBlend 中使用的颜色位置数组。

  • BorderColor
  • 用作边框等强调色。

  • FocalPoints
  • Drawing.Drawing2D.ColorBlend 的 CenterPoint 和 FocusScales。

  • BlendGradientType
  • 用于绘制 ColorBlend 的画笔类型 - LinearPath

  • BlendGradientMode
  • 线性渐变颜色混合的类型。

  • BlendPathShape
  • ColorBlend 的路径形状 - RectangleEllipseTrianglePolygon

  • BlendPathCenterPoint
  • 路径 ColorBlend 中心的位置。

  • BarHeight
  • 颜色混合条的高度。

  • ShowSample
  • 显示或隐藏样本。

方法

在控件外部使用的方法。

  • GetColorBlendForBrush - 返回 Drawing.Drawing2D.ColorBlend
  • BlendConvertCenterPoint - 返回给定 Rectangle 相对于样本 CenterPointCenterPoint

鼠标事件

跟踪光标是否位于指针上方以选择或添加指针,或任何其他矩形(如样本、边框选择器或箭头按钮)。通过检查鼠标的 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)。在视觉上,颜色是相同的,实际的 ARGB 属性值是相等的,但 (Color.Yellow = Color.FromArgb(255, 255, 255, 0))False。将颜色设置为 Color.YellowName 属性返回“Yellow”。使用 ARGB 值设置颜色,例如 Color.FromArgb(255, 255, 255, 0)Name 属性返回“ffffff00”。这可能是一个真正的麻烦,尤其是在处理只包含命名颜色的 ColorBox 时。为了保持颜色的一致性,我使用 GetColorNearestNameGetColorNearestKnownGetColorBestNameGetColorBest 函数。这些函数保持事物有序,可以限制颜色仅限于 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 月。
© . All rights reserved.