Splines, Pens and Brushes


The program of this section demonstrates the drawing of cubic splines. The output of the program is shown below.

Splines are cubic curves. Four points that lie on an upright cubic are sufficient to determine the cubic. However, more often, splines are specified using the two end points and two control points that determine the tangents at the endpoints.

An array of four points is declared as the data of the window procedure - as shown below.

struct window_data
{
    ipoint point_array[4];
};

These four points are the two endpoints (point_array[0] and point_array[3]) and the two intermediate control points (point_array[1] and point_array[2]). The function that draws the curves is draw_bezier - which is shown below.

void draw_bezier(handle device_context, point point_array[])
{
 draw_splines(device_context,point_array,4);

 move_to(device_context,point_array[0](0),point_array[0](1));
 draw_line_to(device_context,point_array[1](0),point_array[1](1));

 move_to(device_context,point_array[2](0),point_array[2](1));
 draw_line_to(device_context,point_array[3](0),point_array[3](1));
}

The first call (to draw_splines) draws the cubic in its entirety. The remaining four calls draw the tangents to the cubic. Much of the remaining portion of the code is used to specify and manipulate the control points.

When the window is scaled, the control points of the spline are reinitialized as shown below.

case message::size:
 {
  int width_of_client  = low_part(parameter2);
  int height_of_client = high_part(parameter2);

  point_array[0](0) = width_of_client/4;
  point_array[0](1) = height_of_client/2;

  point_array[1](0) = width_of_client/2;
  point_array[1](1) = height_of_client/4;

  point_array[2](0) =     width_of_client/2;
  point_array[2](1) = 3 * height_of_client/4;

  point_array[3](0) = 3 * width_of_client/4;
  point_array[3](1) =     height_of_client/2;
 }
break;

When the mouse is moved with either the left or right mouse button depressed, the control points are updated (the first control point corresponds to the left mouse button and the second control point corresponds to the right mouse button). If neither button is depressed, the mouse movement is ignored. The code for mouse movement is shown below.

case message::mouse_move:
 if (parameter1 & mouse_state::leftButton || parameter1 & mouse_state::rightButton)
  {
   handle device_context = get_device_context(window_handle);

   select_object(device_context,get_standard_object(standard_pen::White));
   DrawBezier(device_context,point_array);

   if (parameter1 & mouse_state::leftButton)
    {
     point_array[1](0) = low_part(parameter2);
     point_array[1](1) = high_part(parameter2);
    }

   if (parameter1 & mouse_state::rightButton)
    {
     point_array[2](0) = low_part(parameter2);
     point_array[2](1) = high_part(parameter2);
    }

   select_object(device_context,get_standard_object(standard_pen::Black));
   DrawBezier(device_context,point_array);
   release_device_context(window_handle,device_context);
  }
 break;

See message::mouse_move for a description of the flags applicable to parameter1. When the mouse is moved, the existing curve is redrawn with the brush set to white - to erase it. The new curve is then drawn in black. To view various splines, the reader should run the program and drag the mouse with either the left or right button depressed.

Pens

When line primitives are drawn, they are rendered to the device in the current pen. A pen may be selected through the function select_object. The function get_standard_object may be used to create one of the standard pens (black and white only). The function delete_object destroys pens of any type.

To create a pen other than a standard pen, the function create_pen may be used. As well as specifying the style to this function, the pen width and color are also specified. The width parameter applies to pen_style::solid, pen_style::blank and pen_style::inside_frame. A width of 0 indicates that the pen should be 1 pixel wide. Standard pens are 1 pixel wide. If a width greater than 1 is specified for any of the dotted and dashed styles, a solid pen is used instead. When a pen with style other than pen_style::inside_frame is selected into a device context, the color of the pen is converted to the nearest available pure color. Pens of style pen_style::inside_frame may use dithered colors when a width greater than 1 is specified.

