Render text onto a RenderTargetBitmap with WPF and C#

[RenderTargetBitmap]

In earlier examples such as Easily render rotated text in a WPF program using C#, I explained how to render text in a WPF program. Those examples draw text on the program’s window at runtime. Sometimes you may want to render text into a bitmap. This example uses the following RenderTextOntoBitmap method do that.

// Render text onto a bitmap.
public static RenderTargetBitmap RenderTextOntoBitmap(
    string text, double dpiX, double dpiY,
    string font_name, double em_size, TextAlignment text_align,
    Brush bg_brush, Pen rect_pen, Brush text_brush,
    Thickness margin)
{
    // Make the Typeface and FormattedText.
    Typeface typeface = new Typeface(font_name);
    FormattedText formatted_text = new FormattedText(
        text, CultureInfo.CurrentUICulture,
        FlowDirection.LeftToRight, typeface,
        em_size, text_brush);
    formatted_text.TextAlignment = text_align;

    // See how big we need the result to be.
    double width = formatted_text.Width -
        formatted_text.OverhangLeading;
    double height = formatted_text.Height;

    // See where we need to draw the text to
    // position it with upper left corner at (0, 0).
    Point text_origin = new Point(0, 0);
    if (text_align == TextAlignment.Center)
        text_origin.X = width / 2;
    else if (text_align == TextAlignment.Right)
        text_origin.X = width;

    // Add room for the bounding rectangle.
    if (rect_pen != null)
    {
        width += rect_pen.Thickness;
        height += rect_pen.Thickness;
        text_origin.X += rect_pen.Thickness / 2;
        text_origin.Y += rect_pen.Thickness / 2;
    }

    // Add the margin.
    width += margin.Left + margin.Right;
    height += margin.Top + margin.Bottom;
    text_origin.X += margin.Left;
    text_origin.Y += margin.Top;

    // Add 1 to width and height to the bottom and
    // right edges of the rectangle aren't clipped.
    int width_int = (int)(width + 1);
    int height_int = (int)(height + 1);

    // Make a DrawingVisual and draw on it.
    DrawingVisual drawing_visual = new DrawingVisual();
    DrawingContext drawing_context = drawing_visual.RenderOpen();
    drawing_context.DrawRectangle(bg_brush,
        rect_pen, new Rect(0, 0, width_int, height_int));
    drawing_context.DrawText(formatted_text, text_origin);
    drawing_context.Close();

    // Copy the result onto a RenderTargetBitmap.
    RenderTargetBitmap bm = new RenderTargetBitmap(
        width_int, height_int, dpiX, dpiY, PixelFormats.Pbgra32);
    bm.Render(drawing_visual);

    return bm;
}

The method’s parameters are:

  • text – The text to draw
  • dpiX, dpiY – The number of dots-per-inch in the X and Y directions that should be used in the bitmap
  • font_name – The name of the font as in “Times New Roman” or “Comic Sans MS”
  • em_size – The font’s size (the height of the M character)
  • text_align – The text’s horizontal alignment (Left, Center, Right, or Justify)
  • bg_brush – The Brush that should be used to fill the bitmap’s background
  • rect_pen – The Pen that should be used to outline the bitmap’s edge
  • text_brush – The Brush that should be used to draw the text
  • margin – The margin that should be left around the text

Note that you can set bg_brush or rect_pen to null to not draw the background or outline.

The method starts by creating the necessary Typeface and FormattedText objects. It sets the FormattedText object’s TextAlignment property to align the text as desired. The method then creates a DrawingVisual on which to draw.

Next the code calculates how large the text will be. (See Get font metrics in a WPF program using C# for details about finding the text’s width.) Depending on the text’s alignment, it then sets the Point text_origin to the position where it needs to draw the text so its upper left corner is at position (0, 0).

The code then determines how much space is needed to hold the text, its margin, and the outline rectangle. For example, if rect_pen is not null, the code adds the width of the pen to the required width and height. (The rectangle’s lines are drawn centered along the outline rectangle so half of each side, the top, and the bottom will lie outside of the rectangle. The code adds 2 times half of the line’s thickness to the width and height.) If rect_pen isn’t null, the code also moves text_origin so the text is drawn shifted far enough to leave room for the rectangle between the text and (0, 0).

Similarly the code adds room for the margin. (The margin is a structure not a class, so the main program cannot pass in the value null for it.)

Now the method creates a DrawingVisual to hold the drawing. It uses that object’s RenderOpen method to get an associated DrawingContext object and uses its methods to draw the text and the rectangle around it.

After closing the DrawingContext, the code creates a RenderTargetBitmap and calls its Render method to make it draw the DrawingVisual onto itself. The method finishes by returning the RenderTargetBitmap.

Yes, this does all seem rather roundabout to me:

  1. Create a DrawingVisual
  2. Make an associated DrawingContext
  3. Draw
  4. Close the DrawingContext
  5. Make a RenderTargetBitmap
  6. Make it render the DrawingVisual

Performing similar operations in GDI+ isn’t completely intuitive, either, although it is shorter:

  1. Make a Bitmap
  2. Make an associated Graphics object
  3. Draw

The RenderTextOntoBitmap method makes drawing text onto a bitmap reasonably simple, and you can use similar techniques to render other drawing commands on a bitmap in WPF.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in drawing, graphics, image processing, wpf, XAML and tagged , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *