Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Implementing Flicker-Free Motion


SEP95: Implementing Flicker-Free Motion

Paul, a computer-science student at Lehigh University, has consulted for Gimpel Software on the PC-Lint for C/C++ project. He is currently developing a multimedia graphics adventure game in a new programming language called "AGPL." Paul can be contacted on CompuServe at 73340,3456.


Moving an object across a screen convincingly requires smooth motion without flicker. To move a large object in a high-resolution mode, a great deal of computation and a large amount of memory are necessary. In this article, I'll present a program that implements flicker-free mouse motion that operates efficiently in resolutions of up to 800x600x24-bit color. Given any bitmap in a standard file format, the program will turn that bitmap into a mouse cursor that moves fluidly across the screen.

To implement my algorithm, you need a high-powered development environment. I've chosen the combination of Genus Microprogramming's graphics libraries and the Phar Lap 286|DOS-Extender under Borland C++. This combination of tools allows large bitmaps to be stored in upper memory and displayed with blazing speed on almost any video card.

The Algorithm

It is tempting to implement motion in the following manner:

  1. Save the portion of screen background where the mouse cursor will be placed.
  2. Paint the mouse cursor.
  3. On motion, redraw the screen background saved in Step 1, then go back to Step 1.
This method works reasonably well when the mouse is moved a great distance, because then the current and previous cursor locations will not intersect. Since the two regions are disjointed, there is only one screen-write operation to any one pixel of the screen. This results in a motion without flicker.

Unfortunately, large motions are not the general case with the mouse pointing device. Typically, motions are in very small increments, and the new cursor intersects the old. In the intersection region, there are two screen-write operations: The first redraws the background, and the second draws the portion of the cursor in that window. This causes a great deal of flicker when the mouse is moved slowly across the screen. The flicker-free algorithm I present here addresses this problem. The algorithm performs the following:

  1. Saves a portion of screen background (where the mouse cursor will be placed) into a small buffer.
  2. Paints the mouse cursor if it is not already at the proper location.
  3. On motion, calculates the minimum window that contains both previous and current cursor positions and allocates a buffer of that size.
  4. Repaints the saved screen background at the proper location within the large buffer.
  5. Saves the portion of the large buffer where the cursor will be placed into the small buffer allocated in Step 1.
  6. Transparently places the cursor into the large buffer.
  7. Paints the large buffer to the screen.
  8. Deallocates the large buffer and returns to Step 3.

Implementation

The graphics routines that implement this algorithm are provided by a suite of tools from Genus Microprogramming--GX Kernel, GX Graphics, GX Effects, and GX Images 286 (protected-mode version).

GX Kernel provides low-level graphics routines that include hardware support for more than 50 video cards. The GX Kernel transparently supports video modes up to 1024x768x16.7 million colors. You simply select the VESA equivalent of a resolution, and the GX Kernel tests whether a compatible hardware mode is available. If not, the VESA will be used only as a last resort.

The chief feature of the GX Kernel is its support for virtual off-screen buffers. These buffers are key in implementing flicker-free motion because the basic premise is to paint the screen only once per motion. This requires that off-screen buffers of sufficient size be allocated. Table 1 lists the functions from GX Kernel that I use in my program. The include file (Listing One) and the source file (Listing Two) provide implementation details of these functions. (The complete source code, makefile script, linker response file, and configuration options are available electronically; see "Availability," page 3.) To handle events, I use the GX Graphics toolkit, which provides its own interrupt-service routine that captures events such as mouse movements, button presses, button releases, and keyboard presses. GX Graphics also provides you with several crude internal mouse pointers--a simple arrow, a plus sign, and an hourglass. Important functions from the GX Graphics package are listed in Table2.

Bitmaps are loaded from standard file format to virtual buffer using the GX Images package, which supports GIF, PCX, BMP, JPEG, TIFF, and additional file formats; see Table 3 for a list of functions used.

The final Genus package needed is GX Effects, which provides excellent routines for special display effects such as slide and weave, animation via sprites, and support for FLIC files. This package allows for transparently placing a bitmap into a virtual buffer; the necessary routines are listed in Table 4. The GX Effects package also provides some very high-level functions for animation. However, I don't use these functions because they would not properly show how the flicker-free algorithm is implemented.

