Managed C++: Use GDI+ to Render Reflected Text

I've always had a fascination with graphics applications that allow you to twist and manipulate text and images—not so much from the end-user perspective, but more from the angle of a programmer who's always seen this type of programming as near-guru level. To that end, my previous few articles have focused on the GDI+ engine, which essentially makes many tasks such as rendering 3D text more accessible to those of us who don't have either the time or the inclination to become gurus in the world of graphics programming.

This article brings to a close this mini-series on text-manipulation via GDI+ by illustrating how to use the TranslateTransform and ScaleTransform methods of the Graphics class to draw reflected text.

Steps for Drawing Reflected Text

First, implement a handler for the PictureBox control's Paint method. Whenever you need to display new text, simply call the PictureBox control's Invalidate method, which in turn calls its paint method. The following steps and code refer to work within the paint method.

Note: The first few steps are the same as those in previous articles, as they pertain to tasks that are common to any text drawing. These tasks include obtaining a presentation space to draw on, instantiating a font object, measuring the text to display, and so on. I describe these steps here again because I don't want assume that the reader has read all of the articles in this series. If you have read those articles, simply skip to Step #5, where the commonality between most of the examples ends.
  1. Obtain the Graphics object for the picture control.
    You could acquire this object via the PictureBox object's CreateGraphics method. However, that Graphics object would be volatile in that it would be collected by the GC (Garbage Collector) when the object goes out of scope. Therefore, you need to use the Graphics object that is passed to the Paint method (via the PaintEventArgs::Graphics member):
    Graphics* g = e->Graphics;
  2. Instantiate the Font object based on the user-supplied font size.
    This application uses hard-coded values for the font typeface (Times New Roman) as well as the font size (40) and style (regular):
    System::Drawing::Font* font = 
      new System::Drawing::Font("Times New Roman", 40,
                                FontStyle::Regular);
    
  3. Obtain the size of the text to be rendered.
    As discussed in a previous article, the Graphics::MeasureString method is a convenient method for measuring a string, given the presentation space in which you will draw it and the font that you will use:
    SizeF textSize = g->MeasureString(textToDisplay, font);
  4. Clear the PictureBox Box control.
    Initialize the PictureBox control using the PictureBox::Clear method and specifying the desired color. Here, I use a hard-coded value of Color::White simply because it's easier to view the demo's black and gray text on a white background:
    g->Clear(Color::White);
  5. Calcualte where the text will be rendered on the PictureBox control.
    Here, you need to determine the x and y coordinates of the text you will draw. I center the text horizontally, so that's pretty self-explanatory. However, to ensure that the totality of both the original and reflected strings (and not just the first string) is centered, vertically I need to multiply the text's height by 1.5:
    Single x = (picText->Width  - textSize.Width) / 2;
    Single y = (picText->Height - (textSize.Height*1.5)) /2;
    
  6. Prepend the translation to the transformation matrix of the PictureBox control.
    Shortly, you'll need to scale the Graphics so that the reflected text is drawn inverted. However, scaling affects the entire graphics object, not just the text you wish to render. Therefore, you need to reposition the origin of the Graphics object (normally based at 0,0) to the x,y location where you wish to draw the text:
    g->TranslateTransform(x, y);

Managed C++: Use GDI+ to Render Reflected Text

  1. Calculate the true height of the text being rendered.
    You need the reflected text to be drawn starting below the original text. Although you got the text's height from the Measurestring method, that value includes spacing for descenders and whitespace. In this case, you want only the height from the baseline. This involves getting the cell ascent of the font family being used. (You can retrieve the font family from the Font object via its FontFamily property.) However, the cell ascent value is in design units, which must be converted to pixels and then scaled for the font size. After much testing and research, this is the best formula I've found for doing this, where the final value (represented by cy) is the height of the text to be drawn).
    This formula is optimized for characters that are drawn above the baseline. If you use characters that are drawn below the baseline (i.e., characters with descents), you'll see overlap in the descending parts of the characters between the original and reflected text. To remedy this, tweak the calculation by incorporating the cell descent (retrieved via the FontFamily::GetCellDescent method):
    int lineAscent = font->FontFamily->GetCellAscent(font->Style);
    int lineSpacing = font->FontFamily->GetLineSpacing(font->Style);
    Single lineHeight = font->GetHeight(g);
    Single cy = lineHeight * lineAscent / lineSpacing;
    
  2. Draw the original text.
    Note that the x and y positions are at 0 and 0. This is due to your having called TranslateTransform as described earlier:
    g->DrawString(textToDisplay, font, Brushes::Black, 0, 0);
  3. Apply the necessary scaling factor to the graphic object's transformation matrix.
    To reflect text, you use the ScaleTransform method with a value of -1 to invert the text without distorting it:
    g->ScaleTransform(1, -1.0F);
  4. Draw the reflected text.
    Once the scaling factor has been established, you simply draw the reflected text. Note that the y value is set to the negative of the text height multiplied by two so that it displays below the original text:
    g->DrawString(textToDisplay, font, Brushes::Gray, 0, -(cy*2));

[GDIPlusReflectedText.jpg]

Saving and Restoring the GraphicsState

The previous section showed how to use the ScaleTransform method to produce reflected text. In terms of the order in which the text was drawn, the code first drew the original string, scaled the text in the exact opposite direction (in the y direction), and then drew the text's "reflection." However, what if you needed to perform these steps in reverse? In other words, draw the reflection first. You could scale the text twice—once to draw the reflection and then back to the normal scaling. Or, you could save the current state of the Graphics object, set the scaling factor, and then when finished drawing on that scale, restore the original graphics state to draw the normal text. Here's how that would look:

// Save the graphics state
GraphicsState* prevGraphicsState = g->Save();

// Scale to draw reflected text
g->ScaleTransform(1.0F, -1.0F);

// Draw the reflected text

// Restore the saved graphics state
g->Restore(prevGraphicsState);

// Draw the normal text


About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • The first phase of API management was about realizing the business value of APIs. This next wave of API management enables the hyper-connected enterprise to drive and scale their businesses as API models become more complex and sophisticated. Today, real world product launches begin with an API program and strategy in mind. This API-first approach to development will only continue to increase, driven by an increasingly interconnected web of devices, organizations, and people. To support this rapid growth, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds