Windows SDK GDI: How do I fill rectangles, regions and texts in graded colours?
Q: How do I fill rectangles, regions and texts in graded colours?
A: Filling colours in a gradient way along one direction is shown in the following knowledge base article. Using the same technique, but extending it a bit, you can make rectangles, regions and texts in graded colours as shown in the attached image.
Filling rectangles in graded colours is done by calculating the smallest rectangle having the same background colour. Colour at a point is evaluated by linearly interpolating the colours at its four corners.
Here is the code for drawing a rectangle in graded colours. The code is wrapped into a neat function, arguments of which are device context handle (of type 'HDC'), rectangle object (of type 'RECT') and array to colours at corners (of type 'LPCOLORREF'). Colours at four corners are passed to this and similar functions in the clockwise order starting from left-top (left-top, right-top, right-bottom and left-bottom).
Code:
// Fills the rectangle 'rect' in graded colours
void GradientFill(HDC hdc, RECT rect, LPCOLORREF color_array)
{
// Calculates size of single colour bands
int xStep = (rect.right - rect.left) / 256 + 1;
int yStep = (rect.bottom - rect.top) / 256 + 1;
// X loop starts
for (int iX = 0, X = rect.left; iX < 256; iX++)
{
// Calculates end colours of the band in Y direction
int RGBColor[3][2] = {
{IPOL(GetRValue(color_array[0]), GetRValue(color_array[1]), iX),
IPOL(GetRValue(color_array[3]), GetRValue(color_array[2]), iX)
},
{IPOL(GetGValue(color_array[0]), GetGValue(color_array[1]), iX),
IPOL(GetGValue(color_array[3]), GetGValue(color_array[2]), iX)
},
{IPOL(GetBValue(color_array[0]), GetBValue(color_array[1]), iX),
IPOL(GetBValue(color_array[3]), GetBValue(color_array[2]), iX)
}
};
// Y loop starts
for (int iY = 0, Y = rect.top; iY < 256; iY++)
{
// Calculates the colour of the rectangular band
COLORREF Color = RGB(IPOL(RGBColor[0][0], RGBColor[0][1], iY),
IPOL(RGBColor[1][0], RGBColor[1][1], iY),
IPOL(RGBColor[2][0], RGBColor[2][1], iY));
// Creates the brush to fill the rectangle
HBRUSH hBrush = CreateSolidBrush(Color);
// Paints the rectangular band with the brush
RECT Rect = {X, Y, X + xStep, Y + yStep};
FillRect(hdc, &Rect, hBrush);
// Deletes the brush
DeleteObject(hBrush);
// Updates Y value of the rectangle
Y += yStep;
if (Y > rect.bottom)
Y = rect.bottom;
}
// Updates X value of the rectangle
X += xStep;
if (X > rect.right)
X = rect.right;
}
}
Creating a region in graded colours is done in three steps. First is to create the bounding rectangle of the region in graded colours and attach it to a memory DC. This memory DC is BitBlt-ed to the display DC in 'SRCINVERT' mode. Second step is to paint the region in black over this. Third and final step is to BitBlt the memory DC once more in 'SRCINVERT' mode. Whatever is inside the region will be painted in graded colours and whatever is outside will be cancelled due to two BitBlts of 'SRCINVERT' mode.
Here is the code. Arguments are similar except that the second is the region instead of rectangle in earlier snippet.
Code:
// Fills the region 'rgn' in graded colours
void GradientFill(HDC hdc, HRGN rgn, LPCOLORREF color_array)
{
// Creates memory DC
HDC hMemDC = CreateCompatibleDC(hdc);
if (hMemDC) // Memory DC creation successful
{
// Gets bounding rectangle of region
RECT rectRgn;
GetRgnBox(rgn, &rectRgn);
// Left top point of applying mask
int X = rectRgn.left, Y = rectRgn.top;
// Size of mask
int Width = rectRgn.right - X, Height = rectRgn.bottom - Y;
// Creates bitmap for the mask
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, Width,
Height);
if (hBitmap) // Bitmap created successfully
{
// Selects bitmap in memory DC
HBITMAP hOldBitmap = (HBITMAP) SelectObject(hMemDC, hBitmap);
// Prepares gradient filled mask and applies to output DC
OffsetRect(&rectRgn, -rectRgn.left, -rectRgn.top);
GradientFill(hMemDC, rectRgn, color_array);
BitBlt(hdc, X, Y, Width, Height, hMemDC, 0, 0, SRCINVERT);
// Displays region in black in output DC
FillRgn(hdc, rgn, (HBRUSH) GetStockObject(BLACK_BRUSH));
// Applies mask to output DC again
BitBlt(hdc, X, Y, Width, Height, hMemDC, 0, 0, SRCINVERT);
// De-selects bitmap from memory DC
SelectObject(hMemDC, hOldBitmap);
// Deletes bitmap
DeleteObject(hBitmap);
}
// Deletes memory DC
DeleteDC(hMemDC);
}
}
Writing a text in graded colours is similar to displaying a region in graded colours except the fact that the intermediate step is to display the text in black colour. Arguments, apart from display DC and colour array, are the X and Y positions of the string and the string as 'LPSTR' type.
Code:
// Fills the text 'text' in graded colours at (x, y)
void GradientFill(HDC hdc, int x, int y, LPSTR text, LPCOLORREF color_array)
{
// Creates memory DC
HDC hMemDC = CreateCompatibleDC(hdc);
if (hMemDC) // Memory DC creation successful
{
// Gets size of the string
SIZE Size;
GetTextExtentPoint32(hdc, text, lstrlen(text), &Size);
// Creates bounding rectangle of the text
RECT rectText = {x, y, x + Size.cx, y + Size.cy};
// Creates bitmap for the mask
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, Size.cx, Size.cy);
if (hBitmap) // Bitmap created successfully
{
// Selects bitmap in memory DC
HBITMAP hOldBitmap = (HBITMAP) SelectObject(hMemDC, hBitmap);
// Prepares gradient filled mask and applies to output DC
OffsetRect(&rectText, -rectText.left, -rectText.top);
GradientFill(hMemDC, rectText, color_array);
BitBlt(hdc, x, y, Size.cx, Size.cy, hMemDC, 0, 0, SRCINVERT);
// Displays text in black colour in output DC
// Background mode is transparent
COLORREF TextColor = SetTextColor(hdc, RGB(0, 0, 0));
int BkMode = SetBkMode(hdc, TRANSPARENT);
TextOut(hdc, x, y, text, lstrlen(text));
SetBkMode(hdc, BkMode);
SetTextColor(hdc, TextColor);
// Applies mask to output DC again
BitBlt(hdc, x, y, Size.cx, Size.cy, hMemDC, 0, 0, SRCINVERT);
// De-selects bitmap from memory DC
SelectObject(hMemDC, hOldBitmap);
// Deletes bitmap
DeleteObject(hBitmap);
}
// Deletes memory DC
DeleteDC(hMemDC);
}
}
Code for interpolating the colours in between is given as a macro:
Code:
// Interpolates intermediate value at N
// X0 corresponds to N = 0 and X1 to N = 255
#define IPOL(X0, X1, N) ((X0) + ((X1) - (X0)) * N / 256)
Last edited by Andreas Masur; March 12th, 2006 at 06:13 AM.
* The Best Reasons to Target Windows 8
Learn some of the best reasons why you should seriously consider bringing your Android mobile development expertise to bear on the Windows 8 platform.