All of the Genus packages operate in 286 protected mode, which requires a 286 DOS extender. My program supports Borland's Pascal DOS extender, Blinkinc's Blinker, Tenberry Software's (formerly Rational Systems) 16M, Borland Power Pack, and the Phar Lap 286|DOS-Extender, which I selected because of its transparency to the programmer and its robust API.

The Phar Lap 286|DOS-Extender is remarkably simple to use in an application. Just linking in a new library module allows for programs to allocate up to 16 MB of dynamic memory by using familiar malloc() and new() calls. The DOS extender also includes a full-featured API, called "PHAPI," which lets you write protected-mode interrupts and capture protected-mode exceptions. Table 5 lists some of the DOS-extender functions I use.

Conclusion

The Genus packages are excellent tools for incorporating graphics into almost any application. They afford seamless integration of animation, event handling, drawing, and image incorporation. Genus also has a collection of graphics packages for Windows. Unfortunately, these Windows toolkits do not provide the same functionality as the DOS toolkits. This makes porting a Genus DOS application to Windows somewhat difficult.

For More Information

GX Kernel, GX Images, GX Effects, GX Graphics

Genus Microprogramming

115 Dairy Ashford, Suite 200

Houston, TX 77079-3012

800-227-0918

286|DOS-Extender SDK

Phar Lap Software

60 Aberdeen Avenue

Cambridge, MA 02138

617-661-1510

Table 1: GX Kernel functions.

Function                Description

<I>gxVirtualFree</I>          Checks if enough memory is available for allocation.
<I>gxCreateVirtual</I>        Allocates a virtual buffer.
<I>gxDestroyVirtual</I>       Deallocates a virtual buffer.
<I>gxVirtualDisplay</I>       Displays a virtual buffer on the screen.
<I>gxVirtualVirtual</I>       Copies one section of a virtual buffer to another.
Table 2: GX Graphics functions.
Function            Description

<I>grSetEventMask</I>     Turns on event handler and sets which events to detect.
<I>grGetEvent</I>         Gets the top event on the queue.
<I>grSetMouseStyle</I>    Sets an internal mouse-pointer style.
<I>gxDisplayMouse</I>     Displays or hides the mouse.
Table 3: GX Images functions.
Function            Description

<I>imgFileGetHeader</I>   Returns information about the file type and size.
<I>imgFileConvert</I>     Loads file and converts to the format of the virtual buffer.
Table 4: GX Effects routines.
Function            Description 

<I>fxCreateImage</I>      Creates an image that can be made transparent.
<I>fxSetKeyColor</I>      Selects key color to make transparent (default is black).
<I>fxImageVirtual</I>     Transparently places an image into a virtual buffer.
Table 5: Functions in the Phar Lap 286|DOS-Extender.
Function                 Description

<I>DosSetExceptionHandler</I>  Sets up a protected-mode exception handler.
<I>DosSetRealProtVec</I>       Installs both a real- and protected-mode interrupt.
<I>DosSetPassToProtVec</I>     Installs a protected-mode interrupt.

Listing One

