Introduction
This pair of classes is useful for graphics programs which need colour washes, or need smooth flowing palettes. CGradient
provides all the useful functions for creating colour washes which can be rendered into CPalettes
or arrays of RGBTRIPLES
. CGradientCtrl
is a control for editing the CGradient
.
I wrote this pair of classes because I've never seen anything available which does the job, and as it was kind of crucial to my Fractal Generating Program, I started coding from scratch . The only place I've ever seen a control like this is in the Corel DRAW series, and the CGradientCtrl
is clone of Corel's version.
New in This Version
CGradient
- Peg positions defined on a float scale of 0 - 1
- The gradient can be quantized to make a blocky palette
- A choice of 10 different interpolation methods
- Background can now be selected.
CGradientCtrl
- Horizontal and Vertical Display modes
- Keyboard support
- Pegs can be shown on the left side of the gradient, right side, both or neither
- Smooth dragging of pegs
- Customizable Tooltips
- Fixed slight blockiness in the rendering of the gradient
- Better rendering of the wash
Note: The CGradient and CGradientCtrl 2.0 classes have been manipulated quite significantly since version 1.0, so the class cannot be a drop-replacment to the 1.0 class.
CGradientCtrl Demo
I hope that the control is reasonably obvious how to use. In the demo I used a slightly modified version of the Microsoft CFireWnd
to demonstrate how CGradient
can be used to paletize 8-bit bitmaps. I've also provided a few sample gradient files containing some interesting colour effects.
CGradient Features
How it works
I have used some terms which I created myself to describe features of a colour wash.
The diagram shows an annotated diagram of a gradient. The start peg and the end peg are fixed at either end of the gradient, with the movable colour pegs in-between them. Every peg has an associated colour, and the colour wash interpolated between the two the nearest pegs. The gradient also has an optional background colour which can become the 1st entry in rendered palettes.
Interpolation Methods
The gradient class now provides a choice of interpolation methods:
Linear Interpolation
With linear interpolation each channel is treated individually, and the actual channel values are calculated by making weighted averages to move between pegs. The method can make the transition over a peg look rather sharp.
Cosine Interpolation
Cosine interpolation provides smoother gradients than linear interpolation by using 180� segments of the cosine wave. Again each channel is interpolated independently of the others. This method tends to lead to a smoother flowing palettes especially around the pegs.
Flat Start, Flat End, Flat Middle Interpolation
The three flat interpolation methods fill the gap between pegs with a constant colour. Flat Start and End use the colours of the start and end pegs of a segment respectively as the constant colour, and Flat Middle interpolation takes an average of the two pegs. These methods make the palette look really blocky.
Reverse Interpolation
Reverse interpolation is linear interpolation in reverse! Instead of starting with the first colour and fading through to the last for each peg, interpolation starts from the last colour and works in reverse. This method makes the palette look really cut up.
HSL Clockwise, HSL Anticlockwise, HSL Shortest, HSL Longest Interpolation
With the HSL interpolation methods, colour is interpolated using the colour wheel. Saturation, luminance and hue values are interpolated linearly. Because the hue value is a circular type value, there are always two ways of getting from one point on the circle to the other - a long route and a short route. With clockwise and anticlockwise modes the hue is always interpolated by the clockwise or anticlockwise route respectively. And with shortest and longest modes the hue is always interpolated by the shortest or longest routes. These modes allow you to create a gaudy rainbow effect between pegs.
CPeg Class
Every peg has an index. The end pegs and the background have special indexes. The actual value is irrelevant as I have #defined all the symbols in the "Gradient.h" file.
Peg |
Symbol |
Index (irrelevant as the symbols are #defined) |
Background |
BACKGROUND |
-4 |
Start Peg |
STARTPEG |
-3 |
End Peg |
ENDPEG |
-2 |
None |
NONE |
-1 |
The information of the pegs is stored in a CArray
of CPeg
s. The CPeg
is a class which contains the data for a gradient peg.
class CPeg : CObject
{
public:
CPeg();
CPeg(const CPeg &src);
CPeg& operator = (const CPeg &src);
void Serialize(CArchive &ar);
const UINT GetID() const {return id;};
DECLARE_SERIAL(CPeg)
COLORREF colour;
float position;
protected:
UINT id;
static UINT uniqueID;
};
CGradient Class
CGradient
is publicly derived from the MFC CObject class
Construction & Destruction
CGradient()
Creates a CGradient
with no pegs except the start peg and the end peg which are assigned by default to black and white respectively.
CGradient(CGradient &gradient)
Copy constructor.
virtual ~CGradient()
Destructor
Operators
CGradient& operator =(CGradient &src)
Assignment operator
Attributes
int GetPegCount() const
Retrieves the number of pegs in the gradient. This does not include the start and end pegs.
const CPeg GetPeg(int iIndex) const
Returns a copy of a peg from a given index. If iIndex
is a fixed peg (STARTPEG
, ENDPEG
or BACKGROUND)
, the pegs for these will be returned.
int SetPeg(int iIndex, COLORREF crColour, int iPosition)
Will set a peg of a given index with this colour and position info. The function returns the new index of the peg, in case it has been moved. -1 is returned if iIndex
refers to a fixed peg such as BACKGROUND
, STARTPEG
or ENDPEG
.
int SetPeg(int iIndex, CPeg peg)
Similar to SetPeg(int, COLORREF, int)
, except the pegs are set from a CPeg not position and colour data.
int AddPeg(COLORREF crColour, float iPosition)
Adds a peg with colour crColour
and position iPosition
. If iPosition
is less than 0 or more than the gradient size, it will be truncated so that it falls within the bounds of the gradient. The return value is the index of the new peg.
int AddPeg(CPeg peg)
Add a peg to the gradient, the return value is the index of the peg.
void RemovePeg(int iIndex)
Deletes a peg at index iIndex
.
int IndexFromPos(float pos)
Returns the index of the peg which appears at the beginning of the interpolation segment, which pos
fall inside.
void SetStartPegColour(const COLORREF crColour)
void SetEndPegColour(const COLORREF crColour)
void SetBackgroundColour(const COLORREF crColour)
All three set the colour of their respective fixed pegs.
COLORREF GetStartPegColour() const
COLORREF GetEndPegColour() const
COLORREF GetBackgroundColour() const
All three will return the colour of their respective pegs.
void SetUseBackground(const BOOL bUseBackground)
Sets the Gradient's Background mode. If bUseBackground
is TRUE
0'th entry in a palette will be set to the bacground colour, if bUseBackground
is FALSE
, the 0'th entry will be the first colour in the wash.
BOOL GetUseBackground() const
Return the background mode. See SetUseBackground
...
InterpolationMethod GetInterpolationMethod() const
Retrieves the interpolation method used on the gradient.
It can be
CGradient::Linear
,
CGradient::FlatStart
,
CGradient::FlatMid
,
CGradient::FlatEnd
,
CGradient::Cosine
,
CGradient::HSLRedBlue
,
CGradient::HSLBlueRed
,
CGradient::HSLShortest
,
CGradient::HSLLongest
or
CGradient::Reverse
void SetInterpolationMethod(const InterpolationMethod method)
Sets the interpolation method to and of the values listed above.
void SetQuantization(const int entries)
SetQuantization allow you to set a number of entries that the palette should be quantized to. So with 8 entries the palette will have 8 discrete blocks in it. If entries is -1 quantization is disabled.
int GetQuantization() const
Returns the number of entries the palette should be quantized to. If -1 is returned, quantization is not active.
Operations
void MakePalette(CPalette *lpPal);
Creates a windows palette into the pointer, and fills the palette with the 256 colours from the gradient.
void Make8BitPalette(RGBTRIPLE *lpPal);
Fills an array of 256 RGBTRIPLE
s with the gradient colours.
void MakeAllEntries(RGBTRIPLE *lpPal, int iEntryCount);
Fill the array of RGBTRIPLE
s lpPal
of size iEntryCount
with the colour gradient. This function allows you to flexible create any type of palette from 2-colour palettes to 65525 palettes.
COLORREF ColourFromPosition(int iPos);
Returns the colour of the gradient at a given position.
void Serialize(CArchive &ar);
Standard serialization function.
CGradientCtrl Features
The CGradientCtrl
is a CWnd
derived control that allows the viewing and editing of the CGradient
class.
Features...
- Full keyboard support
- Now runs in horizontal and vertical modes.
- Displays the movable pegs as series of arrows, and the end pegs as a pair of squares.
- The control shows the gradients as a vertical column, with a smooth movement of colour.
- As pegs are dragged, the gradient is updated dynamically.
- Pegs which are close together are stacked up:
- Pegs can be shown on the left side, right side, none or both sides
- Customizable Tooltip support.
Peg Sides
|
|
|
|
No Pegs - Read only! |
Left/Bottom side pegs |
Right/Top side pegs |
Both side pegs |
Keyboard Support
Keys |
Function |
Tab |
Select Next Peg |
Up, Left |
Slide peg up towards start |
Down, Right |
Slide peg towards end |
Home |
Slide peg to the start |
End |
Slide peg to end |
Del, Backspace |
Delete Peg |
Return, Space |
Equivalent to double clicking on a peg |
Insert |
Duplicates a peg |
CGradientCtrl Class
CGradient
control is derived from the MFC CWnd class
Construction
CGradientCtrl();
Default constructor.
BOOL Create(const RECT& rect, CWnd* pParentWnd, UINT nID);
Creates a control in the window pParentWnd
, in the rectangle rect
, and with the ID nID
. The return value is the success of the function.
Attributes
void SetGradientWidth(int iWidth);
The function sets the width that the gradient is drawn with the control. iWidth
specifies the number of pixels width the control should be. If GCW_AUTO
is passed to iWidth
, the width of control will be automatically adjusted to best fit the control into it's window.
int GetGradientWidth();
Returns the width that gradient is set to draw to. The function may return GCW_AUTO
which means that the gradient width will be drawn so that it is best fitted into the control window.
int GetSelIndex();
Returns the index of the currently selected peg. This value return may be STARTPEG
, ENDPEG
or NONE
.
int SetSelIndex(int iSel, BOOL bUpdate);
Sets the currently selected peg to the index iSel
. This value must be either a peg index between 0, and the number of movable pegs, or STARTPEG
, ENDPEG
, or NONE
. If bUpdate
is TRUE
, the control is redrawn. The return value is the index of the previously selected peg.
CPeg GetSelPeg();
Returns an CPeg
structure representing the currently selected peg. If the currently selected peg is an end peg, the position value in the CPeg
class will be set to -1.
CGradient& GetGradient();
Returns a reference to the CGradient
from which the control works.
void SetGradient(CGradient src)
Copies the source gradient into the controls embedded CGradient
.
void ShowTooltips(BOOL bShow = true)
Switches tooltips on, or off
CGradient::Orientation GetOrientation() const
Returns the orientation mode of the control. The function can return CGradient::ForceHorizontal
, CGradient::ForceVertical
or CGradient::Auto
. For CGradient::Auto
, the orientation is automatically selected. So if the width is greater that the height, the control will be shown in horizontal mode.
void SetOrientation(CGradient::Orientation orientation)
Sets the orientation mode of the control. The modes CGradient::ForceHorizontal
, CGradient::ForceVertical
or CGradient::Auto
are available.
void SetPegSide(BOOL setrightup, BOOL enable)
Sets the peg side mode for the control. So if setrightup
is TRUE
, enable will enable disable peg drawing on the right or top side. With right FALSE
, enable will enable or disable pegs displayed on the left or bottom side.
BOOL GetPegSide(BOOL rightup) const
Retrieves the peg side mode for the side specified by rightup
. So with rightup
TRUE
, GetPegSide
will return a BOOL
for whether pegs are to be displayed on the right or upper side of the control.
void SetTooltipFormat(const CString format)
Sets the tooltip format string to the value of format
. A tool-tip format string is a piece of multi-line tokened text with a line associate with each possible tool-tip. So the default British tool-tip string looks like this:
&SELPOS\nPosition: &SELPOS Colour: R &R G &G B &B\nColour: R &R G &G B &B\nColour: R &R G &G B &B\nDouble Click to Add a New Peg
The string is made up of a series of tokens:
Token |
Meaning |
&SELPOS |
Displays the position of the selected text. |
&R |
Displays the red component of the selected peg in decimal (0-255). |
&G |
Displays the green component of the selected peg in decimal (0-255). |
&B |
Displays the blue component of the selected peg in decimal (0-255). |
&HEXR |
Displays the red component of the selected peg in two digit hex (00-FF). |
&HEXG |
Displays the green component of the selected peg in two digit hex (00-FF). |
&HEXB |
Displays the blue component of the selected peg in two digit hex (00-FF). |
&FLOATR |
Displays the red component of the selected peg as a floating point value between 0.0 and 1.0 to three decimal place accuracy. |
&FLOATG |
Displays the green component of the selected peg as a floating point value between 0.0 and 1.0 to three decimal place accuracy. |
&FLOATB |
Displays the blue component of the selected peg as a floating point value between 0.0 and 1.0 to three decimal place accuracy. |
&& |
Displays an ampersand - "&" |
Each line is assigned to a different tool-tip for a different situation:
Line |
Use |
1 |
Used when the peg is being dragged |
2 |
Used when the mouse hovers over a peg. |
3 |
Used when the mouse hovers over then start peg |
4 |
Used when the mouse hovers over then end peg |
5 |
Used when the mouse hovers over the gradient area |
This system of token strings allows the control to be fairly neutral with respect to internationalization. The token strings can be stored in string table so that they can be auto-selected.
CString GetTooltipFormat() const;
Returns the current tool-tip format string.
Operations
void DeleteSelected(BOOL bUpdate);
Deletes the selected peg. No operation is performed if the selected peg is not a movable peg. If bUpdate
is TRUE
, the control is redrawn.
int MoveSelected(int iNewPos, BOOL bUpdate);
Moves the selected peg to a new position iNewPos
. No operation is performed if the selected peg is not a movable peg. If iNewPos
is less than 0 or greater than the gradient size. If bUpdate
is TRUE
, the control is redrawn. The return value is the new index of the moved peg.
COLORREF SetColourSelected(COLORREF crNewColour, BOOL bUpdate);
Sets the colour of the selected peg to new crNewColour
. If bUpdate
is TRUE
, the control is redrawn. The return value is the previous colour of the selected peg.
Notification Messages
The gradient control may send any of these notification messages to it's parent window. The CGradientCtrl
uses two NMHDRs
: PegNMHDR
, and DoubleClickCreateNMHDR
struct PegNMHDR
{
NMHDR nmhdr;
CPeg peg;
int index;
};
struct PegCreateNMHDR
{
NMHDR nmhdr;
float position;
COLORREF colour;
};
GC_SELCHANGE
Value: 1
Notification that the the selected peg has changed. A PegNMHDR
is sent containing information about the currently selected peg. The return value is ignored.
GC_PEGMOVE
Value: 2
Notification that a colour peg is currently being moved. A PegNMHDR
is sent containing information about the peg being dragged. The returned value is ignored.
GC_PEGMOVED
Value: 3
Notification that a movement operation has just completed. A PegNMHDR
is sent containing information about the peg dragged. The returned value is ignored.
GC_PEGREMOVED
Value: 4
Notification that a peg has been removed. A PegNMHDR
is sent containing information about the moved peg. The returned value is ignored.
GC_CREATEPEG
Value: 5
Notification that the user has double clicked on an empty spot on the gradient. This event can can also be triggered by the user pressing the "Insert" key. You may want to bring up a dialog asking for the colour of the new peg, or as in the case of the demo create a peg of a with the gradient colour in the position. A DoubleClickCreateNMHDR
is sent containing the position that was double clicked, and colour present at that point.
GC_EDITPEG
Value: 6
Notification that the user has double clicked on a peg. This event can can also be triggered by the user pressing the Space Bar or the Return key. This message is sent to request the host to show an editing dialog. A PegNMHDR
is sent containing information about the peg to be edited. The returned value is ignored.
GC_CHANGE
Value: 7
Notification is sent when the user makes any change to the gradient. This is a catch-all for all the above notifications. A PegNMHDR
is sent containing information about the peg that has changed. The returned value is ignored.
Using CGradientCtrl
Into your dialog template you'll need to create a custom control (). Then you'll need to configure it. Set the windows ID to something like IDC_GRADIENT
. The caption of the control is irrelevant. Set the class to "MFCGradientCtrl
". Leave the style on 0x50010000
, and if you want the control to have a client edge as shown in the demo you'll need to set the ExStyle to 0x200
. VC should look something like this...
Now in the source for your dialog, in DoDataExchange
add this line:
DDX_Control(pDX, IDC_GRADIENT, m_wndGradientCtrl);
The code should look like this when you've finished
void CFooDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_GRADIENT, m_wndGradientCtrl);
}
In the header file add this line:
CGradientCtrl m_wndGradientCtrl;
You can put it anywhere in the file. It will should work fine. I put mine right beneath the dialog data section.
Forthcoming Improvements
Of course I will endeavour to fix all the bugs that get reported. Transparency support would be nice soon!
Revisions
Revision 2.0: Added Interpolation methods, quantization, keyboard support, notification messages, tooltips, horizontal and vertical orientation modes.
Revision 1.0B: Fixed resource leak in demo (I think). Other minor changes.
Revision 1.0A: Added message GC_CHANGE
, and tested CGradientCtrl::Create
with successful results.
British and Proud!
Sorry if the abundant use of colour rather than color offends you! :-)