Pens may also be created via the function create_pen_indirect. In this case, a pen is created using an instance of the class logical_pen. Whenever a pen is created through either of the two aforementioned functions, they are separate objects in their own right and are not directly related to a device context (until selected).

Background Mode and Background Color

When drawing with pens that contain dots and dashes, the coloring of the gaps depends upon the background mode and the background color. When background::Opaque is in effect, the operating system fills the spacings between dots and dashes with the current background color. The background mode may be set using the function set_background_mode; whereas, the background color may be set using set_background_color. When the background is background::transparent, the spacings are left unaltered.

Drawing Modes

When a pen is used, the operating system performs bitwise boolean operation between the pixels of the pen and the destination display surface. Such operations are commonly referred to as "binary raster operations". The enumeration mix gives symbolic names to each of the raster operations. The default mix mode is mix::copyPen, which implies that the pen color is copied without reference to the color of the destination pixel.

To simplify the discussion, let it be assumed that the display is monochrome. Then the following table documents the effect of the binary raster operations

pen (P) 1 1 0 0 operation Drawing mode
Destination (d) 1 0 1 0
Results0 0 0 0 0 mix::Black
0 0 0 1 ~(P | d) mix::NotMergePen
0 0 1 0 ~P & d mix::MaskNotPen
0 0 1 1 ~P mix::NotcopyPen
0 1 0 0 P & ~d mix::MaskPenNot
0 1 0 1 ~d mix::Not
0 1 1 0 P ^ d mix::ExclusiveOrPen
0 1 1 1 ~(P & d) mix::NotMaskPen
1 0 0 0 P & d mix::MaskPen
1 0 0 1 ~(P ^ d) mix::NotExclusiveOrPen
1 0 1 0 d mix::NoOperation
1 0 1 1 ~P | d mix::MergeNotPen
1 1 0 0 P mix::copyPen
1 1 0 1 P | ~d mix::MergePenNot
1 1 1 0 P | d mix::MergePen
1 1 1 1 1 mix::White

The value mix::NoOperation leaves the destination unchanged.

Filled Areas

There are six functions that draw filled areas - as shown in the table below.

draw_rectangle A standard rectangle (square corners).
draw_rounded_rectangle A rectangle with elliptically rounded corners.
draw_ellipse An ellipse.
draw_sector A pie wedge of an ellipse with endpoints connected by a chord.
draw_polygon A polygon.
draw_polygons Multiple polygons.

The outline of the figure is drawn with the currently selected pen. The current background mode, background color and drawing mode are used when drawing the outline of the figure. Figures are filled using the currently selected brush. The default brush is white and there is a number of other standard brushes available. The function get_standard_object may be used to create one of the standard brushes. The function select_object may be used to select a brush. If the outline of a figure is to be drawn without drawing the interior, standard_brush::null may be selected. If the interior is to be filled without drawing the boundary, standard_pen::null may be selected.

When drawing with the function draw_polygon, the fill mode is used to calculate the interior of the figure. The fill mode may be set using the function set_fill_mode.

Brushing the Interior

The interiors of the aforementioned primitives are filled with the current brush. A brush is an 8x8 pixel bitmap that is pattern repeated throughout the figure. There are several functions used to create different types of brushes, as shown in the table below.

get_standard_object Loads one of the standard brushes.
create_solid_brush creates a brush of a given color.
create_brush_indirect creates a logical brush.
create_pattern_brush creates a brush using a given bitmap.
create_hatch_brush creates a logical brush with the given color and hatch style.
create_device_independent_bitmap_pattern_brush creates a brush from a device independent bitmap held in global memory.
create_device_independent_bitmap_pattern_brush_pointer creates a brush from a device independent bitmap stored in memory.

Information about a brush may be obtained using the following call.

get_object(device_context,sizeof(logical_brush),(void*)&LogicalBrushGet);

A brush is selected using the function select_object. A brush may be relinquished using the function delete_object. A brush should not be deleted whilst it is the currently selected brush.