// GRAPH.H (MOUSE.EXE)
// (C) 1994 Ahpah Software Inc.
// Description: Interface to GENUS toolkits.
#include <c:\genus\include\gxlib.h>
#include <c:\genus\include\fxlib.h>
#include <c:\genus\beta\include\imglib.h>
#include <c:\genus\include\grlib.h>
/* maximums macros */
#define GR_PALSIZE      768  // 256 color load palette
#define GR_XMAX         640  // screen x resolution
#define GR_YMAX         480  // screen y resolution
#define GR_CURSXMAX     150  // maximum x length for a cursor
#define GR_CURSYMAX     150  // maximum y length for a cursor
#define GR_VECT           4  // number of elements in a vector
#define GR_INTERNAL       0  // cursor is internal
#define GR_BITMAP         1  // cursor is a bitmap
#define GR_REMOVE         0  // remove cursor
#define GR_PLACE          1  // place cursor
/* useful macros */
#define X1 0
#define Y1 1
#define X2 2
#define Y2 3
#define RECT(v)    v[0], v[1], v[2], v[3]
#define UPLEFT(v)  v[0], v[1]
#define LORIGHT(v) v[2], v[3]
#define XWID(v)    (v[2] - v[0] + 1)
#define YWID(v)    (v[3] - v[1] + 1)
/* image save structure */
STRUCT GR_IMAGE {
    GXHEADER gxheader;
    WORD xmax, ymax;
};
/* effects image structure */
STRUCT GR_FXIMAGE {
    FXIMAGE fxheader;
    WORD xmax, ymax;
};
/* cursor structure */
STRUCT GR_CURSOR {
    WORD type;
    SIGNINT style;
    LONG color;
    GR_FXIMAGE *fximage;
    WORD location[GR_VECT];
    GXHEADER savearea;
};
/* prototypes */
VOID      gr_cursormove( WORD, WORD );
VOID      gr_cursorset( SIGNINT, LONG );
VOID      gr_cursorset( GR_FXIMAGE * );
VOID      gr_cursorstatus( WORD );
VOID      gr_ender( VOID );
VOID      gr_error( STRING );
VOID      gr_error( STRING, SIGNINT );
VOID      gr_fxcreate( GR_IMAGE *, GR_FXIMAGE * );
VOID      gr_fxfree( GR_FXIMAGE *, BOOLEAN = NO );
VOID      gr_fxshow( GR_FXIMAGE *, WORD, WORD );
VOID      gr_getevent( GREVENT * );
VOID      gr_imagefree( GR_IMAGE *, BOOLEAN = NO );
VOID      gr_imageload( GR_IMAGE *, STRING );
VOID      gr_imageshow( GR_IMAGE *, WORD * );
VOID      gr_mousebounds( WORD, WORD, WORD, WORD );
BOOLEAN   gr_mousein( GREVENT *, WORD, WORD, WORD, WORD );
VOID      gr_starter( VOID );
VOID      gr_vectorcopy( WORD *, WORD *, INDEX );
/* function like macros */
#define GR_CURSOROFF() CALL grDisplayMouse( grHIDE ); \
               gr_cursorstatus( GR_REMOVE )
#define GR_CURSORON()  gr_cursorstatus( GR_PLACE );\
               CALL grDisplayMouse( grSHOW )

Listing Two

// GRAPH.CPP (MOUSE.EXE)
// (C) 1994 Ahpah Software Inc.
// Description: Interface to GENUS toolkits.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "defs.h"
#include "graph.h"
#include "system.h"
/* variables */
SIGNINT   gr_atype;      // memory allocation type
WORD      gr_mode;       // initialized graphics mode
GR_CURSOR gr_cursor;     // cursor information block
BOOLEAN   gr_alloc;      // allocation have occured
TEXT      gr_pal[GR_PALSIZE];  // palette for loading 256 color images
/* local functions */
STATIC BOOLEAN   gr_trymode( WORD, BOOLEAN );
/* gr_dogenus() reports an error on a toolkit call. (inline) */
INLINE VOID gr_dogenus( SIGNINT retval, STRING str )
    {
    if( retval != gxSUCCESS )
    gr_error( str, retval );
    }
/* gr_max() returns the maximum of two WORDS */
INLINE WORD gr_max( WORD a, WORD b )
    {
    if( a < b )
    return b;
    return a;
    }
/* gr_min() returns the maximum of two WORDS */
INLINE WORD gr_min( WORD a, WORD b )
    {
    if( a > b )
    return b;
    return a;
    }
/* gr_cursormove() will move the cursor to its new location */
VOID gr_cursormove( WORD x, WORD y )
    {
    WORD temploc[GR_VECT], newloc[GR_VECT] = { 0, 0, 0, 0 };
    WORD *curloc = gr_cursor.location;
    WORD xlen, ylen;
    GXHEADER temp;
    if( gr_cursor.type != GR_BITMAP )
    return;
    xlen = gr_cursor.fximage->xmax;
    ylen = gr_cursor.fximage->ymax;
    if( xlen / 2 < x )
    newloc[X1] = x - xlen / 2;
    if( ylen / 2 < y )
    newloc[Y1] = y - ylen / 2;
    newloc[X2] = newloc[X1] + xlen;
    newloc[Y2] = newloc[Y1] + ylen;
    temploc[X1] = gr_min( curloc[X1], newloc[X1] );
    temploc[Y1] = gr_min( curloc[Y1], newloc[Y1] );
    temploc[X2] = gr_max( curloc[X2], newloc[X2] );
    temploc[Y2] = gr_max( curloc[Y2], newloc[Y2] );
    if( gxCreateVirtual( gr_atype, &temp, gr_mode, XWID( temploc ), 
                                              YWID( temploc ) ) != gxSUCCESS )
    gr_error( (STRING) "Could not create buffer." );
    CALL gxDisplayVirtual( RECT( temploc ), 0, &temp, 0, 0);
    CALL gxVirtualVirtual( &gr_cursor.savearea, 0, 0, xlen, ylen, &temp,
    curloc[X1] - temploc[X1], curloc[Y1] - temploc[Y1], gxSET );
    CALL gxVirtualVirtual( &temp, newloc[X1] - temploc[X1],
    newloc[Y1] - temploc[Y1], newloc[X1] - temploc[X1] + xlen,
    newloc[Y1] - temploc[Y1] + ylen, &gr_cursor.savearea, 0, 0, gxSET );
    CALL fxImageVirtual( &gr_cursor.fximage->fxheader, &temp,
    newloc[X1] - temploc[X1], newloc[Y1] - temploc[Y1] );
    CALL gxVirtualDisplay( &temp, 0, 0, RECT( temploc ), 0 );
    CALL gxDestroyVirtual( &temp );
    gr_vectorcopy( gr_cursor.location, newloc, GR_VECT );
    }
/* gr_cursorset() will set an internal cursor */
VOID gr_cursorset( SIGNINT style, LONG color )
    {
    gr_cursorstatus( GR_REMOVE );
    if( gr_cursor.type == GR_BITMAP )
    {
    gr_cursorstatus( GR_REMOVE );
    CALL grDisplayMouse( grSHOW );
    }
    gr_cursor.type = GR_INTERNAL;
    gr_cursor.style = style;
    gr_cursor.color = color;
    CALL grSetMouseStyle( style, color );
    gr_cursorstatus( GR_PLACE );
    }
/* gr_cursorset() will see a user bitmap cursor */
VOID gr_cursorset( GR_FXIMAGE *fxi )
    {
    if( gr_cursor.type == GR_BITMAP && fxi == gr_cursor.fximage )
    return;
    if( fxi->xmax > GR_CURSXMAX || fxi->ymax > GR_CURSYMAX )
    gr_error( (STRING) "Mouse cursor too large." );
    gr_cursorstatus( GR_REMOVE );
    if( gr_cursor.type == GR_INTERNAL )
    {
    CALL grDisplayMouse( grHIDE );
    gr_cursorstatus( GR_PLACE );
    }
    gr_cursor.type = GR_BITMAP;
    gr_cursor.fximage = fxi;
    gr_cursorstatus( GR_PLACE );
    }
/* gr_cursorstatus() will set the cursor on or off and update screen */
VOID gr_cursorstatus( WORD stat )
    {
    STATIC SIGNINT counter = 0;
    WORD *curloc = gr_cursor.location, xlen, ylen;
    SHORT x, y;
    switch( stat )
    {
    case GR_PLACE:
        counter++;
        if( gr_cursor.type != GR_BITMAP || counter != 1 )
        return;
        CALL grGetMousePos( &x, &y );
        xlen = gr_cursor.fximage->xmax;
        ylen = gr_cursor.fximage->ymax;
        curloc[X1] = 0;
        curloc[Y1] = 0;
        if( xlen / 2 < (WORD) x )
        curloc[X1] = (WORD) x - xlen / 2;
        if( ylen / 2 < (WORD) y )
        curloc[Y1] = (WORD) y - ylen / 2;
        curloc[X2] = curloc[X1] + xlen;
        curloc[Y2] = curloc[Y1] + ylen;
        CALL gxDisplayVirtual( RECT( curloc ), 0, 
                                                   &gr_cursor.savearea, 0, 0);
        gr_fxshow( gr_cursor.fximage, UPLEFT( curloc ) );
        break;
    case GR_REMOVE:
        counter--;
        if( gr_cursor.type != GR_BITMAP || counter != 0 )
        return;
        CALL gxVirtualDisplay( &gr_cursor.savearea, 0, 0, 
                                             RECT( gr_cursor.location ), 0 );
        break;
    default:
        break;
    }
    }
