Introduction
There can be a lot of trial and error, tweaking the code, and running the code many times, to create the correct color blend. I thought it would be nice to have a control like those seen in drawing programs that would create the blend visually to get a proper placement of the colors. I will also try to explain the basics of making a color blended brush to paint with. This is an update to my original ColorBlender. It is still the same core idea, but I was never really satisfied with the layout and how to use it.
Build-A-Blend
Building a two color color blend is fairly simple. Using a LinearGradientBrush
from the System.Drawing.Drawing2D
namespace, you can paint an area with a blend from one color to another color.
Dim rect As New Rectangle(0, 0, 100, 100)
Using br As New LinearGradientBrush( _
rect, _
Color.White, _
Color.Black, _
LinearGradientMode.Horizontal)
g.FillRectangle(br, rect)
End Using
If you want more than two colors, a ColorBlend
is needed. First, create an array of colors for each position in the blend. Then, create an array of values representing the position of each color in the color array. The first color has a position of 0, and the last color has a position of 1. All the other colors in between have a decimal position value between 0 and 1. After creating the arrays, assign them to the ColorBlend
's Colors
and Positions
properties. Then, create the same LinearGradientBrush
as before, setting the two colors to any color just as a placeholder. Set the Brush
's InterpolationColors
property to the ColorBlend
just created to set the new multicolor blend. The LinearGradientBrush
can be replaced with the PathGradientBrush
for more complex shapes.
Dim blend As ColorBlend = New ColorBlend()
Dim bColors As Color() = New Color() { _
Color.Red, _
Color.Yellow, _
Color.Lime, _
Color.Cyan, _
Color.Blue, _
Color.Violet}
blend.Colors = bColors
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)
br.InterpolationColors = blend
g.FillRectangle(br, rect)
End Using
gColorBlender Control
The gColorBlender
User Control consists of a horizontal bar of color with a starting and ending color gradient. Clicking along the bar adds a new color pointer. The pointer can be dragged back and forth to any position along the bar with the left button, and removed with the right button. Preset color swatches, an owner-drawn ComboBox with all the known colors, a Dimmer Slider, and ARGB Sliders can be changed to alter the color and transparency of the selected pointer. There is a sample preview on the control to display the current blend. The LinearGradientBrush
paints the ColorBlend
on a line from point A to point B. The PathGradientBrush
paints the blend around a path. Right-Click the Sample to change these. The sample can be hidden by setting the ShowSample
property to False
Events
Control Properties
Here is a list of the primary properties:
cbColor
Array of colors used in cBlenderItems
class ColorBlend
.
cbPosition
Array of color positions used in ColorBlend
.
BorderColor
For use as an accent color like a border.
FocalPoints
The CenterPoint and FocusScales for the Drawing.Drawing2D.ColorBlend
.
BlendGradientType
Type of brush used to paint the ColorBlend
- Linear
or Path
.
BlendGradientMode
Type of linear gradient color blend.
BlendPathShape
Shape of path for the ColorBlend
- Rectangle
, Ellipse
, Triangle
, Polygon
.
BlendPathCenterPoint
Position of the center of the path ColorBlend
.
BarHeight
Height of color blender bar.
ShowSample
Show or hide the sample.
Methods
Methods to use outside the control.
GetColorBlendForBrush
- Returns a Drawing.Drawing2D.ColorBlend
.
BlendConvertCenterPoint
- Returns a CenterPoint
for the given Rectangle
relative to the sample CenterPoint
.
Mouse Events
Track if the cursor is over a pointer to select or add a pointer, or any of the other rectangles like the
sample, border selector, or arrow buttons. Rectangles are easily checked by seeing if the mouse's X, Y is contained within the boundaries of the rectangle, i.e., rectSample.Contains(X, Y)
. Checking a
path has the extra step of being converted to a Region first.
Using PointerRegion As New Region(BuildPointer(GetpX(1)))
Return PointerRegion.IsVisible(X, Y)
End Using
Drawing
Contains the routines to draw the pointers, build the brushes, and build the ColorBlend
.
Painting
Paint the control to a Bitmap
to create a buffer, eliminating flicker.
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
MyBase.OnPaint(e)
BuildABlend()
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
Dim br As Brush = LinearBrush(rectBar, LinearGradientMode.Horizontal)
g.FillRectangle(br, rectBar)
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
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
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
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
e.Graphics.DrawImage(bitmapBuffer.Clone, 0, 0)
bitmapBuffer.Dispose()
br.Dispose()
g.Dispose()
End Sub
SortCollection
Method that uses the CallByName
function to sort the Collection
of pointers by the pPos
property value.
ColorBox
Is an owner-drawn ComboBox with the DrawItem
event overridden to list the known colors.
I finally have a way to add Named Colors
to a list ordered by color. The KnownColor
enumeration is alphabetic so the only way to do it before was to use a hard coded list of color name strings
in the order you wanted rather than the built in enumeration.
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
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
Now add the sorted list to the ComboBox:
With ColorBox
.Items.Clear()
For Each c As Color In cList
.Items.Add(c.Name)
Next
End With
ColorExtensions Module
While working with Color
objects named colors sometimes get changed to their ARGB equivalents. So Color.Yellow
becomes
Color.FromArgb(255, 255, 255, 0)
. Visually the colors are the same and the actual
A
, R
, G
, and B
property values are equal,
but (Color.Yellow = Color.FromArgb(255, 255, 255, 0))
is False
. Set a color to Color.Yellow
and the
Name
property returns
"Yellow". Set a color using the ARGB values like Color.FromArgb(255, 255, 255, 0)
and the
Name
property returns "ffffff00".
This can be a real pain, especially when dealing with the ColorBox
that only has named colors in it. To keep the colors
consistent I use the
GetColorNearestName
,
GetColorNearestKnown
, GetColorBestName
, and GetColorBest
functions. These functions keep things in order, give
the ability to restrict the colors to only KnownColors
, or just get the best name possible.
GetColorNearestName
: Returns the Name of the KnownColor
that most closely matches the given color
GetColorNearestKnown
: Returns the KnownColor
that most closely matches the given color
GetColorBestName
: If the given color matches a KnownColor
that
name is returned, otherwise the current name is returned
GetColorBest
: If the given color matches a KnownColor
the KnownColor
is returned, otherwise the ARGB Color is returned
Additional functions in this Module are:
DimTheColor
: Takes the given color and
lightens or darkens it by the given valueGrayTheColor
: Takes the given color and returns its gray equivalent
I made these functions into Extensions
. by adding the <Extension()>
attribute from the System.Runtime.CompilerServices
namespace. Now any object with data type Color
will have these functions directly associated to them. In other words typing a period after the color object will show these functions in the IntelliSense.
Usage
The control can be used as a stand alone control directly on the form like in the main
source project.
As shown in the demo project, it can also be used in a ToolStripDropDown
:
or as a TypeEditor in the PropertyGrid.
UITypeEditor
Use of the Type Converters and editors are fully explained my UITypeEditor article: UITypeEditorsDemo[^].
Pointer
The cPointer
class contains four properties.
pPos
- Position of the color.
pColor
- Color at the position value.pAlpha
- Value from 0 to 255 that is the Transparency level of the color.
pIsCurr
- The pointer currently selected.
History
- Version 1.0 - July 2008.
- Version 2.0.3 July 2012.