wpfimagexamlrendertargetbitmapformatted-text

How to present FormattedText with RenderTargetBitmap in an Image?


I can't find the answer on Google.

I have many strings to be presented on a canvas. Each string will be created using the FormattedText() method within a string converter called from an ItemsControl.

The problem with the code is that it needs a ridiculously large width and height in the RenderTargetBitmap() to display all the strings at different positions, even though each string has an approximate actual width of 700 and actual height of 40. (It seems as though the RenderTargetBitmap() needs to be large enough to hold not just the string, but also the position of that string from the drawing context).

How do you create an image of just a single formatted text string with the correct actual height and width of just the Formatted Text and then correctly position that image at the point of "topleft" ?

The converter called from an ItemsControl is defined as:

 public class InkConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var stringviewmodel = value as StringViewModel;
        if (stringviewmodel != null)
        {
           stringviewmodel.ft = new FormattedText(
           stringviewmodel.text,
           CultureInfo.CurrentCulture,
           FlowDirection.LeftToRight,
           new Typeface(new FontFamily("Segoe Script"), FontStyles.Italic, FontWeights.Normal, FontStretches.Normal),
           stringviewmodel.emSize,
           stringviewmodel.color);

           stringviewmodel.ft.TextAlignment = TextAlignment.Left;
           stringviewmodel.ft.LineHeight =(double)stringviewmodel.lineheight;

            Image myImage = new Image();

            DrawingVisual dw = new DrawingVisual();
            DrawingContext dc = dw.RenderOpen();
            dc.DrawText(stringviewmodel.ft, stringviewmodel.topleft);
            dc.Close();


            int Width = 2000;
            int Height = 2000;
            RenderTargetBitmap bmp = new RenderTargetBitmap(Width, Height, 120, 96, PixelFormats.Pbgra32);  

            bmp.Render(dw);
            myImage.Source = bmp;

            return myImage;
        }
        else
        {
            return null;
        }
    }

    public object  ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

}

Addendum:

I wrote this which solved the problem, but does not answer my question. (gt and lt symbols removed).

  1. Changed the itemscontrol to include:

    ItemsControl.ItemContainerStyle Style TargetType="{x:Type FrameworkElement} Setter Property="Canvas.Top" Value="{Binding topleft.Y}" Setter Property="Canvas.Left" Value="{Binding topleft.X}" Setter Property="Height" Value="{Binding ft.Height}"

  2. Removed all positioning from the Converter. The converter now reads as

    public object Convert(object value, Type targetType, object parameter,   
    System.Globalization.CultureInfo culture)
    {
        var stringviewmodel = value as StringViewModel;
        if (stringviewmodel != null)
        {
           stringviewmodel.ft = new FormattedText(
           stringviewmodel.text,
           CultureInfo.CurrentCulture,
           FlowDirection.LeftToRight,
           new Typeface(new FontFamily("Segoe Script"), FontStyles.Italic, FontWeights.Normal, FontStretches.Normal),
           stringviewmodel.emSize,
           stringviewmodel.color);
    
    
            stringviewmodel.ft.TextAlignment = TextAlignment.Left;
            stringviewmodel.ft.LineHeight =(double)stringviewmodel.lineheight;
    
            Image myImage = new Image();
            DrawingVisual dw = new DrawingVisual();
            DrawingContext dc = dw.RenderOpen();
    
            dc.DrawText(stringviewmodel.ft, new Point(0,0));
    
            dc.Close();
    
           double width = stringviewmodel.ft.text.Width - stringviewmodel.text.OverhangLeading -   stringviewmodel.text.OverhangTrailing;
    
            RenderTargetBitmap bmp = new RenderTargetBitmap(
            (int)(width,
            (int)stringviewmodel.ft.Height,
            96d,            
            96d,
            PixelFormats.Pbgra32);  
    
            bmp.Render(dw);
            myImage.Source = bmp;
    
            return myImage;
        }
        else
        {
            return null;
        }
    }
    

Solution

  • You're living in the past with all that WinForms-looking code... this is WPF! First, you don't need any FormattedText objects as you can do this all in XAML very easily. Take this basic example:

    <TextBlock Name="TextBlockToGetImageFrom">
        <Run FontFamily="Arial" FontWeight="Bold" FontSize="40" Foreground="Red" Text="W" />
        <Run FontFamily="Tahoma" Text="indows" FontSize="20" BaselineAlignment="Bottom" />
        <Run FontFamily="Arial" FontWeight="Bold" FontSize="40" Foreground="Red" Text=" P" />
        <Run FontFamily="Tahoma" Text="resentation" FontSize="20" BaselineAlignment="Center" />
        <Run FontFamily="Arial" FontWeight="Bold" FontSize="40" Foreground="Red" Text=" F" />
        <Run FontFamily="Tahoma" Text="ormat" FontSize="20" BaselineAlignment="Top" />
    </TextBlock>
    

    enter image description here

    This just shows some of the flexibility of WPF and there are also far more options not shown here. So once you have your TextBlock set up as required, you can turn it into an image with just a few lines of code using the RenderTargetBitmap class:

    RenderTargetBitmap renderTargetBitmap = 
        new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
    renderTargetBitmap.Render(TextBlockToGetImageFrom); 
    PngBitmapEncoder pngImage = new PngBitmapEncoder();
    pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
    using (Stream fileStream = File.Create(filePath))
    {
        pngImage.Save(fileStream);
    }