/* gr_ender() will close out video related functions */
VOID gr_ender()
    {
    if( gr_alloc )
    CALL gxDestroyVirtual( &gr_cursor.savearea );
    CALL gxClearDisplay( 0L, 0 );
    CALL gxSetMode( gxTEXT );
    CALL grStopMouse();
    CALL grSetEventMask( grENOEVENTS );
    CALL gxDone();
    }
/* gr_error() will display a fatal error */
VOID gr_error( STRING s )
    {
    gr_ender();
    sys_ender();
    printf( "Error: %s\n\n", s );
    exit( 1 );
    }
/* gr_error() will display a fatal error with GENUS exit code */
VOID gr_error( STRING s, SIGNINT n )
    {
    gr_ender();
    sys_ender();
    printf( "Error: %s (code %d)\n\n", s, n );
    exit( 1 );
    }
/* gr_fxcreate() will create an effects image from a GR_IMAGE */
VOID gr_fxcreate( GR_IMAGE *grimage, GR_FXIMAGE *fxi )
    {
    CALL fxSetKeyColor( 0L );
    if( fxCreateImage( &fxi->fxheader, &grimage->gxheader, 
                                                     gr_atype ) != gxSUCCESS )
    gr_error( (STRING) "Could not create image." );
    fxi->xmax = grimage->xmax;
    fxi->ymax = grimage->ymax;
    }
/* gr_fxfree() will free an effects image */
VOID gr_fxfree( GR_FXIMAGE *fxi, BOOLEAN freeit )
    {
    CALL fxDestroyImage( &fxi->fxheader );
    if( freeit )
    delete fxi;
    }
/* gr_fxshow() will show an effects image */
VOID gr_fxshow( GR_FXIMAGE *fxi, WORD x, WORD y )
    {
    CALL fxImageDisplay( &fxi->fxheader, x, y, 0 );
    }
/* gr_getevent() will wait for an event */
VOID gr_getevent( GREVENT *event )
    {
    CALL grSetEventMask( (SIGNINT) (grELPRESS|grERPRESS|grEMOUSEMOVE|
                                                               grEKEYBOARD) );
    while( grGetEvent( event ) != gxSUCCESS )
    ;
    CALL grSetEventMask( grENOEVENTS );
    gr_cursormove( (WORD) event->curx, (WORD) event->cury );
    }
/* gr_imagefree() will free a loaded image */
VOID gr_imagefree( GR_IMAGE *image, BOOLEAN freeit  )
    {
    CALL gxDestroyVirtual( &image->gxheader );
    if( freeit )
    delete image;
    }
/* gr_imageload() will load any supported image type */
VOID gr_imageload( GR_IMAGE *image, STRING fn )
    {
    IMGINHDR imghead;
    if( imgFileGetHeader( fn, 0, &imghead, gr_pal ) != gxSUCCESS )
    gr_error( (STRING) "Could not load image header." );
    image->xmax = imghead.Width;
    image->ymax = imghead.Height;
    if( gxCreateVirtual( gr_atype, &image->gxheader, gr_mode, image->xmax, 
                                                  image->ymax ) != gxSUCCESS )
    gr_error( (STRING) "Could not create buffer." );
    if( imgFileConvert( fn, 0, &image->gxheader ) != gxSUCCESS )
    gr_error( (STRING) "Could not load image data." );
    }
#if 0
/* gr_imagemake() will make a virtual buffer in GR_IMAGE form */
GR_IMAGE *gr_imagemake( WORD xlen, WORD ylen )
    {
    GR_IMAGE *image = new GR_IMAGE;
    if( gxCreateVirtual( gr_atype, &image->gxheader, gr_mode, xlen, 
                                                          ylen ) != gxSUCCESS )
    gr_error( (STRING) "Could not allocate buffer." );
    image->xmax = xlen;
    image->ymax = ylen;
    return image;
    }
