Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CGradient and CGradientCtrl

0.00/5 (No votes)
29 Jan 2003 6  
A pair of classes for rendering and editing colourful washes

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

 

Flat Start
 
Flat End
 
Flat Middle
 

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 CPegs. The CPeg is a class which contains the data for a gradient peg.

class CPeg : CObject
{
public:
  CPeg();                                 // Default Constructor

  CPeg(const CPeg &src);                  // Copy Constructor

  CPeg& operator = (const CPeg &src);     // Assignment operator

  void Serialize(CArchive &ar);	          // Used to save the data to a file

  const UINT GetID() const {return id;};  // Returns the unique id of the peg

  
  DECLARE_SERIAL(CPeg)     // Handy MFC thingy

  COLORREF colour;         // The colour of the peg

  float position;          // The position of the peg


protected:
  UINT id;                 // The unique ID of the peg

  static UINT uniqueID;    // Used to create new  ids for fresh pegs

};

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 RGBTRIPLEs with the gradient colours.

void MakeAllEntries(RGBTRIPLE *lpPal, int iEntryCount);

Fill the array of RGBTRIPLEs 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

// Standard NMHDR used for all the notifications except GC_DBL_CREATE
struct PegNMHDR
{
	NMHDR nmhdr;		// Standard NMHDR

	CPeg peg;		// A copy of the CPeg in quesyion

	int index;		// The index of the peg in question

};

// NMHDR used in the GC_DBL_CREATE
struct PegCreateNMHDR
{
	NMHDR nmhdr;		// Standard NMHDR

	float position;		// The position at which a peg should be created

	COLORREF colour;	// The colour at the position

};

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);
    //{{AFX_DATA_MAP(CFooDlg)

    //}}AFX_DATA_MAP

    DDX_Control(pDX, IDC_GRADIENT, m_wndGradientCtrl); //<-- Add this line

}

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! :-)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here