Blog

2011-04-13 16:56:47

Rendering an MS Visual Studio 2010-like Window Background with GDI

Here is a short description on how to draw a background similar to that found in MS Visual Studio 2010. it uses GDI ROP2 filters (not GDI+, which is nicer to use, but suffers with performance issues (I tried and couldn't make it do this as efficiently as GDI does)).


Fig. 1 - Visual Studio's Background

As can be seen here, the construction of the effect is quite simple - there is a stipple pattern super-imposed and repeated over a gradiented background.

The stipple pattern is 4x4 pixels in size and is non-varying - that is, the pixel values do not change based on where the pixels occur in the final image.

The gradiented background is composed of two symmetrical gradients from the top and bottom respectively of the client area to the center of the client area.

The background gradients need to be drawn first, and then the stipple pattern needs to be applied over it. The stipple pattern can be best implemented as a gdi bitmap brush and thus some start-up logic is required to build the necessary brushes.

CREATING THE BRUSHES

At start-up we need to create the gdi brushes needed to draw the stipple pattern over the background. this involves two brushes; the first for masking the future draw operation, and the second for actually drawing the pattern. Each brush will require its own bitmap as a source of its pixel data.

The first brush to create is the pattern mask brush. This brush is used as a sort of alpha-mask which is used to draw the stipple pattern. The bitmap that the pattern mask brush uses looks like this:

Fig. 2 - Mask Pattern Bitmap

Generating the pattern mask bitmap is quite straight-forward:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// returns a monocrhome (black/white) 32bbp bitmap which serves as an alpha mask
HBITMAP GeneratePatternMask()
{   // the size of the tile pattern
    Rect patternRect(0, 0, 4, 4);
    Bitmap patternMask(patternRect.Width, patternRect.Height, PixelFormat32bppARGB);   
    Graphics* gfx = Graphics::FromImage(&patternMask); 
    // colours used in drawing
    Colour black = "#000";
    Colour white = "#fff";
    Colour transparent = "#ffffff00";
    // fill the bitmap with white
    SolidBrush backgroundBrush(white);
    gfx->FillRectangle(&backgroundBrush, patternRect);
    // set the pixels to black where the stipple pattern is to appear
    patternMask.SetPixel(0, 0, black);
    patternMask.SetPixel(0, 1, black);
    patternMask.SetPixel(2, 2, black);
    patternMask.SetPixel(2, 3, black);
    // get a gdi hbitmap from gdi+
    HBITMAP patternMaskBrush;
    patternMask.GetHBITMAP(transparent, &patternMaskBrush);

    return patternMaskBrush;
}
I use GDI+ to create both the pattern mask and the stipple pattern bitmaps simply due to its ease of use and the fact that this (start-up) code is not time-sensitive. The stipple pattern brush uses the following bitmap:

Fig. 3 - Stipple Pattern Bitmap

The stipple pattern bitmap is generated in a very similar way to the pattern mask bitmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// returns a full-colour 32bpp bitmap which contains the stipple pixels
HBITMAP GenerateStipplePattern()
{   // the size of the tile pattern
    Rect patternRect(0, 0, 4, 4);
    Bitmap stipplePattern(patternRect.Width, patternRect.Height, PixelFormat32bppARGB);
    Graphics* gfx = Graphics::FromImage(&stipplePattern);  
    // colours used in drawing
    Colour black = "#000";
    Colour darkerBlue = "#293955";
    Colour lighterBlue = "#35496a";
    Colour transparent = "#ffffff00";
    // fill the bitmap with black
    SolidBrush backgroundBrush(black);
    gfx->FillRectangle(&backgroundBrush, patternRect);
    // draw the stipple pattern pixels
    stipplePattern.SetPixel(0, 0, darkerBlue);
    stipplePattern.SetPixel(0, 1, lighterBlue);
    stipplePattern.SetPixel(2, 2, darkerBlue);
    stipplePattern.SetPixel(2, 3, lighterBlue);
    // get a gdi hbitmap from gdi+
    HBITMAP stipplePatternBrush;
    stipplePattern.GetHBITMAP(transparent, &stipplePatternBrush);

    return stipplePatternBrush;
}

Creating the required brushes from these bitmaps is also a simple process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// returns a gdi brush containing the stipple mask bitmap
HBRUSH GeneratePatternMaskBrush()
{   // generate the pattern mask bitmap
    HBITMAP bgMaskPatternBitmap = GeneratePatternMask();
    // create the mask pattern brush from the monochrome mask bitmap
    HBRUSH maskPatternBrush = CreatePatternBrush(bgMaskPatternBitmap);
    // delete the pattern mask bitmap which is no longer required
    DeleteObject(bgMaskPatternBitmap);

    return maskPatternBrush;
}

// returns a gdi brush ontaining the stipple pattern bitmap
HBRUSH GenerateStipplePatternBrush()
{   // generate the pattern mask bitmap
    HBITMAP bgStipplePatternBitmap = GenerateStipplePattern();
    // create the mask pattern brush from the monochrome mask bitmap
    HBRUSH stipplePatternBrush = CreatePatternBrush(bgStipplePatternBitmap);
    // delete the pattern mask bitmap which is no longer required
    DeleteObject(bgStipplePatternBitmap);

    return stipplePatternBrush;
}

Once the brushes have been created we can continue and start drawing the background of the window.

DRAWING THE GRADIENT BACKGROUND

The gradient background is actually two gradients, each starting from the center of the window and extending up and down to the top and bottom of the client area. The gradients share start and end colours and are symmetrical, mirrored around the vertical center of the window client area.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// draw a vertical gradient over the specified rect
void DrawBackgroundGradient(HDC dc, RECT& clientRect)
{   // dark at the top and bottom, and lighter in the centre
    Colour darkerBlue = "#293955";
    Colour lighterBlue = "#35496a";
    // contains the colour for each corner of the gradient rect
    TRIVERTEX vertex[4];
    // top-left
    vertex[0].x     = 0;
    vertex[0].y     = 0;
    vertex[0].Red   = darkerBlue[0] << 8;
    vertex[0].Green = darkerBlue[1] << 8;
    vertex[0].Blue  = darkerBlue[2] << 8;
    vertex[0].Alpha = darkerBlue[3] << 8;
    // center-right
    vertex[1].x     = clientRect.right;
    vertex[1].y     = clientRect.bottom / 2;
    vertex[1].Red   = lighterBlue[0] << 8;
    vertex[1].Green = lighterBlue[1] << 8;
    vertex[1].Blue  = lighterBlue[2] << 8;
    vertex[1].Alpha = lighterBlue[3] << 8;
    // center-left
    vertex[2].x     = 0;
    vertex[2].y     = clientRect.bottom / 2;
    vertex[2].Red   = lighterBlue[0] << 8;
    vertex[2].Green = lighterBlue[1] << 8;
    vertex[2].Blue  = lighterBlue[2] << 8;
    vertex[2].Alpha = lighterBlue[3] << 8;
    // bottom-right
    vertex[3].x     = clientRect.right;
    vertex[3].y     = clientRect.bottom;
    vertex[3].Red   = darkerBlue[0] << 8;
    vertex[3].Green = darkerBlue[1] << 8;
    vertex[3].Blue  = darkerBlue[2] << 8;
    vertex[3].Alpha = darkerBlue[3] << 8;
    // rectangle struct references vertices
    GRADIENT_RECT gRect[2];
    gRect[0].UpperLeft  = 0;
    gRect[0].LowerRight = 1;
    gRect[1].UpperLeft  = 2;
    gRect[1].LowerRight = 3;
    // paint the gradient vertically with a triangle-strip
    GradientFill(dc, vertex, 4, gRect, 2, GRADIENT_FILL_RECT_V);
}

The gradients are drawn with a single call to GradientFill, using triangle strips as a source of vertex data.

Drawing the Stipple Pattern

First, the background mode of the DC (GDI Device Context) is set to transparent - this means the background is not cleared/changed before drawing to it. A null pen is then selected into the DC.

The current drawing mode of the DC is then set to combine the colour being drawn with the existing colour of the target pixel using bit-wise AND (R2_MASKPEN).

The mask pattern brush is then selected into the DC. This brush is a 4x4 32bpp bitmap, with the pixels that mark where the stipple pattern appears being black; and the rest (where the gradient background will show through) being white.

The mask pattern brush is then tiled over the entire client area. This turns the pixels where the stipple pattern will be to black, but leaves the rest of the gradient background intact. The null pen then selected back into the DC.

The drawing mode is then changed (to R2_MERGEPEN) so that the colour being drawn is combined with the existing colour using bit-wise OR.


Fig. 4 - Stipple Pattern Application Process

The stipple pattern brush is then selected into the DC. This brush is a 4x4 32bpp bitmap, with the background being white but having zero opacity; and the pixels where the stipple is to be drawn being the final colour values.

The stipple pattern brush is then tiled over the entire client area in alignment with the previously applied mask pattern so that the pixels of the stipple pattern fall exactly onto pixels that have been coloured black through the application of the mask pattern.

The bit-wise OR combiner results in the background pixels retaining their colour values, but the stipple pixels being coloured with the values in the stipple bitmap (as they are being OR'd with 0 (black)).

End Result

This is what it should look like. There is a link to a MSVS2010 project containing the source-code to this 'tutorial' below.


Fig. 5 - Final Result

Thank you for visiting! :)

msvs2010-background.zip

Size: 26.54 KB, Date: 2011-12-03 21:01, MD5: b50fd694fb81f2655bbf632552824d38