#endif
/* gr_imageshow() will display an image */
VOID gr_imageshow( GR_IMAGE *image, WORD *vect )
    {
    GXHEADER newgx, *mhead;
    WORD xlen, ylen, x, y;
    xlen = vect[X2] - vect[X1] + 1;
    ylen = vect[Y2] - vect[Y1] + 1;
    x = vect[X1];
    y = vect[Y1];
    mhead = &image->gxheader;
    if( abs( xlen - image->xmax ) > 2 || abs( ylen - image->ymax ) > 2 )
    {
    if( gxCreateVirtual( gr_atype, &newgx, gr_mode, xlen, ylen )
                                                                != gxSUCCESS )
        gr_error( (STRING) "Could not create buffer." );
    CALL gxVirtualScale( &image->gxheader, &newgx );
    mhead = &newgx;
    }
    else
    {
    xlen = image->xmax;
    ylen = image->ymax;
    }
    GR_CURSOROFF();
    CALL gxVirtualDisplay( mhead, 0, 0, x, y, x + xlen - 1, y + ylen - 1, 0 );
    GR_CURSORON();
    if( mhead == &newgx )
    CALL gxDestroyVirtual( &newgx );
    }
/* gr_mousebounds() will set the mouse boundaries */
VOID gr_mousebounds( WORD x1, WORD y1, WORD x2, WORD y2 )
    {
    CALL grSetMouseBounds( x1, y1, x2, y2 );
    }
/* gr_mousein() will check to see if the mouse is in a certain region */
BOOLEAN gr_mousein( GREVENT *event, WORD x1, WORD y1, WORD x2, WORD y2 )
    {
    if( x1 <= (WORD) event->curx && (WORD) event->curx <= x2 &&
    y1 <= (WORD) event->cury && (WORD) event->cury <= y2 )
    return YES;
    return NO;
    }
/* gr_starter() will initialize graphics routines. */
VOID gr_starter()
    {
    gr_dogenus( gxInit(), "Could not intialize GX Kernel." );
    gr_atype = gxCMM;
    if( !gr_trymode( gxVESA_112, NO ) )
    if( !gr_trymode( gxVESA_111, NO ) )
        if( !gr_trymode( gxVESA_110, NO ) )
        CALL gr_trymode( gxVESA_101, YES );
    if( gr_mode <= gxVESA_103 )
    {
    CALL gxGetConvertPalette( gxPAL5, gr_pal );
    CALL gxSetDisplayPalette( gr_pal );
    CALL gxSetDitherMatrix( gxNODITHER );
    }
    if( grInitMouse() != gxSUCCESS )
    {
    CALL gxSetMode( gxTEXT );
    gr_error( (STRING) "No mouse found." );
    }
    CALL grSetMouseStyle( grCARROW, gxRGBPacked( 255, 255, 255 ) );
    CALL grDisplayMouse( grSHOW );
    CALL grTrackMouse( grTRACK );
    gr_mousebounds( 0, 0, GR_XMAX - 1, GR_YMAX - 1 );
    if( gxCreateVirtual( gr_atype, &gr_cursor.savearea, gr_mode, GR_CURSXMAX, 
                                                   GR_CURSYMAX ) != gxSUCCESS )
    gr_error( (STRING) "Could not create buffer." );
    gr_alloc = YES;
    }
/* gr_trymode() will attempt to initialize a video mode */
BOOLEAN gr_trymode( WORD mode, BOOLEAN doabort )
    {
    if( doabort )
    {
    gr_dogenus( gxSetDisplay( mode ), "Graphics mode not supported." );
    gr_dogenus( gxSetMode( gxGRAPHICS ), "Could not switch 
                                                          to graphics mode." );
    gr_mode = mode;
    return YES;
    }
    else
    {
    if( gxSetDisplay( mode ) == gxSUCCESS )
        if( gxSetMode( gxGRAPHICS ) == gxSUCCESS )
        {
        gr_mode = mode;
        return YES;
        }
    }
    return NO;
    }
/* gr_vectorcopy() will copy one vector onto another */
VOID gr_vectorcopy( WORD *dest, WORD *src, WORD len )
    {
    memcpy( dest, src, len * sizeof( WORD ) );
    }


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.