Managed Extensions - Using GDI+ to Draw 3D Text

In a previous article - Managed Extensions - Using GDI+ Brushes to Draw Text - I presented step-by-step instructions as well as a demo application illustrating how to draw 2D text using GDI+ objects. This week, I'll take that a step a further and show you how to render 3D text in order to accomlish the following effects:

The shadowed, blocked, embossed or engraved text-effects are typically realized by drawing the text multiple times starting with the text furthest in the background (i.e., the shadow) and proceeding until the top-most, or foreground, text is drawn last. In other words, drawing 3D text is basically just drawing 2D text multiple times. Therefore, I won't describe each step of setting up the various GDI+ objects as I've already done that in the article , Managed Extensions - Using GDI+ Brushes to Draw Text. Instead, I'll focus on the code necessary to accomplish each of the aforementioned 3D effects.

Note: In order to test these code snippets, first simply drag a PictureBox from the Toolbox onto a Form in a Managed Extensions Windows Forms application and name the PictureBox variable picText. Then, copy and paste the desired snippet into your application to have the text rendered onto the PictureBox

Steps to Rendering Shadowed Text

To achieve a shadowed appearance, you simply draw the shadowed text twice: first at the desired depth first and then the foreground text. For example, the following code will draw a sample text on a window (represented by the Graphics object, g) where the text has a shadow version of itself drawn 5 pixels in the background.

// Assumes a PictureBox on the form named picText
// with this code being the picText object's 
// Paint method

private: 
System::Void picText_Paint(
  System::Object *  sender, 
  System::Windows::Forms::PaintEventArgs *  e)
{
  // Test string
  String* textToDisplay = S"Test string";

  // Obtain Graphics object
  Graphics* g = e->Graphics;

  // Create a Font object, Times New Roman, 25pt
  System::Drawing::Font* font = 
    new System::Drawing::Font("Times New Roman", 
                              Convert::ToSingle(25),
                              FontStyle::Regular);

  // Obtain the size of the text to be rendered
  SizeF textSize = g->MeasureString(textToDisplay, font);

  // Text will be centered on PictureBox control
  Single x = (picText->Width - textSize.Width) / 2;
  Single y = (picText->Height - textSize.Height) / 2;

  // Clear background
  g->Clear(Color::White);
  
  // Draw the shadow text
  g->DrawString(textToDisplay, 
                font,
                SystemBrushes::ControlLight,
                x + 5, y + 5);

  // Draw the foreground text
  g->DrawString(textToDisplay, font, SystemBrushes::ControlText, x, y);
}

Steps to Rendering Blocked Text

To get the blocked-text effect, the text is be repeatedly drawn starting at the desired depth and moving one pixel at a time up to where the foreground text is drawn. Obviously, you have to decide in which direction this repeated drawing occurs. I personally draw blocked text with the light source from the upper-right. This means using a for loop and subtracting the offset depth from the X dimension. To move the light-source to the upper-left, simply increment the offset.

// Assumes a PictureBox on the form named picText
// with this code being the picText object's 
// Paint method

private: 
System::Void picText_Paint(
  System::Object *  sender, 
  System::Windows::Forms::PaintEventArgs *  e)
{
  // Test string
  String* textToDisplay = S"Test string";

  // Get drawing surface for PictureBox and clear background
  Graphics* g = e->Graphics;

  // Create a Font object
  System::Drawing::Font* font = new System::Drawing::Font("Times New Roman", Convert::ToSingle(25), FontStyle::Regular);

  // Obtain the size of the text to be rendered
  SizeF textSize = g->MeasureString(textToDisplay, font);

  // Text will be centered on Picture Box control
  Single x = (picText->Width - textSize.Width) / 2;
  Single y = (picText->Height - textSize.Height) / 2;

  // Clear background
  g->Clear(Color::White);
  
  // Print the background text multiple times starting 
  // at the furthest point in the background up to
  // the foreground text
  for (int i = Convert::ToInt32(5); i >= 0; i--)
  {
    g->DrawString(textToDisplay, 
                  font,
                  SystemBrushes::ControlLight,
                  x - i, y + i);
  }

  // Draw the foreground text
  g->DrawString(textToDisplay, font, SystemBrushes::ControlText, x, y);
  
}

Steps to Rendering Embossed and Engraved Text

I'll explain both of these in the same section because engraved-text is simply the inverse of embossed text. The emossed-text effect is usually accomplished by simply using the shaddow-text technique with the depth set 1 pixel and the foreground text colour set to the same colour as the backround on which the text is being rendered. The shadow text is then some darker colour - such as grey or black. The engraved effect is done in reverse where the offset for the shadow colour is one pixel up and left from the foreground text.

 

// Assumes a PictureBox on the form named picText
// with this code being the picText object's 
// Paint method

private: 
System::Void picText_Paint(
  System::Object *  sender, 
  System::Windows::Forms::PaintEventArgs *  e)
{
  // Test string
  String* textToDisplay = S"Test string";

  // Get drawing surface for PictureBox and clear background
  Graphics* g = e->Graphics;

  // Create a Font object
  System::Drawing::Font* font = new System::Drawing::Font("Times New Roman", Convert::ToSingle(25), FontStyle::Regular);

  // Obtain the size of the text to be rendered
  SizeF textSize = g->MeasureString(textToDisplay, font);

  // Text will be centered on Picture Box control
  Single x = (picText->Width - textSize.Width) / 2;
  Single y = (picText->Height - textSize.Height) / 2;

  // Clear background
  g->Clear(Color::White);
  
  // Change the isEmossed value to switch
  // between embossed and engraved
  bool isEmbossed = false;
  g->DrawString(textToDisplay, 
                   font, 
                   SystemBrushes::ControlText,
                   x + Convert::ToSingle( (isEmbossed? 1 : -1)),
                   y + Convert::ToSingle( (isEmbossed ? 1 : -1)));

  // Draw the foreground text
  g->DrawString(textToDisplay, font, new SolidBrush(Color::White), x, y);
  
}

Sample Application

I've attached a demo application with this article that will allow you to play around with the various effects you can accomplish with GDI+ and 3D text. The following image is a screen capture of that demo application